本文共 9608 字,大约阅读时间需要 32 分钟。
进程是操作系统进行资源分配的最小单位
进程跟进程之间的资源是隔离的,同一个进程之中的线程可以共享进程的资源。 线程是进程的一个实体,是CPU 调度和分派的基本单位,,依赖于进程存在。 线程无处不在:任何一个程序都必须要创建线程,特别是Java 不管任何程序都必须启动一个
main 函数的主线程; Java Web 开发里面的定时任务、定时器、JSP 和Servlet、异 步消息处理机制,远程访问接口RM 等,任何一个监听事件, onclick 的触发事件等都 离不开线程和并发的知识。核心数、线程数:目前主流CPU 都是多核的。增加核心数目就是为了增加线程数,因为操作系统是通过线程来执行任务的,一般情况下它们是1:1 对应关系,也就是说四核CPU 一般拥有四个线程。但Intel 引入超线程技术后,使核心数与线程数形成1:2 的关系
中止
stop()还是interrupt() ? 判断方法: isInterrupted()和static方法interrupted() suspend()、resume()和stop(),暂定、恢复、停止等方法不建议使用,因为这些方法都不会释放资源。 建议使用interrupt() 修改中断标示位(boolean值),当前获取到CPU运行权限的线程通过isInterrupted()或者interrupted()来判断,之后自己决定是否释放CPU权限。注意:处于死锁状态的线程无法被中断
start()方法让一个线程进入就绪队列等待分配cpu,分到cpu 后才调用实现
的run()方法,start()方法不能重复调用,如果重复调用会抛出异常。 而run 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方 法并没有任何区别,可以重复执行,也可以被单独调用。yield()方法:使当前线程让出CPU 占有权,但让出的时间是不可设定的,
不会释放锁资源。注意:并不是每个线程都需要这个锁的,而且执行yield( )的线 程不一定就会持有锁,我们完全可以在释放锁后再调用yield 方法。 所有执行yield()的线程有可能在进入到就绪状态后会被操作系统再次选中 马上又被执行。join()方法:把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。
比如在线程B 中调用了线程A 的Join()方法,直到线程A 执行完毕后,才会继续 执行线程B。线程的优先级:在Java 线程中,通过一个整型成员变量priority 来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认
优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。 设置线程优先级时,针对频繁阻塞(休眠或者I/O 操作)的线程需要设置较 高优先级,而偏重计算(需要较多CPU 时间或者偏运算)的线程则设置较低的 优先级,确保处理器不会被独占。在不同的JVM 以及操作系统上,线程规划会 存在差异,有些操作系统甚至会忽略对线程优先级的设定。Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调
度以及支持性工作。这意味着,当一个Java 虚拟机中不存在非Daemon 线程的 时候,Java 虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置 为Daemon 线程。我们一般用不上,比如垃圾回收线程就是Daemon 线程。 sleep(不会释放锁),它是线程的方法。 wait()释放锁、 notify()、notifyAll() ,Object的方法,代表着必须是在同步代码中才能执行,只有拿到锁的对象才能调用当前的方法,不然会抛异常。 wait()被唤醒之后会顺着上次执行到的地方继续执行。随便找的一个模拟死锁的代码:
public class DeadLock implements Runnable{ private static Object obj1 = new Object(); private static Object obj2 = new Object(); private boolean flag; public DeadLock(boolean flag){ this.flag = flag; } @Override public void run(){ System.out.println(Thread.currentThread().getName() + "运行"); if(flag){ synchronized(obj1){ System.out.println(Thread.currentThread().getName() + "已经锁住obj1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj2){ // 执行不到这里 System.out.println("1秒钟后,"+Thread.currentThread().getName() + "锁住obj2"); } } }else{ synchronized(obj2){ System.out.println(Thread.currentThread().getName() + "已经锁住obj2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj1){ // 执行不到这里 System.out.println("1秒钟后,"+Thread.currentThread().getName() + "锁住obj1"); } } } }}
Java中线程的状态:
1、初始:创建了线程,但是还没有调用start方法。 2、运行:这里其实分为两种情况,一种就绪等待CPU,一种已经抢到CPU执行中。 3. 阻塞:线程被锁所阻塞。 4. 等待:wait,不满足对应的执行条件而等待,这里其实跟io阻塞很类似,io阻塞需要等待其他硬件执行返回。wait是自己设置了条件,可能被其他线程唤醒之后还是不满足条件,自己又进入wait,如果满足条件则进入就绪状态。 5. 超时等待:自己设置了等待时间,时间到了就可以进入就绪状态强CPU执行权。 6. 终止:线程执行完了任务。 引用一下别人画的图:状态之间的变迁如下图所示Volatile 最轻量的同步机制
Valatile修饰的变量当发生改变时能够被其他线程感知,适合场景:一个线程写,多个线程读。yield() 、sleep()被调用后,都不会释放当前线程所持有的锁。
调用wait()方法后,会释放当前线程持有的锁,而且当前被唤醒后,会重新去竞争锁,锁竞争到后才会执行wait 方法后面的代码。 调用notify()系列方法后,对锁无影响,线程只有在syn 同步代码执行完后才 会自然而然的释放锁,所以notify()系列方法一般都是syn 同步代码的最后一行。 Synchronized和vaolatile后面会详细讲解。线程的状态切换:
ThreadLocal 类接口很简单,只有4 个方法,我们先来了解一下:
• void set(Object value) 设置当前线程的线程局部变量的值。 • public Object get() 该方法返回当前线程所对应的线程局部变量。 • public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0 新增的方法。需要指出的是,当线程结束后,该线程的局部变量将自动 被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。 • protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected 的方法,显然是为 了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1 次调用get() 或set(Object)时才执行,并且仅执行1 次。ThreadLocal 中的缺省实现直接返回一 个null。 创建:public final static ThreadLocalRESOURCE = new ThreadLocal ();线程内部的ThreadLocal,而ThreadLocal内部维护一个static class ThreadLocalMap { }ThreadLocalMap 内部维护的Entry static class Entry extends java.lang.ref.WeakReference > { java.lang.Object value; Entry(java.lang.ThreadLocal threadLocal, java.lang.Object o) { /* compiled code */ } }
静态的,直接类名.方法调用,set、get、remove是主要的方法
看到entry我们就知道是键值对的形式存储。 value是我们的数据源,key是以当前线程的ThreadLocal作为key,它不是静态的,而且是虚引用创建,所以在高并发的情况下如果我们对ThreadLocal的数据使用完之后不调用remove会造成内存泄漏。图中的虚线表示弱引用。
这样,当把threadlocal 变量置为null 以后,没有任何强引用指向threadlocal 实例,所以threadlocal 将会被gc 回收。这样一来,ThreadLocalMap 中就会出现 key 为null 的Entry,就没有办法访问这些key 为null 的Entry 的value,如果当前 线程再迟迟不结束的话,这些key 为null 的Entry 的value 就会一直存在一条强 引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value 永 远不会被访问到了,所以存在着内存泄露。 只有当前thread 结束以后,current thread 就不会存在栈中,强引用断开, Current Thread、Map value 将全部被GC 回收。最好的做法是不在需要使用 ThreadLocal 变量后,都调用它的remove()方法,清除数据。工作密取
即当前线程的Task 已经全被执行完毕,则自动取到其他线程的Task 池中取出Task 继续执行。 ForkJoinPool 中维护着多个线程(一般为CPU 核数)在不断地执行Task,每个线程除了执行自己职务内的Task 之外,还会根据自己工作线程的闲置情况去获取其他繁忙的工作线程的Task,如此一来就能能够减少线程阻塞或是闲置的时间,提高CPU 利用率。 我们要使用ForkJoin 框架,必须首先创建一个ForkJoin 任务。它提供在任务中执行fork 和join 的操作机制,通常我们不直接继承ForkjoinTask 类,只需要直接继承其子类。ForkJoin的使用:它是一个抽象类,我们一般实现它的子类,重写compute方法,然后将我们的业务核心写在该方法,可以设置一个阈值,当达不到这个变量的时候继续拆分,通过join方法加入到线程池中,可以理解为递归调用。当一个大的任务,可以拆分成无数个小任务,且内容核心不变的时候可以使用,当业务数量越大的时候优势越明显,例如统计SQL或者excel的数据量,百万千万数量级别的时候比较明显。
使用:
Static CountDownLatch latch = new CountDownLatch(int);latch.await(), 等待中。可以设置最多等待时间
latch.countdown(); //可以理解为减1,当int = 0 时候等待的线程才会继续往下执行。
创建latch,根据业务需求传入具体需要设置int数量。 需要等待的线程拿到latch 对象设置await等待。 其他线程根据业务需求当运行到业务节点的时候调用latch.countdown(),相当于int减1,当到了0的时候则等待的线程会被唤醒继续执行,或者到了最大的等待时间也会被唤醒。注意,该计数器无法重复使用。
明白前面的CountDownLatch,当前这个CyclicBarrier就很容易理解了。
使用: Private static CyclicBarrier barrier = new CyclicBarrier(int); 在需要等待的线程中使用barrier.await(); 有几个需要等待的线程上面的int就设置几,当所有的线程都执行到barrier.await();这个代码的时候,所有等待的线程开始执行,在这之前先执行到这里的线程都必须等待。Private static CyclicBarrier barrier = new CyclicBarrier(int,RunnablebarrierAction);
它有两个构造函数,还可以再接收一个线程,意思是当执行到当前设置的屏障barrier.await();的时候,Thread这个线程可以汇总之前等待的几个线程执行的结果,让当前这个Thread先执行完之后其他等待的线程再执行。barrier.await();是可以反复调用,当触发当前屏障的时候就会执行一次Thread。
只支持两个线程进行Exchange,当两个线程在处理同一类数据的时候,A类型执行之后调用Exchange,B线程执行之后也调用Exchange,将需要交换的数据丢入exchange(xxx);方法中,JDK保证数据同步安全,使用场景比较狭窄。
public class FutureTaskimplements java.util.concurrent.RunnableFuture { }public interface RunnableFuture extends java.lang.Runnable, java.util.concurrent.Future { void run();}
它实现了Runanble,所以它也可以当做一个runnable扔到Thread里面去执行,同时它又实现了Future,Future可以看做是定义了一系列辅助Callable的方法的接口。
一个FutureTask 对象可以对 调用了Callable 和Runnable 的对象进行包装,由于FutureTask 也是调用了 Runnable 接口所以它可以提交给Executor 来执行。例如OKhttp就是。
Future里面定义什么方法来辅助呢?
public interface Future{ boolean cancel(boolean b); boolean isCancelled(); boolean isDone(); V get() throws java.lang.InterruptedException, java.util.concurrent.ExecutionException; V get(long l, java.util.concurrent.TimeUnit timeUnit) throws java.lang.InterruptedException, java.util.concurrent.ExecutionException, java.util.concurrent.TimeoutException;}
从方法名我们就知道什么意思了,这里就不详细讲解了。
Future 就是对于具体的Runnable 或者Callable 任务的执行结果进行取消、查 询是否完成、获取结果。必要时可以通过get 方法获取执行结果,该方法会阻塞 直到任务返回结果。我们在前面看到线程的中止方法并没有cancel这种,它的核心其实也是调用interrupt(),所以能不能成功中止线程还是要看具体的操作者有没有添加对应的判断,愿意停下来不。
本篇算是一个科普,讲的都是基础,很多都是概念,但是还是有必要了解的东西。