尚未搞明白的
synchronized和volatile都体现在字节码上(monitorenter、monitorexit),主要是加入了内存屏障。而Lock,是纯粹的java api。
Thread
wait notify
aa.wait()
先获取aa锁,然后执行wait()方法,然后将执行aa.wait()的当前线程转入阻塞状态,让出CPU的控制权,释放aa锁。
aa.wait() 后 线程将会被移到等待队列WaitQueue中,进入等待状态 ,释放aa锁 aa.notify()/aa.notifyAll() 后,会把WaitQueue中等待aa的线程移入到SynchronizedQueue
aa.wait()和object.nitify()和aa.nitifyAll()要求都必须放在同步代码块中,且同步代码块锁住的也必须得是aa对象,否则运行时回报错 java.lang.IllegalMonitorStateException
aa.notify()
@Test public void testNotify(){ Object obj = new Object(); // this statement will throw exception when run, the exception is java.lang.IllegalMonitorStateException obj.notify(); System.out.println("Program complete!"); } @Test public void testNotify2(){ Object obj = new Object(); synchronized (obj) { // 不再抛出异常 obj.notify(); } // 此语句会正常输出 System.out.println("Program complete!"); } @Test public void testNotify3(){ Object obj = new Object(); Object obj2 = new Object(); synchronized (obj2) { // this statement will throw exception when run, the exception is java.lang.IllegalMonitorStateException // aa.notify必须的放在同步代码块中,且锁定的对象必须是aa obj.notify(); } // 此语句会正常输出 System.out.println("Program complete!"); }
aa.notifyAll()
叫醒其他所有的因为执行了aa.wait()而陷入阻塞状态的线程
调用一个lock.notify(); 只能唤醒一个因lock.wait();而陷入等待的线程 调用2个lock.notify(); 只能唤醒2个因lock.wait();而陷入等待的线程 为了保证唤醒所有因lock.wait();而陷入等待的线程 可以调用lock.notifyAll();
wai(long n)
先释放锁,陷入阻塞x秒, x秒过后继续开始竞争锁。 如果没有其他线程霸占锁,则该线程会继续得到锁后继续执行 否则继续陷入阻塞,直到可以获取锁为止
代码1
package com.haobin.multiplythread.notifywait.pack003; /** * <pre> * wait(x) 用法 * 先释放锁陷入阻塞x秒, * x秒过后继续开始竞争锁。 * 如果没有其他线程霸占锁,则该线程会继续得到锁后继续执行 * 否则继续陷入阻塞,直到可以获取锁为止 * Auther: haobin * Date: 2019-5-10 * </pre> */ public class MyRunnable { static private Object lock = new Object(); static private Runnable runnable1 = new Runnable() { @Override public void run() { try { synchronized (lock) { System.out.println("wait begin timer=" + System.currentTimeMillis()); // 在本程序中 lock.wait(100); 将自动唤醒自己, 如果改为lock.wait(); 则是 由t2线程唤醒 // lock.wait(100); 则本线程runnable1执行完毕,runnable2才会执行 // lock.wait(5000); 和 lock.wait(); 都会导致本线程runnable1 执行一半后,runnable2继续执行,runnable2执行完毕后 runnable1继续开始执行 lock.wait(100); System.out.println("wait end timer=" + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } }; static private Runnable runnable2 = new Runnable() { @Override public void run() { synchronized (lock) { System.out.println("notify begin timer=" + System.currentTimeMillis()); lock.notify(); System.out.println("notify end timer=" + System.currentTimeMillis()); } } }; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(runnable1); t1.start(); Thread.sleep(2000); Thread t2 = new Thread(runnable2); t2.start(); } }
代码
wait/notify、notifyAll都必须得放在同步代码块中,且要求都必须锁住的是当前对象,且notify不具备释放锁的功能
public class SimpleWN_HB002 { final static Object object1 = new Object(); final static Object object2 = new Object(); final static Object object3 = new Object(); public static class T1 extends Thread { public void run() { synchronized (object1) { // 10 System.out.println(System.currentTimeMillis() + ":T1 start! "); try { System.out.println(System.currentTimeMillis() + ":T1 wait for object1 "); // 如果这里wait和 上面10行synchronized的不是同一个对象,则会报错 java.lang.IllegalMonitorStateException // 把10行的object1改为 object2或 object3 都会报错 object1.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + ":T1 end!"); } } } public static class T2 extends Thread { public void run() { synchronized (object1) { //25 System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread"); // 如果这里notify和 上面25行synchronized的不是同一个对象,则会报错 java.lang.IllegalMonitorStateException // 把10行的object1改为 object2或 object3 都会报错 // 本语句不会释放objetc1 锁,因此只有T2线程执行结束后,T1线程才会继续往下执行 object1.notify(); System.out.println(System.currentTimeMillis() + ":T2 last but one!"); try { Thread.sleep(8000); } catch (InterruptedException e) { } System.out.println(System.currentTimeMillis() + ":T2 end!"); } } } public static void main(String[] args) throws Exception{ Thread t1 = new T1(); Thread t2 = new T2(); t1.start(); Thread.sleep(1000); t2.start(); } }
先notify 后wait会导致wait陷入阻塞
/** * <pre> * 先notify,后wait并不会报错,但是这会导致后面执行的wait会一直陷入阻塞: * Auther: haobin * Date: 2019-5-10 * </pre> */ public class SimpleWN_HB003 { final static Object object1 = new Object(); public static class T1 extends Thread { public void run() { synchronized (object1) { System.out.println(System.currentTimeMillis() + ":T1 start! "); try { System.out.println(System.currentTimeMillis() + ":T1 wait for object1 "); object1.wait(); // 21行 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(System.currentTimeMillis() + ":T1 end!"); } } } public static class T2 extends Thread { public void run() { synchronized (object1) { System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread"); object1.notify(); System.out.println(System.currentTimeMillis() + ":T2 end!"); } } } public static void main(String[] args) throws Exception{ Thread t1 = new T1(); Thread t2 = new T2(); t2.start(); Thread.sleep(2000); // t1 会卡在 21行不动,陷入阻塞 t1.start(); } }
陷入wait()时调用interrupt()会出现异常
当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常。
public class Test { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep(2000); a.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } } public class Service { public void testMethod(Object lock) { try { synchronized (lock) { System.out.println("begin wait()"); lock.wait(); System.out.println(" end wait()"); } } catch (InterruptedException e) { e.printStackTrace(); System.out.println("出现异常了,因为呈wait状态的线程被interrupt了!"); } } } public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { Service service = new Service(); service.testMethod(lock); } }
wait经典实用模式
wait的用法一般都是
synchronized (obj) { while (<condition does not hold>) …… obj.wait(); …… }
你如果去看Object.wait()
方法的Javadoc的话会发现官方也是建议上面这样的用法
StackOverflow上有一个问题里一个叫xagyg的回答解释的比较清楚,有兴趣的可以看下。 简单来说因为:
wait前会释放监视器,被唤醒后又要重新获取,这瞬间可能有其他线程刚好先获取到了监视器,从而导致状态发生了变化, 这时候用while循环来再判断一下条件(比如队列是否为空)来避免不必要或有问题的操作。 这种机制还可以用来处理伪唤醒(spurious wakeup),所谓伪唤醒就是no reason wakeup
,对于LockSupport.park()
来说就是除了unpark
和interrupt
之外的原因。
LockSupport
也会有同样的问题,所以看AQS的源码会发现很多地方都有这种re-check的思路,我们下一篇文就来看下AbstractQueuedSynchronizer
类的源码。
Sleep
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法时,线程虽然休眠了,但是对象的锁并没有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
join
概述
join不需要写在同步代码块里面
假设有一个 aa线程对象,则 aa.join(); 暂停当前正在执行 aa.join();的线程使之进入等待状态, 直到 aa 所对应的线程运行终止之后,当前线程才会获得继续执行的机会, 注意:aa.join() 不是暂停aa对象所对应的线程
在join过程中,如果当前线程对象被中断,则当前线程出现异常。
线程必须要先start,才能join,只有启动了,才能对线程进行操作
join的应用场景
join可以实现线程的串行化 比如如果A线程要是用B线程的最终运行结果,则可以在A线程中写 b线程对象.join()
在很多情况下,主线程创建并启动子线程,如果子线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。 如果主线程想等待子线程执行完成之后再结束, 比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待其他线程对象执行结束。 在多线程程序中,如果在一个线程运行的过程中要用到另一个线程的运行结果,则可进行线程的串型化处理。
join与wait sleep的区别
阅读jdk源代码可以得知:join()方法的核心本质上就是调用wait(0)方法, join(x)方法的核心本质上就是调用wait(x)方法, 由此可见,当前线程执行完obj.wait()后会陷入等待,被等待的obj线程会得到执行, 被等待的线程执行完之后会在退出前调用notifyAll()通知所有的等待线程继续执行。
方法join具有使线程排队运行的作用,有些类似同步的运行效果。
join与synchronized的区别是:join在内部使用wait()方法进行等待, 而sychronized关键字使用的是“对象监视器”原理做为同步。
方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。 Thread.sleep(long)方法却不释放锁。
代码1
public class TestJoin { public static void main(String args[]) { MyRunner r = new MyRunner(); Thread threadObject001 = new Thread(r); threadObject001.start(); try { /** * 暂停当前正在执行threadObject001.join();的线程, * 直到 threadObject001 所对应的线程运行终止之后, * 当前线程才会获得继续执行的机会 */ threadObject001.join(); // 7行 } catch (InterruptedException e) { e.printStackTrace(); } // 经测试发现,程序最终运行结果是: 先输出1000个 "子线程。。。。。" ,然后才是输出这里的 "主线程。。。。。。" for (int i = 0; i < 2000; i++) { System.out.println("主线程:" + i); } } } class MyRunner implements Runnable { public void run() { for (int i = 0; i < 2000; i++) { System.out.println("子线程: " + i); //29 } } }
停止 线程
概述
interrupt interrupted() isInterrupted()都是 Thread 类的方法,只不过 interrupted()是static,其他两个是非static
中断线程的两种方式
1. 使用 退出逻辑标识位
// 标志一般使用volatile进行修饰,使其读可见,然后通过设置这个值来控制线程的运行,这已经成了约定俗成的套路。 private volatile boolean flag= true; public void run() { while (flag) { 。。。。。 } }
2. 使用interrupt方法终止线程
java while (!Thread.currentThread().isInterrupted()){ 。。。。。 }
interrupt方法通常不能中断一些处于阻塞状态的I/O操作。比如写文件,或者socket传输 等。这种情况,需要同时调用正在阻塞操作的close方法,才能够正常退出。
threadObject.interrupt()
interrupt() 是 Thread 类的非 static 方法
threadObject.interrupt() 会把 threadObject 对象的线程的中断标志位设置为true,本方法无返回值,本方法并不具备强制中断线程的功能,
-
如果 threadObject 对应的线程:处于1. object.wait() 2、Thread.sleep() 3、 threadObject .join(), 4. Condition.await(), 5. Lock.lockInterruptibly() 这五种状态的任何一种状态, threadObject 线程就会立即抛出InterruptEdException异常,然后中断标志位就会设置为false,之所以抛出异常后中断标志位会被重置为false,是因为interrupt()方法既然已经抛出了异常,这已经达到中断线程的目的了,即:已经响应了中断操作,所以jdk的编写者认为此时就应该把中断标志位重置为false。
-
如果 threadObject 对应的线程:处于 LockSupport.park(),则 threadObject 线程会终止阻塞,把中断标志位设置为true,但是不会抛出异常,程序会继续往下执行。
注意:Lock.lock() 是不会响应中断的 这说明LockSupport.park() 创作者的意图是:相应中断,但是具体处理逻辑交给开发者。 下面的这个五个方法:1. object.wait() 2、Thread.sleep() 3、 threadObject .join(), 4. Condition.await(), 5. Lock.lockInterruptibly() 创作者的意图则是: 直接由编译器自己来处理中断请求: 抛出异常,然后中断标志位设置为false
-
如果 threadObject 对应的线程 没有1、wait() 2、sleep() 3、join(), 4. await(), 5、Lock.lockInterruptibly() 这五种代码,或者有但是程序还没有执行到这五种代码处,则本方法只会把 threadObject 的线程的中断标志位设置为true,程序不会终止,而是会继续运行。 此时又分两种:
-
程序运行一会后运行到了 这五种代码处,则程序会抛出异常,中断标志位就会重置为false
-
threadObject 对象的线程根本就没有这五种代码,则程序会继续往下运行,如果程序代码写的正常结束,则线程会正常结束; 如果程序写的是死循环,则程序会继续死循环下去。
-
java.lang.Thread#interrupted 源代码是:
public static boolean interrupted() { return currentThread().isInterrupted(true); }
换而言之,如果 threadObject 对应的线程没有这五种代码,则我们调用 threadObject .interrupt() 根本是无法终止线程的,解决办法是 我们在 threadObject 线程中可以添加
// 注意: 这里严格禁止 写成 threadObject.isInterrupted() ,因为threadObject.isInterrupted() 不具备重置中断标志位为false的功能 // 如果中断标志位是true,则 Thread.interrupted() 会返回ture,同时把中断标志位重置为false,这刚好和 上面说的“threadObject.interrupt(), threadObject 线程如果运行正处于这五种的任何一种时,会抛出异常,重置中断标志位为false ” 的操作是一致的 // 查看 interrupted()源代码可以得知,这里写this.interrupted() 或者Thread.interrupted() ,甚至写 threadObject.interrupted() 都是可以的,因为最终都是调用 当前线程的isInterrupted(true) 方法 if ( Thread.interrupted() ) { /* 中断异常的处理代码 进入此if语句,这意味着当前现成的中断标志位已经又被重置为了false, 如果我们想继续抛出异常给上一层调用者 可以继续 Thread.interrupted() */ }
public static boolean java.lang.Thread.interrupted()
会检测当前线程中断标志位是否为true,如果为true,则本方法会把中断标志位设置为false(专业术语叫做清除中断)并返回true;
如果当前线程中断标志位本来就是false,则直接返回false
interrupted 是staic方法,查看源代码得知只能作用于当前线程
interrupt和isInterrupted() 都是非static方法,都作用于调用该方法的线程对象所对应的线程
public boolean java.lang.Thread. isInterrupted ()
threadObject.isInterrupted() 只检测当前线程中断标志位是否为true,是则返回ture,否则返回false。 此方法不会修改threadObject线程的中断标志位状态
Lock.lock()无法响应 interrupt中断,Lock.lockInterruptibly()则可以响应interrupt()中断
代码
package com.haobin.multiplythread.interrupt.pack006; /** * <pre> * 本方法演示了 interrupt interrupted isInterrupted 三者结合的用法 * 即便某个线程没有 1. wait() 2、sleep() 3、join(), 4. await(), 5. Lock.lockInterruptibly() * 我们也可以通过 * 在父线程 调用 sonThreadObject.interrupt * 子线程 调用 this.interrupted()或者Thread.isInterrupted(), 外加tyr catch。。。。 * 的方式达到 父线程 优雅的结束 子线程的目的 * Auther: haobin * Date: 2019/5/11 0011 * </pre> */ public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); // Thread.sleep(1000); thread.interrupt(); } catch (Exception e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } } public class MyThread extends Thread { @Override public void run() { super.run(); try { // 加锁 的目的是 为了 希望 18行 19行 的语句不要间断, // 这样可以测试 interrupted() 调用后,是否会自动清除 终止标识位 synchronized (this) { for (int i = 0; i < 500000; i++) { // 18 System.out.println("1 i = " + i + ", this.isInterrupted() = " + this.isInterrupted() ); // 19 if (Thread.interrupted()) { // this.isInterrupted() 肯定会返回false, 因为19行调用完 Thread.interrupted() 后 就会清楚中断标志位 System.out.println("2 this.interrupted() = " + this.isInterrupted()); // Thread.interrupted() 肯定会返回false, 因为19行调用完 Thread.interrupted() 后 就会清楚中断标志位 System.out.println("3 Thread.interrupted() = " + Thread.interrupted()); throw new InterruptedException(); } System.out.println("4 i=" + (i + 1)); } } System.out.println("我在for下面"); } catch (InterruptedException e) { System.out.println("4 进MyThread.java类run方法中的catch了!"); System.out.println("5 this.interrupted() = " + this.interrupted()); e.printStackTrace(); } } }
interrupt
interrupt() 是Thread.java类中的非static方法,即:interrupt方法是Thread类的实例方法,在执行interrupt方法的时候并不需要获取Thread实例的锁定,任何线程在任何时刻,都可以通过线程实例来调用其他线程的interrupt方法。
threadObj.interrupt(); 仅仅是把threadObj的中断标志位设置为true而已,并不是真的停止线程。
执行完 threadObj.interrupt() 后, threadObj对应的线程的 threadObj.isInterrupted()方法不一定返回值就是true, 因为
-
如果threadObject对应的线程:处于1. object.wait() 2、Thread.sleep() 3、threadObject.join(), 4. Condition.await(), 5. Lock.lockInterruptibly() 这五种状态的任何一种状态,threadObject线程就会立即抛出,此后调用 threadObj.isInterrupted() 返回值必然是false
示例:
public static void f1() throws Exception { Thread aaThread = new Thread(() -> { while (true) { try { TimeUnit.MILLISECONDS.sleep(4); } catch (InterruptedException e) { // 这里的 Thread.currentThread().isInterrupted() 返回值是 false System.out.println(Thread.currentThread().isInterrupted()); break; } } }); aaThread.start(); TimeUnit.MILLISECONDS.sleep(1); aaThread.interrupt(); }
-
如果aa对应的线程 没有1、wait() 2、sleep() 3、join(), 4. await(), 5、Lock.lockInterruptibly() 这五种代码,则调用 threadObj.isInterrupted() 返回值必然是true
将方法interrupt()与return结合使用也能实现停止线程的效果。不过还是建议使用抛异常的方法来实现线程的停止,因为在catch块中还可以将异常向上抛,使线程停止的事件得以传播。
郝斌:我猜测: 假设你是jdk编写者,为了防止暴力强制关闭程序,interrupt 方法也只能在线程中增加一个停止标记位,除此别无他法。interrupt为了防止暴力关闭,所以自然不会强制关闭 正常运行的代码,只会对很耗时长时间运行的代码(一般都是因为IO 锁、 阻塞)强制关闭抛出异常,这也就解释为为什么threadObj.interrupt() 只对1. wait() 2、sleep() 3、join(), 4. await(), 5. Lock.lockInterruptibly() 这五个操作起作用且只会让这五个操作抛出异常。
interrupt能抛出异常的方法有五个:
threadObj.interrupt()能让线程抛出InterruptedException异常的方法暂时已知的有5个:1. wait() 2、sleep() 3、join(), 4. Condition.await(), 5. Lock.lockInterruptibly()
interrupt方法其实只是改变了中断状态而已。而sleep、wait和join,await,lockInterruptibly这些方法的内部会不断的检查中断状态的值,从而自己抛出InterruptEdException。所以,如果在线程进行其他处理时(指非sleep,非wait, 非join,非await,非lockInterruptibly的其他处理),调用了它的interrupt方法,线程也不会抛出InterruptedException的,只有当线程走到了sleep, wait, join,await, lockInterruptibly这些方法的时候,才会抛出InterruptedException。若是没有调用sleep, wait, join,await, lockInterruptibly这些方法,或者没有在线程里自己检查中断状态,没有自己抛出InterruptedException,那InterruptedException是不会抛出来的。 比如线程执行yield方法期间调用了它的interrupt方法,线程是不会抛出InterruptedException异常的
interrupt与 LockSupport.park()
执行aa.interrupt(), 如果aa对应的线程:处于 LockSupport.park(),则aa线程会终止阻塞,把中断标志位设置为true,但是不会抛出异常,程序会继续往下执行。
这说明LockSupport.park() 创作者的意图是:相应中断,但是具体处理逻辑交给开发者。 下面的这个五个方法:1. object.wait() 2、Thread.sleep() 3、threadObject.join(), 4. Condition.await(), 5. Lock.lockInterruptibly() 创作者的意图则是: 直接由编译器自己来处理中断请求: 抛出异常,然后中断标志位设置为false
API 文档 中文翻译:
void java.lang.Thread.interrupt() 非static方法 中断线程。 如果当前线程没有中断它自己(这在任何情况下都是允许的),则该线程的 checkAccess 方法就会被调用,这可能抛出 SecurityException。 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个 InterruptedException。 如果该线程在可中断的通道上的 I/O 操作中受阻,则该通道将被关闭,该线程的中断状态将被设置并且该线程将收到一个 ClosedByInterruptException。 如果该线程在一个 Selector 中受阻,则该线程的中断状态将被设置,它将立即从选择操作返回,并可能带有一个非零值,就好像调用了选择器的 wakeup 方法一样。 如果以前的条件都没有保存,则该线程的中断状态将被设置。 中断一个不处于活动状态的线程不需要任何作用。 抛出: SecurityException - 如果当前线程无法修改该线程
Sleep与Interrupt
thread.interrupt();
如果thread对应的线程正处于sleep状态,则执行本语句后,
第一:会清除中断状态,
第二:thread对应的线程会立即跑出InterruptedException异常
参见 代码1
如果thread对应的线程虽然有sleep语句,但是程序还没有运行到sleep语句,则thread对应的线程是不会抛出异常的,只有thread对应的线程运行到了sleep,才会抛出InterruptedException异常
参见代码2
代码1
/** * sleep中断后 抛出异常并会自动清除中断状态 * 如果希望sleep后可以判断中断状态, * 则必须在sleep的异常处理中,再次设置Thread.currentThread().interrupt(); * @author Administrator * */ public class InterruputSleepThread { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread() { @Override public synchronized void run() { while (true) { if (Thread.currentThread().isInterrupted()) { System.out.println("Interruted!"); break; } try { Thread.sleep(5000); } catch (InterruptedException e) { /** * this.isInterrupted() 返回false 说明threadObject.interrupt(); * 如果threadObject对应的线程处于sleep状态,则执行本语句后,会清除中断状态 */ System.out.println("Interruted When Sleep " + this.isInterrupted()); // 设置中断状态 ,此步骤不能省略 因为Thread.sleep()在由于interrupt()而抛出异常后,会自动清除中断标记 // 所以如果把本行代码注释掉,则会导致程序陷入死循环 Thread.currentThread().interrupt(); } Thread.yield(); } } }; t1.start(); Thread.sleep(2000); t1.interrupt(); } }
代码2
/** * <pre> * thread.interrupt(); * 如果thread对应的线程虽然有sleep语句,但是程序还没有运行到sleep语句,则thread对应的线程是不会抛出异常的, * 只有thread对应的线程运行到了sleep,才会抛出InterruptedException异常 * Auther: haobin * Date: 2019/5/11 0011 * </pre> */ public class SleetInterruptTest005 { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { public void run(){ try { // 程序不会在for循环中抛出异常,只会在 21 行抛出异常 for(int i=0;i<1000000;i++){ System.out.println("i="+(i+1)); } System.out.println("run begin"); Thread.sleep(10); // 21 行 System.out.println("run end"); } catch (InterruptedException e) { e.printStackTrace(); } } }); t1.start(); t1.interrupt(); System.out.println("end!"); } }
interrupted()
public static boolean java.lang.Thread.interrupted()
静态方法,2个功能:
-
测试当前线程是否已经中断,如果该线程已经中断,则返回 true;否则返回 false, 因为interrupted()是static方法,所以一般的写法是。Thread.currentThread().isInterrupted()
-
interrupted()方法具有清除中断状态的功能。因此,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
返回:
如果当前线程已经中断,则返回 true;否则返回 false。
Thread.interrupted()等价于 Thread.currentThread().interrupted(true),查看jdk 1.8 源代码可以得知:
public static boolean interrupted() { return currentThread().isInterrupted(true); }
isInterrupted
public boolean java.lang.Thread. isInterrupted ()
非静态方法 测试线程是否已经中断。线程的中断状态 不受该方法的影响。 返回: 如果该线程已经中断,则返回 true;否则返回 false。
isInterrupted()方法没有清除中断状态的功能,因此可以连续多次调用,即如果第一次返回true,后续再连续多次调用,返回的依然都是true
一般使用 isInterrupted () 方法的方式是:
Thread.currentThread().isInterrupted()
synchronzied
概述
synchronized(类对象名aa) //1行 { 同步代码块 //3行 } //4行
synchronized(类对象名aa) 的含义是:判断aa是否已经被其他线程霸占,如果发现已经被其他线程霸占,则当前线程陷入等待中,如果发现aa没有被其他线程霸占,则当前线程霸占住aa对象,并执行3行的同步代码块,在当前线程执行3行代码时,其他线程将无法再执行3行的代码(因为当前线程已经霸占了aa对象),当前线程执行完3行的代码后,会自动释放对aa对象的霸占,此时其他线程会相互竞争对aa的霸占,最终CPU会选择其中的某一个线程执行最终导致的结果是:一个线程正在操作某资源的时候,将不允许其它线程操作该资源,即一次只允许一个线程处理该资源Synchronized修饰一个方法时,实际霸占的是该方法的this指针所指向的对象即Synchronized修饰一个方法时,实际霸占的正在调用该方法的对象霸占的专业术语叫锁定,霸占住的那个对象专业术语叫做监听器
synchronized不具有继承性 如果父类的方法是同步方法,子类继承后该方法也是同步方法,但是如果子类重写了父类的方法并且没有标注为synchronized,则子类的该方法就是非同步方法,即synchronized不具有继承性
方法中的变量不存在非线程安全问题, 永远都是线程安全的。
方法中的变量不存在非线程安全问题,永远都是线程安全的。因为一个对象的同一个方法被调用多次,则该方法中的局部变量也会生成多份
多个线程访问同一个对象中的同步方法时一定是线程安全的
多个线程访问同一个对象的多个方法的问题
假设有A和B两个线程
-
A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
-
A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。
脏读
其他线程可以随意调用anyObject对象的所有非同步方法引起的脏读示例
当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确地讲,是获得了对象的锁,此时其他线程必须等A线程执行完毕才可以调用anyObject对象的x方法和其他的所有同步方法,但其他线程可以随意调用anyObject对象的所有非同步方法,不过此时其他线程调用anyObject对象的非同步方法可能会引起脏读,
比如说:假设有一个A对象有很多个属性,T1线程正在通过set方法设置aa对象多个属性的值,T2线程正在通过get方法读取aa对象多个属性的值,很明显这必然会出现脏读,解决办法是把读写方法都设为互斥的同步方法。
代码
/** * <pre> * 脏读 其他线程可以随意调用anyObject对象的所有非同步方法引起的脏读示例 * 当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确地讲,是获得了对象的锁,此时其他线程必须等A线程执行完毕才可以调用anyObject对象的x方法和其他的所有同步方法,但其他线程可以随意调用anyObject对象的所有非同步方法,不过此时其他线程调用anyObject对象的非同步方法可能会引起脏读, * 比如说:假设有一个A对象有很多个属性,T1线程正在通过set方法设置aa对象多个属性的值,T2线程正在通过get方法读取aa对象多个属性的值,很明显这必然会出现脏读,解决办法是把读写方法都设为互斥的同步方法。 * Auther: haobin * Date: 2019-07-29 * </pre> */ class A { private String name = "aa"; private int a = 1; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getA() { return a; } public void setA(int a) { this.a = a; } public synchronized void setAll(int a, String name) { this.a = a; try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } this.name = name; } // 这里加 synchronized 输出的是 2 bb, 不加 synchronized 则输出2 aa public synchronized void getAll() { System.out.printf(this.a + ", " + this.name); } } public class TestSynchronized005 { public static void main(String[] args) throws Exception { A aa1 = new A(); Thread t1 = new Thread(() -> { aa1.setAll(2, "bb"); }); t1.start(); Thread.sleep(100); aa1.getAll(); } }
synchronized (aa) 锁住的是aa指向的对象,锁住的不是aa变量本身
public class A implements Runnable{ public B bb = new B(); public A() { } public void run() { B bb2 = bb; /** * 在这里 写synchronized (bb2) 和 写synchronized (bb) 是一样的运行结果 * 这说明 假设aa是一个类对象的引用, 则synchronized (aa) 锁住的是aa指向的对象,锁住的不是aa变量本身 */ synchronized (bb2) { try { while (true) { System.out.println("14 " + Thread.currentThread().getName()); Thread.sleep(500); System.out.println("16 " + Thread.currentThread().getName()); } } catch (InterruptedException e) { e.printStackTrace(); } } } }
synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。这意味着,在一个synchronized方法/块的内部调用本类的其他synchronized方法时,是永远可以得到锁的。
当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。郝斌:其实所谓调用父类的同步方法实际都是调用子类自己的方法,严格意义上讲,不存在子类调用父类方法的情况,所有父类的方法都继承到了子类对象中。
当一个线程锁住某个对象但是在执行过程中抛出异常后,该线程会终止运行,且会释放锁。
synchronized不具有继承性
如果父类的方法是同步方法,子类继承后该方法也是同步方法,但是如果子类重写了父类的方法并且没有标注为synchronized,则子类的该方法就是非同步方法,即synchronized不具有继承性
假设有两个线程,如果一个锁定aa对象,另一个锁定aa对象中的属性str,则这两个线程是无法做到同步的
synchronized修饰static静态方法等同于锁定Class
即: 多个对象只有访问synchronized + static 方法法时才会串行访问
synchronized 修饰static方法 等价于
synchronized(类的名.class)
synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。
Class锁对所有的Class类型的对象都起作用,即: 假设A类有a1 a2 a3 a4多个对象, A类有多个static的同步方法,则a1 a2 a3 a4 都只能以同步的方式访问A类的这些 static 并且是 synchronized 的方法 ,但是对非static方法无法达到同步效果,对非synchronized方法也无法达到同步效果,对static方法也无法达到同步效果,对synchronized也无法达到同步效果,只能对static + synchronized方法达到同步效果。
具体参见 示例: Class锁对所有的Class类型的对象都起作用
代码1
package com.haobin.multiplythread.synchronizeds.statics.pack002; /** * <pre> * synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。 * Class锁对所有的Class类型的对象都起作用, * 即: 假设A类有a1 a2 a3 a4多个对象, A类有多个static的synchronized方法, * 则a1 a2 a3 a4 都只能以同步的方式访问A类的这些 static 并且是 synchronized 的方法 , * 但是对非static方法无法达到同步效果,对非synchronized方法也无法达到同步效果, * 对static方法也无法达到同步效果,对synchronized也无法达到同步效果, * 只能对static + synchronized方法达到同步效果。 * Auther: haobin * Date: 2019/11/15 * </pre> */ class A{ private String name; private int age; public A(String name, int age) { this.name = name; this.age = age; } // 普通方法 多个对象访问 普通方法不会串行访问 public void printA(){ System.out.println(Thread.currentThread().getName() + ", " + name + ", " + age); } // synchronized 但是非 static 方法, 多个对象访问 synchronized 但是非 static 方法 不会串行访问 public synchronized void printB(){ System.out.println(Thread.currentThread().getName() + ", " + name + ", " + age); } // synchronized + static 方法, 多个对象只有访问synchronized + static 方法法时才会串行访问 public synchronized static void printC(){ System.out.println(Thread.currentThread().getName() ); } public static synchronized void s1() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } public class StaticsSynchronoizedTest002 { public static void main(String[] args) throws Exception { A aa = new A("aa", 1); Thread t1 = new Thread( ()->aa.printA(), "thread_haobin_001"); Thread t2 = new Thread( ()->aa.printA(), "thread_haobin_002"); Thread t3 = new Thread( ()->aa.printB(), "thread_haobin_003"); Thread t4 = new Thread( ()->aa.printC(), "thread_haobin_004"); Thread t = new Thread( ()->A.s1(), "thread_haobin_004"); t.start(); Thread.sleep(1000); /** * 最终发现,只有 t4线程 会等待 t 线程的结束后才可以执行 * t1 t2 t3会和 t 线程 并行执行 */ t1.start(); t2.start(); t3.start(); t4.start(); } }
代码2
/** * <pre> * synchronized修饰static静态方法 * synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。 * Class锁对所有的Class类型的对象都起作用,即: 假设A类有a1 a2 a3 a4多个对象, A类有多个static的同步方法,则a1 a2 a3 a4 都只能以同步的方式访问A类的这些static同步方法 * 具体参见 示例: Class锁对所有的Class类型的对象都起作用 * * </pre> * auther haobin <br /> * date 2019-5-13 **/ public class Run { public static void main(String[] args) { Service service1 = new Service(); Service service2 = new Service(); ThreadA a = new ThreadA(service1); a.setName("A"); a.start(); ThreadB b = new ThreadB(service2); b.setName("B"); b.start(); } } import com.haobin.util.DateUtil; import java.util.Date; public class Service { synchronized public static void printA() { try { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + DateUtil.formatDate(new Date()) + "进入printA"); Thread.sleep(3000); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + DateUtil.formatDate(new Date()) + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { try { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + DateUtil.formatDate(new Date()) + "进入printB"); Thread.sleep(3000); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + DateUtil.formatDate(new Date()) + "离开printB"); } catch (Exception e) { e.printStackTrace(); } } } public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { super(); this.service = service; } @Override public void run() { service.printA(); } } public class ThreadB extends Thread { private Service service; public ThreadB(Service service) { super(); this.service = service; } @Override public void run() { service.printB(); } }
只要对象不变,既使对象的属性被改变,也不会影响所有相关线程的同步效果
ThreadLocal
没搞明白
摘自网络: synchronized这类线程同步的机制可以解决多线程并发问题,如果多个线程要访问同一份变量的内容。为了防止在多线程访问的过程中,可能会出现的并发错误。不得不对多个线程的访问进行同步,这样也就意味着,多个线程必须先后对变量的值进行访问或者修改,这是一种以延长访问时间来换取线程安全性的策略。ThreadLocal类为每一个线程都维护了自己独有的变量拷贝。每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了,那就没有任何必要对这些线程进行同步,它们也能最大限度的由CPU调度,并发执行。并且由于每个线程在访问该变量时,读取和修改的,都是自己独有的那一份变量拷贝,变量被彻底封闭在每个访问的线程中,并发错误出现的可能也完全消除了。对比前一种方案,这是一种以空间来换取线程安全性的策略
问题: 如果一个线程修改了共享变量,另一个线程就要立即看到修改结果,这种情况应该不适合使用ThreadLocal吧? ThreadLocal到底用于哪些场合?
解决ThreadLocal.get()第一次返回值为null的问题, 感觉没什么用
ThreadLocal 在类中通常定义为静态类变量
因为set(v), v变量是绑定保存到了具体的Thread线程中的,而不是绑定保存到TheadLocal中的。 即 <线程,线程要绑定对象> 这对关系是保存在线程对象的 threadLocals 属性中的,threadLocals 属性本质上是个Map。换句话说,Map<ThreadLocal, V> 是在每个Thread中定义的,ThreadLocal中并没有此Map、 具体可以通过查看TheadLocal源代码可以得知。 所以TheadLocal没必要定义多份,多个线程共用一个TheadLocal对象即可!
源代码分析
假设有一个ThreadLocal对象名叫做 threadLocal
现在绑定线程t1,设置值为v1, 即: 假设t1线程执行了thredLocal.set(v1),注意,threadLocal对象中并没有一个map属性来存储<t1, v1>, thredLocal.set(v1)实际是把v1设置到了t1线程对象的threadLocals属性中,该属性是java.lang.ThreadLocal.ThreadLocalMap类型,本质就是个HashMap,t1线程对象的threadLocals属性的key也不是t1线程对象,而是threadLocal对象, 还有:threadLocals本质是个HashMap这说明了t1线程可以绑定到多个不同的ThreadLocal对象上
initialValue
解决ThreadLocal.get()第一次返回值为null的问题
/** * <pre> * 解决ThreadLocal.get()第一次返回值为null的问题 * 但是感觉没什么用,还不如 * * if (null == ThreadLocal.get()) { * ThreadLocal.set * } * * </pre> * auther haobin <br /> * date 2019-5-14 **/ public class ThreadLocalExt { // new ThreadLocal() 可以用 ThreadLocal.withInitial 替代 // 所以IDE 报错:Anonymous new ThreadLocal() can be replaced with ThreadLocal.withInitial() public static ThreadLocal<String> tl = new ThreadLocal<String>() { // 如果把6-8行注释掉了,则16-17行都输出 “我的值"” protected String initialValue() { //6 return "aaa"; } //8 }; // new ThreadLocal() 可以用 ThreadLocal.withInitial 替代 // public static ThreadLocal tl = ThreadLocal.withInitial(()->"aa"); public static void main(String[] args) throws Exception { if (tl.get() == null) { tl.set("bbb"); } System.out.println(tl.get()); //16 Thread.sleep(1000); System.out.println(tl.get()); //17 } }
InheritableThreadLocal
InheritableThreadLocal类可以让子线程自动接收所有可继承的父线程局部变量的初始值 假设A线程中创建了B线程,则A相对于B则是父线程,B相对于A则是子线程
在使用InheritableThreadLocal类需要注意一点的是,如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程取到的值还是旧值。
public class ThreadLocalTest001 { public static ThreadLocal<String> threadLocal = new ThreadLocal<>(); // 11 // public static ThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); // 12 public static void main(String[] args) throws Exception { threadLocal.set("main"); Thread t1 = new Thread(() -> // 11行生效,子线程此处无法取出父线程的值,输出null, 12行生效,子线程此处可以取出父线程的值,输出main System.out.println(threadLocal.get()) ); t1.start(); t1.join(); System.out.println(threadLocal.get()); } }
使用场景
ThreadLocalRandom
Random
java.util.Random 是根据seed来生成随机数的, 生成一个随机数的具体过程是:
-
先根据老的seed生成一个新的seed, 注意: seed变量只有一个
-
根据新的seed再生成新的随机数。
-
生成随机数的算法是固定的,seed的值则是在不断变化的。
所以如果在一开始构造Random对象时设置了一个seed,则调用多次该对象生成的随机数都是伪随机数,这些值都是可以预估出来的。即如果多个Random对象具有一样的初始seed,则他们生成的随机数也必然是一模一样的。
构造Random
构造Random时可以指定seed也可以不指定seed,如果不指定seed的话,则默认是以当前时间(不准确,源代码是:seedUniquifier() ^ System.nanoTime())为seed的。
public Random() //如果不指定seed的话,则默认是以当前时间(描述不准确,源代码是:seedUniquifier() ^ System.nanoTime())为seed的 public Random(long seed);
nextInt()
// 产生一个伪随机数,从运行结果来看,数字长度无序,也可以能是负数 public int nextInt(); // 产生一个范围是[0, bound) 的伪随机数 public int nextInt(int bound);
示例1
@Test public void f2() { Random r1 = new Random(); for (int i = 0; i < 5; ++i) { System.out.println(r1.nextInt(5)); } System.out.println("\n\n"); for (int i = 0; i < 5; ++i) { System.out.println(r1.nextInt(5)); } }
seed一样,产生结果必然一样
如果多个Random对象具有一样的初始seed,则他们生成的随机数也必然是一模一样的
示例1
@Test public void f1(){ // r1和r1生成的随机数都是一模一样的,因为r1和r2的初始化seed都是一模一样的 Random r1 = new Random(10); Random r2 = new Random(10); for (int i=0; i<5; ++i) { // 查看源代码得知,先根据旧的seed值根据某个算法得到一个新的seed,然后再根据新的seed去生成一个随机数 System.out.println(r1.nextInt()); } System.out.println("\n\n"); // 第二次和第一次输出结果是一模一样的 for (int i=0; i<5; ++i) { System.out.println(r2.nextInt()); } }
多线程下的局限性
多线程下共用一个Random是没有安全问题的,但是因为种子seed的更新是一个原子操作,这会导致所有的线程在生成随机数时,更新seed的操作都会变成串行操作,严重降低了性能,所以出现了TheadLocalRandom
ThreadLocalRandom
概述
因为多线程下共用一个Random会导致性能严重下降,所以java中又出现了ThreadLocalRandom,使用方式是:
ThreadLocalRandom random = ThreadLocalRandom.current(); random.nextInt(); random.nextInt(int bound)
和多个线程共用一个ThreadLocal对象类似,多个线程也是共用一个ThreadLocalRandom对象的,
只不过“多个线程共用一个ThreadLocal对象”一般写成
public static ThreadLocal tl = new ThreadLocal();
多个线程共用一个ThreadLocalRandom对象 一般写成
// ThreadLocalRandom.current()返回的是一个static对象, 该对象源代码定义是: // static final ThreadLocalRandom instance = new ThreadLocalRandom(); ThreadLocalRandom random = ThreadLocalRandom.current();
Random 与 ThreadLocalRandom异同
区别: seed存储位置不同
Random中本身有一个属性seed属性,该属性定义是
// Random中的属性,因为seed的更新是原子操作,所以这里使用了AtomicLong private final AtomicLong seed;
但是TheadLocalRandom却是把seed存储在了Thread中,具体说是存储在了Thead的 threadLocalRandomSeed属性中,该属性的定义是
// Thread中的属性,供ThreadLocalRandom使用,因为不同的线程各自有自己的seed,不存在线程竞争的问题,所以这里的seed只是一个普通的long类型 long threadLocalRandomSeed;
相同点
ThreadLocalRandom和Random一样,每生成一个随机数时,都是先根据旧的seed生成新的seed,然后利用新的seed生成一个随机数,。
另外要注意:ThreadLocalRandom是通过UNSAFE的putLong方法来把新的seed写入当前线程的Thread对象的threadLocalRandomSeed属性的
// ThreadLocalRandom的生成新seed的源代码: final long nextSeed() { Thread t; long r; // read and update per-thread seed U.putLong(t = Thread.currentThread(), SEED, r = U.getLong(t, SEED) + GAMMA); return r; }
死锁
Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程技术中,“死锁”是必须避免的,因为这会造成线程的“假死”。
volatile
概述
郝斌: volatile似乎只能保证多线程下的可见性(一个线程改变了值,另一个线程会立即看到这个最新改变的值)和有序性(禁止指令的重排序优化, 郝斌:jdk1.5之后volatile才支持禁止指令重排的), 原子性是无法保证的,比如i++不是一个原子操作,使用volatile并无法保证在执行i++期间线程不会切换到其他线程,即volitile并不能代替锁,它当然也不能代替synchronized
关键字volatile的主要作用是使变量在多个线程间可见,且保证禁止指令重排。
关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
volatile无法保证原子性:
在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作,当变量的值由自身的上一个决定时,如n=n+1、n++ 等,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。
。。。。。。也就是说如下的表达式都不是原子操作:“count++”、“count = count+1”。
代码:
/** * <pre> * volitile无法保证原子性 ,无法代替synchronzied 示例 * Auther: haobin * Date: 2019-5-13 * </pre> */ public class PlusTask implements Runnable{ // 这里无论是否添加volatile 都无法让输出结果变为10000 public volatile static int i; @Override public void run() { for (int k=0; k<10000; ++k) { // // ++i 被this锁住,输出10000 // synchronized (this) { // ++i; // } // ++i 不上锁,输出值都会小于10000, 我的结果基本在3500-7000之间 ++i; } } public static void main(String[] args) throws Exception{ PlusTask plusTask = new PlusTask(); Thread[] threads = new Thread[10]; for (int i=0; i<10; ++i) { threads[i] = new Thread(plusTask); threads[i].start(); } // 主线程要等待threads[10] 全部执行结束,自己才能结束 for (int i=0; i<10; ++i) { threads[i].join(); } System.out.println(PlusTask.i); } }
volatile与syncronized
volatile是轻量级同步机制
-
volatile是一种稍弱的同步机制,在访问volatile变量时不会执行加锁操作,也就不会执行线程阻塞,因此volatilei变量是一种比synchronized关键字更轻量级的同步机制。
-
不过jdK1.5之后,对synchronized同步机制做了很多优化,如:自适应的自旋锁、锁粗化、锁消除、轻量级锁等,使得它的性能明显有了很大的提升
当要访问的变量已在synchronized代码块中,或者为常量时,没必要使用volatile。
由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
volatile仅能使用在变量级别,synchronized则可以使用在变量,方法上
volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化
代码
volatile保证了多线程下的可见性 示例
/** * <pre> * volatile保证了多线程下的可见性 * Auther: haobin * Date: 2019-5-13 * </pre> */ public class NoVisibility { // 加volatile 输出42 程序正常结束,不加volatile 则陷入死循环 // 即添加了volatile,主线程修改了ready属性值,其他线程才能看到,否则看不到 // 2019-07-31 haobin: 很奇怪,我在deepin 系统 jdk1.8 1.9 上无论加不加volatile,都能达到终止循环的效果,即:主线程修改了ready属性值,其他线程都会立即看到,原因不得而知。。。。。??? private volatile static boolean ready = false; private static int number; private static class ReaderThread extends Thread { public void run() { while (!ready) ; System.out.println(number); } } public static void main(String[] args) throws Exception { new ReaderThread().start(); Thread.sleep(2000); number = 42; ready = true; } }
jdk举例
CopyOnWriteArrayList#array
java.util.concurrent.CopyOnWriteArrayList#array 属性本身就是 volatile 类型
private transient volatile Object[] array; final void setArray(Object[] a) { array = a; } public boolean add(E e) { synchronized (lock) { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } }
Lock
概述
Lock先unlock的话会抛出异常,Object先singal不会抛出异常, LockSupport 先unpark的话也不会抛出异常
Lock VS Synchronzied
-
lock比Synchronized能更精准的控制程序
-
lock比Synchronized对锁得控制更强
使用synchronized关键字将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。当然,这种方式简化了同步的管理,可是扩展性没有显示的锁获取和释放来的好。例如,针对一个场景,手把手进行锁获取和释放,先获得锁A,然后再获取锁B,当锁B获得后,释放锁A同时获取锁C,当锁C获得后,再释放B同时获取锁D,以此类推。这种场景下,synchronized关键字就不那么容易实现了,而使用Lock却容易许多。
lock通过new Condition 可以实现多路复用,synchronized则从头到尾就只有一个锁
关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助于Condition对象。Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由JVM随机选择的。但使用ReentrantLock结合Condition类是可以实现前面介绍过的“选择性通知”,这个功能是非常重要的,而且在Condition类中是默认提供的。
synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,会出现相当大的效率问题。
Condition对象可以唤醒部分指定线程,有助于提升程序运行的效率。可以先对线程进行分组,然后再唤醒指定组中的线程。一个Lock new出多个Condition对象,相比较synchronzied能更精准控制线程行为示例:
-
-
ReentrantLock的性能要高于synchronized, 1.6以后性能两者差异不太大了
-
Lock.lockInterruptibly()可以响应中断请求抛出异常,synchronized则无法抛出异常
注意:Lock.lock()无法响应 interrupt中断,Lock.lockInterruptibly()则可以响应interrupt()中断,Lock.lockInterruptibly()可以响应中断请求(即t1线程在执行Lock.lockInterruptibly()期间如果其他线程执行了t1线程的interrupt()方法,则t1线程会中断程序抛出异常), 但是synchronized则没有此功能(只有synchronized内部代码块中的1. wait() 2、sleep() 3、join(), 4. Condition.await(), 5. Lock.lockInterruptibly() 这五种才能响应中断抛出异常,synchronized本身无此功能)
-
synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可中断、可公平也可非公平
-
并发量大,就是用Lock,并发量小,可以考虑使用Synchronized. 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
lock()
public void ReentrantLock.lock()
如果当前线程尚未获取到锁但是能获取到锁(因为可以多次调用lock()方法), 就获取到锁,设置当前线程锁持有量是1,并立即返回 如果当前线程已经获取到锁了,也立即返回, 此时线程持有锁的数量会加1 如果无法获取到锁,则陷入休眠状态
ReentrantLock每调用一次lock()方法,则持有的锁数量加1,ReentrantLock每调用一次unlock()方法,则持有的锁数量减1
Lock.lock()无法响应 interrupt中断,Lock.lockInterruptibly()则可以响应interrupt()中断
Acquires the lock. Acquires the lock if it is not held by another thread and returns immediately, setting the lock hold count to one. If the current thread already holds the lock then the hold count is incremented by one and the method returns immediately. If the lock is held by another thread then the current thread becomes disabled for thread scheduling purposes and lies dormant until the lock has been acquired, at which time the lock hold count is set to one
tryLock()
public boolean ReentrantLock.tryLock()
能获取到锁就立即获取该锁并返回ture 无法获取到也会立即返回,只不过返回值将会是false
tryLock(long timeout, TimeUnit unit
public boolean ReentrantLock.tryLock(long timeout, TimeUnit unit)
能获取到锁就立即获取该锁并返回ture 无法获取到则等待timeout时间,如果能获取则返回,如果依然无法获取true,则返回false
tyrLock()和lock()的区别
1. tryLock() 返回值是boolean lock() 返回值是 void
2. tryLock() 不会陷入休眠,能获取到锁立即返回true,获取到不到锁则立即返回false lock() 能获取到锁立即返回void, 获取到不到锁则陷入阻塞状态
参见代码:
com.haobin.multiplythread.locks.trylock.pack001.TestTryLock001
lockInterruptibly
ReentrantLock.lockInterruptibly() 如果能获取到锁就立即获取锁 如果无法获取到锁,则陷入休眠状态
lockInterruptibly 与 interrupt 的组合使用
如果aa对应的线程:处于1. object.wait() 2、Thread.sleep() 3、threadObject.join(), 4. Condition.await(), 5. Lock.lockInterruptibly() 这五种状态的任何一种状态,aa线程就会立即抛出InterruptEdException异常,然后中断标志位就会设置为false,之所以抛出异常后中断标志位会被重置为false,是因为interrupt()方法既然已经抛出了异常,这已经达到中断线程的目的了,即:已经响应了中断操作,所以jdk的编写者认为此时就应该把中断标志位重置为false
上级发现:假设A、 B、 C、 D依次通过调用lock.lockInterruptibly ()获取锁,且依次获取锁都失败,则我们可以通过interrupt()随时中断其中任何的一个线程。这个也是合乎逻辑的。
ReentrantLock.lockInterruptibly()执行时,即便其他线程对本线程执行了interrupt(),当前线程也不存在所谓释放锁的问题,因为
1: 当前线程如果能获取到lock锁,则当前线程会立即往下程序, 此时其他线程再调用本线程的interrupt() 已经太晚了, 将无法达到终止本线程的效果,本线程会继续往下执行。 即:当前线程不会释放lock锁 2: 当前线程如果无法获取到lock锁,当前线程将会陷入休眠状态 此时其他线程调用本线程的interrupt()会立即抛出异常, 当前线程本来就没获取到lock锁,又抛出了异常,自然不存在释放lock锁的说法
try { lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); lock.unlock(); // 这样写是错误的,因为 获取线程失败被中断抛出了异常,锁肯定是没有获取到的,就不存在释放锁的问题,所以这里运行会抛出异常 java.lang.IllegalMonitorStateException }
可参见代码: ccom.haobin.multiplythread.interrupt.lock.interrupt.pack002.TestLockInterruptibly002
1 先执行interrupt
如果执行lockInterruptibly()前,其他线程已经对本线程执行了interrupt, 则本线程在执行到lockInterruptibly()时不再尝试着获取锁,而是立即抛出异常! 参见
com.haobin.multiplythread.locks.lockInterruptibly.pack001.TestLockInterruptibly001
2 先执行lockInterruptibly
当前线程如果能获取到lock锁,则当前线程会立即往下程序, 后面其他线程再调用本线程的interrupt() 已经太晚了, 将无法达到终止本线程的效果,本线程会继续往下执行知道结束。
即:当前线程不会释放lock锁
参加 com.haobin.multiplythread.interrupt.lock.interrupt.pack002.TestLockInterruptibly002
官网API:
Acquires the lock unless the current thread is interrupted. Acquires the lock if it is not held by another thread and returns immediately, setting the lock hold count to one. If the current thread already holds this lock then the hold count is incremented by one and the method returns immediately. If the lock is held by another thread then the current thread becomes disabled for thread scheduling purposes and lies dormant until one of two things happens: The lock is acquired by the current thread; or Some other thread interrupts the current thread. f the lock is acquired by the current thread then the lock hold count is set to one. If the current thread: has its interrupted status set on entry to this method; or is interrupted while acquiring the lock,
lock与lockInterruptibly区别
Lock.lockInterruptibly()会因为interrupt()而取消阻塞,这时不用获取锁,而会抛出一个InterruptedException。
从JDK 1.8 源代码级别来看:Lock.lock()也会因为interrupt()而取消阻塞,但是它会继续尝试获取锁,通常会获取锁失败(因为线程是由于中断被唤醒而不是因为锁释放被唤醒),这会导致线程会继续陷入阻塞。 从外部运行来看,其结果就是 lock.lock()无法响应中断。 但是要注意,当前线程在最后获取到锁时,它会把当前线程设置为interrupted状态, 即执行Thread.currentThread().isInterrupted()将会返回true。
简单说:Lock.lock()无法响应 interrupt中断,但是会在获取到锁时设置线程状态为interrupted状态,Lock.lockInterruptibly()则会响应interrupt()中断,线程会取消中断,释放锁,抛出InterruptedException异常。
public static void test1() throws Exception{ ReentrantLock lock = new ReentrantLock(true); // 第一次 可以直接获取到锁 lock.lock(); // 3 // lock.lockInterruptibly(); System.out.println("1111"); Thread t1 = new Thread(() -> { System.out.println("t1 线程开始"); lock.lock(); // 9 第一个获取锁失败的线程 System.out.println("t1 线程获取到锁"); lock.unlock(); // 26 行 }, "thread_haobin_001"); t1.start(); Thread.sleep(2000); Thread t2 = new Thread(() -> { System.out.println("t2 线程开始"); System.out.println("1 " + Thread.currentThread().isInterrupted()); System.out.println("3 " + Thread.interrupted()); lock.lock(); // 15 第二个获取锁失败的线程 System.out.println("t2 线程获取到锁"); System.out.println("4 " + Thread.currentThread().isInterrupted()); System.out.println("5 " + Thread.interrupted()); System.out.println("6 " + Thread.interrupted()); System.out.println("7 " + Thread.currentThread().isInterrupted()); lock.unlock(); }, "thread_haobin_002"); t2.start(); Thread.sleep(2000); System.out.println("main over!"); t2.interrupt();; // 如果把43行注释掉了,则t1 和 t2线程 都会陷入阻塞, 即便t2执行了中断操作, t2线程依然会陷入阻塞 // lock.unlock(); //43 }
Condition
概述
Condition对象的await() signal() signalAll() 也都必须的放在ReentrantLock对象的lock() unlock()代码块中,否则会报出异常:java.lang.IllegalMonitorStateException,原因不言而喻, 因为Condition 是lock new出来的
Object类中的wait()方法相当于Condition类中的await()方法。 Object类中的wait(longtimeout)方法相当于Condition类中的await(longtime,TimeUnitunit)方法。 Object类中的notify()方法相当于Condition类中的signal()方法。 Object类中的notifyAll()方法相当于Condition类中的signalAll()方法。
先await后singnal会陷入阻塞 先single()/singleAll() 后await,会导致await()线程陷入等待状态, 这个类似于notify/notifyAll()和wait()
JDK中使用到了 Condition的源代码
LinkedBlockingDeque
JDK中的 LinkedBlockingDeque.java有如下代码:
final ReentrantLock lock = new ReentrantLock(); private final Condition notEmpty = lock.newCondition(); private final Condition notFull = lock.newCondition();
DelayQueue
DelayQueue.java 有如下代码
public class DelayQueue<E extends Delayed> extends AbstractQueue<E> implements BlockingQueue<E> { private final Condition available = lock.newCondition(); public boolean offer(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { q.offer(e); if (q.peek() == e) { leader = null; available.signal(); } return true; } finally { lock.unlock(); } } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { E first = q.peek(); if (first == null) available.await(); else { long delay = first.getDelay(NANOSECONDS); if (delay <= 0L) return q.poll(); first = null; // don't retain ref while waiting if (leader != null) available.await(); else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { available.awaitNanos(delay); } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && q.peek() != null) available.signal(); lock.unlock(); } } }
一个Lock 可以new出多个Condition
Condition = Lock.newCondition()
一个Lock 可以new出多个Condition,并且这new 出的多个Condition是完全不同的对象, 但是ReentrantLock即便生成了多个读锁,实际也只是一个读锁, 即便生成了多个写锁,实际也只是一个写锁。
“一个Lock 可以new出多个Condition,并且这new 出的多个Condition是完全不同的对象” 示例:
ReentrantLock reentrantLock = new ReentrantLock(); Condition conditionA = reentrantLock.newCondition(); Condition conditionB = reentrantLock.newCondition(); Condition conditionC = reentrantLock.newCondition(); // 输出 false, false // 说明: 一个Lock 可以new出多个Condition,并且这new 出的多个Condition是完全不同的对象 System.out.println((conditionA==conditionB) + ", " + (conditionA==conditionC));
Condition 比 synchronized的优势
Condition 比 synchronized wait/notify 的优势
关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助于Condition对象。Condition类是在JDK5中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性地进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由JVM随机选择的。但使用ReentrantLock结合Condition类是可以实现前面介绍过的“选择性通知”,这个功能是非常重要的,而且在Condition类中是默认提供的。
synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,会出现相当大的效率问题。
Condition对象可以唤醒部分指定线程,有助于提升程序运行的效率。可以先对线程进行分组,然后再唤醒指定组中的线程。
一个Lock new出多个Condition对象,相比较synchronzied能更精准控制线程行为示例:
参见 示例
com.haobin.multiplythread.locks.condition.pack004.Run
await() 会释放锁lock
示例1
class A extends Thread { public void run(){ System.out.println(Thread.currentThread().getName() + " -------"); } } public class M { public static void main(String[] args) throws Exception { ReentrantLock reentrantLock = new ReentrantLock(); Thread t1 = new Thread( ()->{ try { Thread.sleep(3000); reentrantLock.lock(); System.out.println("*****"); // 27 } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); reentrantLock.lock(); reentrantLock.lock(); reentrantLock.lock(); System.out.println("--------"); Condition c1 = reentrantLock.newCondition(); // 因为这里会释放主线程绑定的锁 reentrantLock, 所以子线程 t1 27行会得到执行输出 ***** c1.await(); } }
示例2
class A extends Thread { public void run() { System.out.println(Thread.currentThread().getName() + " -------"); } } public class M { public static void main(String[] args) throws Exception { ReentrantLock reentrantLock = new ReentrantLock(); reentrantLock.lock(); reentrantLock.lock(); reentrantLock.lock(); System.out.println("111"); Condition c1 = reentrantLock.newCondition(); c1.await(); // 36 System.out.println("222"); // 不会输出 Thread t1 = new Thread(() -> { // 38 // 程序在36行虽然会释放锁,但是因为主线程已经挂起了陷入阻塞状态,所以会导致后36 行后面的代码都得不到执行, reentrantLock.lock(); System.out.println("333"); // 不会输出 }); // 43 t1.start(); } }
示例3
class A extends Thread { public void run() { System.out.println(Thread.currentThread().getName() + " -------"); } } public class M { public static void main(String[] args) throws Exception { ReentrantLock reentrantLock = new ReentrantLock(); reentrantLock.lock(); reentrantLock.lock(); reentrantLock.lock(); System.out.println("11111111"); Condition c1 = reentrantLock.newCondition(); System.out.println("2222222"); Thread t1 = new Thread(() -> { reentrantLock.lock(); // 本语句不会输出,因为主线程没有释放锁 System.out.println("33333"); }); // 43 t1.start(); } }
示例4
class A extends Thread { public void run() { System.out.println(Thread.currentThread().getName() + " -------"); } } public class M { public static void main(String[] args) throws Exception { ReentrantLock reentrantLock = new ReentrantLock(); reentrantLock.lock(); reentrantLock.lock(); reentrantLock.lock(); System.out.println("1111"); reentrantLock.unlock(); reentrantLock.unlock(); reentrantLock.unlock(); System.out.println("2222"); Thread t1 = new Thread(() -> { reentrantLock.lock(); // 本语句会输出 System.out.println("3333"); }); // 43 t1.start(); } }
先await后singnal会陷入阻塞
先single()/singleAll() 后await,会导致await()线程陷入等待状态, 这个类似于notify/notifyAll()和wait()
参见: com.haobin.multiplythread.locks.condition.pack002.TestConditionAwaitSingnal001
waitUninterruptibly
假设线程在调用 condition.await()后处于休眠状态,此时调用thread.interrupt()会抛出异常,然后中断标志位会被设置为false
假设线程在调用 condition.awaitUninterruptibly()后处于休眠状态,此时调用thread.interrupt()当前线程依然会继续陷入休眠状态,awaitUninterruptibly只能被singnal/singnalAll唤醒,然后当前线程的中断标志位会设置为true
比如: 如果当前线程在condition.awaitUninterruptibly()时陷入睡眠,此时其他线程对当前线程执行了interrupt,则当前线程依然会继续陷入睡眠中
即condition.await()可以相应interrupt中断,awaitUninterruptibly()则无法相应interrupt中断
参见代码:
com.haobin.multiplythread.locks.awatUninterruptibly.pack001.TestAwaitUninterruptibly001
awaitUntil
boolean awaitUntil(Date deadline) throws InterruptedException;
void await() throws InterruptedException;
这两个方法的区别是: awaitUntil到达指定的时间后将会结束等待,如果此时还无法获取锁,将返回fales,否则返回true; await则没有时间限制
代码参见
com.haobin.multiplythread.locks.awaitutil.pack001.TeatAwaitUtil001
ReentrantLock
ReentrantLock 是 接口 Lock的实现类
ReentrantLock 是 re + entrant + Lock 的缩写
ReentrantLock可以new Condition, Condition对象的await() signal() signalAll() 也都必须的放在ReentrantLock对象的lock() unlock()代码块中,否则会抛出异常: java.lang.IllegalMonitorStateException,原因不言而喻。因为Condition 是lock new出来的
可以多次调动lock和unlock
一个线程可以多次调用lock()方法,但是unlock()的次数必须的和lock()的次数一致。因为 每调用一次lock成功,则内部的state就会加1,unlock一次成功,state就会减一。
重入指的是可以多次调用Lock.lock()方法,不过要注意,调用lock()和调用unlock()的次数必须一致,否则如果释放锁的次数多那么会得到一个java.lang.llegalMonitorStateException异常,反之,如果释放锁的次数少了,那么相当于线程还持有这个锁,其他线程将无法获取到锁
lock.lock(); lock.lock(); //可以多次调用lock()方法 try { ........ ........ } finally { lock.unlock(); lock.unlock(); //unlock()的次数必须得和lock()的次数一致 }
公平锁与非公平锁
/** * <pre> * 如果公平,输出结果始终为两个线程交替的获得锁, * 如果是非公平,输出结果为一个线程占用锁很长时间,然后才会释放锁,另个线程才能执行。 * Auther: haobin * Date: 2019-5-16 * </pre> */ public class TestFairLock001 implements Runnable{ // true表示公平锁, 不写或写false,则是非公平锁 public static ReentrantLock fairLock = new ReentrantLock(); public void run() { while (true) { try { fairLock.lock(); System.out.println(Thread.currentThread().getName()+",获得锁!"); }finally { fairLock.unlock(); } } } public static void main(String[] args) { TestFairLock001 fairLock = new TestFairLock001(); Thread t1 = new Thread(fairLock, "线程1"); Thread t2 = new Thread(fairLock, "线程2"); t1.start(); t2.start(); } }
getHoldCount
public int ReentrantLock.getHoldCount()
作用是查询当前正在执行本语句的线程获取到锁ReentrantLock的次数
API: Queries the number of holds on this lock by the current thread
如果lock.lock()陷入阻塞,说明没有获取到锁,则调用getHoldCount()返回值是0
ReentrantLock.getQueueLength得到的是lock.lock()陷入阻塞的估计值 ReentrantLock.getWaitQueueLength(Condition condition) 得到的是condition.await()的估计值
补充
ReentrantLock每调用一次lock()方法,则持有的锁数量加1,getHoldCount()就会加一;ReentrantLock每调用一次unlock()方法,则持有的锁数量减1, getHoldCount()就会减一
参见代码: com.haobin.multiplythread.locks.pack004.TestGetHoldCount001
getQueueLength
public final int ReentrantLock.getQueueLength()
API:
Returns an estimate of the number of threads waiting to acquire this lock. The value is only an estimate because the number of threads may change dynamically while this method traverses internal data structures. This method is designed for use in monitoring system state, not for synchronization control
方法 int getQueueLength()的作用是返回正等待获取此锁的线程估计数,比如有10个线程在运行着,其中4个线程执行lock.lock()后都还没有释放锁,那么此时调用lock.getQueueLength()方法后返回值将会是4,说明有4个线程同时在等待lock的释放
调用lock.getQueueLength() 之前不需要先调用lock.lock()
ReentrantLock.getQueueLength得到的是lock.lock()陷入阻塞的估计值 ReentrantLock.getWaitQueueLength(Condition condition) 得到的是condition.await()的估计值
getWaitQueueLength
public int ReentrantLock.getWaitQueueLength(Condition condition)
此方法必须的有参数 Condition
java.util.concurrent.locks.ReentrantLock getWaitQueueLength(Condition condition)
Returns an estimate of the number of threads waiting on the given condition associated with this lock. Note that because timeouts and interrupts may occur at any time, the estimate serves only as an upper bound on the actual number of waiters. This method is designed for use in monitoring of the system state, not for synchronization control
方法int getWaitQueueLength(Conditioncondition)的作用是返回等待与此锁定相关的给定条件Condition的线程估计数,比如有5个线程,每个线程都执行了同一个condition对象的await()方法,则调用getWaitQueueLength(Conditioncondition)方法时返回的int值是5
调用lock.getWaitQueueLength(newCondition) 前必须的先调用lock.lock(),否则会抛出异常:IllegalMonitorStateException,即:释放锁之后是不能调用lock.getWaitQueueLength(newCondition) 方法的,否则会抛出异常:Exception in thread "main" java.lang.IllegalMonitorStateException
“getQueueLength()和getWaitQueueLength(Condition condition) 区别” 请参见 getQueueLength
ReentrantLock.getQueueLength得到的是lock.lock()陷入阻塞的估计值 ReentrantLock.getWaitQueueLength(Condition condition) 得到的是condition.await()的估计值
代码参见
com.haobin.multiplythread.locks.pack005.TestGetWaitQueueLength001
getQueueLength()和getWaitQueueLength(Condition condition) 区别
ReentrantLock.getQueueLength得到的是lock.lock()陷入阻塞的估计值 ReentrantLock.getWaitQueueLength(Condition condition) 得到的是condition.await()的估计值
注意:
Condition condition = lock.newCondition(); lock.lock(); condition.await(); /* 此时lock已经被释放了,所以调用lock.getQueueLength()方法返回值是0 但是调用 lock.getWaitQueueLength(condition) 返回值则是1 */
代码1
com.haobin.multiplythread.locks.pack005.TestGetWaitQueueLength001
代码2
com.haobin.multiplythread.locks.getQueueLength.pack002.TestHasQueuedThread002
hasQueuedThread(thread)、hasQueuedThreads()
1) 方法 boolean ReentrantLock.hasQueuedThread(thread)的作用是查询指定的线程是否正在等待获取此锁定。 API: Queries whether the given thread is waiting to acquire this lock
2) 方法 boolean ReentrantLock.hasQueuedThreads() 的作用是查询是否有线程正在等待获取此锁定。
API: Queries whether any threads are waiting to acquire this lock
代码:
com.haobin.multiplythread.locks.getQueueLength.pack001.TestHasQueuedThread
** isHeldByCurrentThread
public boolean isHeldByCurrentThread()
Queries if this lock is held by the current thread
public class TestIHeldByCurrentThread001 { public static void main(String[] args) { final Service service1 = new Service(true); Runnable runnable = new Runnable() { @Override public void run() { service1.serviceMethod(); } }; Thread thread = new Thread(runnable); thread.start(); } } class Service { private ReentrantLock lock; public Service(boolean isFair) { super(); lock = new ReentrantLock(isFair); } public void serviceMethod() { try { System.out.println(lock.isHeldByCurrentThread()); lock.lock(); System.out.println(lock.isHeldByCurrentThread()); } finally { lock.unlock(); } } }
isLocked
public boolean ReentrantLock.isLocked()
API: Queries if this lock is held by any thread.
public class TestIsLock001 { public static void main(String[] args) throws InterruptedException { final Service service1 = new Service(true); Runnable runnable = new Runnable() { @Override public void run() { service1.serviceMethod(); } }; Thread thread = new Thread(runnable); thread.start(); } } class Service { private ReentrantLock lock; public Service(boolean isFair) { super(); lock = new ReentrantLock(isFair); } public void serviceMethod() { try { System.out.println(lock.isLocked()); lock.lock(); System.out.println(lock.isLocked()); } finally { lock.unlock(); } } }
ReentrantReadWriteLock
概述
ReentrantLock 实现了Lock接口,但是 ReentrantReadWriteLock 并没有实现Lock接口,ReentrantReadWriteLock 实现了 ReadWriteLock接口, ReadWriteLock 接口也并没有继承 Lock接口,ReadWriteLock和Lock不是继承关系,而是ReadWriteLock可以生成Lock
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }
ReentrantLock 执行效率太低,所有的线程都在竞争一个锁(哪怕所有的线程都只是读,也会串行执行)
ReentrantReadWriteLock 执行效率高,因为ReentrantReadWriteLock 可以生成且最多只能生成2个锁:读锁 和 写锁。
ReentrantLock 效率低下 举例:
假设有10个线程读数据,使用ReentrantLock,则这10个线程就只能串行化执行。
参见com.haobin.multiplythread.locks.reentrantlock.pack002.ReadWriteLockDemoHb002
假设有10个线程读数据,使用ReentrantLock,则这10个线程就只能串行化执行,但是使用ReentrantReadWriteLock的话,生成的读锁则可以让这10个线程并行执行。当然这样比较其实没有实际意义的,因为如果只有读线程的话,就没必要加锁了。不过有时候可能会出现既有写又有读,则这时候就能体现出ReentrantReadWriteLock的性能优势了。比如有2个写线程,20个读线程,读写线程互斥,但是一旦某个写线程写完间隙期间,或者2个写线程都写完时,其他的读线程则可以并发的执行了。
参见
com.haobin.multiplythread.locks.reentrantlock.pack001.ReadWriteLockDemoHb
读写锁的互斥
读写锁必须要保证互斥才可以,否则写一半就把数据读出来就会出现脏读的问题。
如果多个线程只有读锁,则不互斥
多个线程只要有一个写锁,就会互斥,即:写锁之间当然会互斥,读锁和写锁之间也会互斥。具体讲:
-
读锁和写锁是互斥的,A线程先获取到了读锁,则B线程将无法获取写锁,反之A线程先获取到了写锁,则B线程将无法获取到读锁
-
另外要注意:如果一个程序有一个写线程,多个读线程,并且写线程先获取到了写锁,则其他所有的读锁线程将会陷入等待,一旦写锁被释放,则其他所有的读锁线程将会并行执行 参见 示例/chapter4/ReadWriteLockBegin3 示例/chapter4/ReadWriteLockBegin4
结:多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。
只能生成一个读锁和写锁
一个ReentrantReadWriteLock对象调用多次readLock()方法返回的是同一个ReadLock对象,调用多次writeLock()方法,返回的是同一个WriteLock对象。
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); ReadLock readLock1 = lock.readLock(); ReadLock readLock2 = lock.readLock(); ReadLock readLock3 = lock.readLock(); WriteLock writeLock1 = lock.writeLock(); WriteLock writeLock2 = lock.writeLock(); WriteLock writeLock3 = lock.writeLock(); System.out.println((readLock1==readLock2) + ", " + (readLock1==readLock3) ); System.out.println((writeLock1==writeLock2) + ", " + (writeLock1==writeLock3)); 输出结果是: true, true true, true
应用场景
比如读写配置文件,一般都是读多写少,但是读写操作肯定要互斥,使用ReentrantReadWreteLock肯定要比使用等待/通知 wait/notify, await/singnal要简单明了很多。
锁降级
多个线程读写数据,不用锁降级,常规的写数据做法有2两种
-
获取写锁,写数据,再读数据做处理,然后释放写锁。如果读数据做处理很耗时,此时性能就会很差, 因为在读数据做处理时,其他线程理论上讲是应该允许读数据才对的。
-
获取写锁,写数据,释放写锁,再读数据做处理 。 这样有可能会出现读写不一致的情况。因为如果释放了写锁再读数据,此时线程可能会切换到另外一个写锁线程写数据,导致当前线程读到的数据不再是当前线程刚刚写写入的数据,当前线程读到的数据变成了另外一个写线程写入的数据。
这时候就可以使用锁降级功能了,
锁降级目的:希望写完数据后当前线程和其他线程都能立即读到数据。
锁降级具体操作是:获取写锁写完数据后,再获取读锁,然后再释放写锁(基于性能考虑,应该尽量早的释放写锁),然后再读数据(注意:此时其他写线程将不会得到执行,但是读线程可以并行执行,即:此时线程已经降级到了“读状态”,但是和传统的读状态唯一的不同就是写线程不会得到执行),然后再释放读锁。
注意:ReentrantReadWriteLock获取写锁后还可以再获取读锁。这是jdk自动提供的功能,下面的话摘自网络(https://www.jianshu.com/p/0db885b4bfaf)
在Java中ReadWriteLock的主要实现为ReentrantReadWriteLock,其提供了以下特性:
-
公平性选择:支持公平与非公平(默认)的锁获取方式,吞吐量非公平优先于公平。
-
可重入:读线程获取读锁之后可以再次获取读锁,写线程获取写锁之后可以再次获取写锁
-
可降级:写线程获取写锁之后,其还可以再次获取读锁,然后释放掉写锁,那么此时该线程是读锁状态,也就是降级操作。
锁降级的一个jdk自带示例,在ReentrantReadWriteLock
的javadoc中有如下代码,其是锁降级的一个应用示例
public class CachedData { Object data; volatile boolean cacheValid; final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { // 获取读锁 rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock,不释放的话下面写锁会获取不成功,造成死锁 rwl.readLock().unlock(); // 获取写锁 rwl.writeLock().lock(); try { // 开始写数据 // Recheck state because another thread might have // acquired write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade(锁降级) by acquiring read lock before releasing write lock // 这里再次获取读锁,如果不获取那么当写锁释放后可能其他写线程再次获得写锁,导致下方`use(data)`时出现不一致的现象 // 这个操作就是降级 rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); // Unlock write, still hold read } } try { // 使用完后释放读锁 use(data); } finally { rwl.readLock().unlock(); } } }
参考代码
com.haobin.multiplythread.locks.lock_degrade.pack002.ReadWriteLockTest
LockSupport
概述:
代码: com.haobin.multiplythread.locksupport.pack001.LockSupport001
Lock先unlock的话会抛出异常,Object先notify或者先notify后wait都不会抛出异常只不过程序会陷入阻塞而已, LockSupport 无论是先park后unpark还是先unpark后park程序都不会陷入阻塞也都不会抛出异常,但是如果只park必然会陷入阻塞, Lock如果只是park的话是不会陷入阻塞的。
Lock.lock是针对锁的获取操作,第一次当然能获取到锁。
LockSupport.park() 是阻塞线程的操作,第一次当然能阻塞到线程。
Lock.lock()和LockSupport.park()是完全不同范畴的两个东西,没任何可比性。
LockSupport.park(); 第一次调用会陷入阻塞,Lock.lock()第一次调用不会陷入阻塞。
LockSupport类中的 park()方法,最终都调用了Unsafe.park()方法. LockSupport类中的 unpark()方法,最终都调用了Unsafe.unpark()方法.
ReentrantLock的lock()调用了LockSupport.park(), 最终都调用了Unsafe.park()方法. ReentrantLock的unlock()调用了LockSupport.unpark(), 最终都调用了Unsafe.unpark()方法.
*Unsafe类的park()和unpark()方法都是native方法*
LockSupport有三个重要方法:park() 、park(Object)、unpark(Thread) 这三个都是static方法,即:不需要new LockSupport对象,可以直接调用这三个方法 其中unpack()方法必须的带参数,park()方法可以带参数也可以不带参数.
park对标的是object.wait或者condition.await方法
unpark对标的是object.singal() object.singnalAll或者condition.singanl() 或者condition.singalAll()
由于park与unpark使用的是许可机制,许可最大为1,所以unpark与park操作不会累加,而且unpark可以在park之前执行,unpark先执行,后面park将不阻塞。
ReentrantLock每调用一次lock()方法,则持有的锁数量加1,ReentrantLock每调用一次unlock()方法,则持有的锁数量减1
unpark永远不会陷入阻塞的
unpark方法必须要指定一个线程
public static void LockSupport.unpark(Thread thread)
Makes available the permit for the given thread, if it was not already available. If the thread was blocked on park then it will unblock. Otherwise, its next call to park is guaranteed not to block. This operation is not guaranteed to have any effect at all if the given thread has not been started
park()
public static void LockSupport.park()
API: Disables the current thread for thread scheduling purposes unless the permit is available. If the permit is available then it is consumed and the call returns immediately; otherwise the current thread becomes disabled for thread scheduling purposes and lies dormant until one of three things happens: Some other thread invokes unpark with the current thread as the target; or Some other thread interrupts the current thread; or The call spuriously (that is, for no reason) returns.
调用了park函数后,会禁用当前线程,除非许可可用。在以下三种情况之一发生之前,当前线程都将处于休眠状态,即下列情况发生时,当前线程会获取许可,可以继续运行。
-
其他某个线程将当前线程作为目标调用 unpark。
-
其他某个线程中断当前线程。
-
该调用不合逻辑地(即毫无理由地)返回
park会响应中断
LockSupport.park()会响应中断,退出阻塞状态继续向下执行,将中断状态设置成true,但是不会抛出异常
比如:T1线程在执行park()陷入阻塞时,如果其他线程调用了T1线程的interrupt()方法,则T1线程会退出阻塞状态(即T1线程会继续执行后面的代码),但是不会抛出InterruptedException异常,且中断状态被设置成true,并且会释放许可,此时
1. Thread.currentThread().interrupted()第一次调用返回true,后续调用将返回false
2. Thread.currentThread().isInterrupted()会一直返回true ,注意:在interrupt()执行前一直返回false的
参见 com.haobin.multiplythread.locks.locksupport.pack001.LockSupport001
这说明LockSupport.park() 创作者的意图是:相应中断,但是具体处理逻辑交给开发者。 下面的这个五个方法:1. object.wait() 2、Thread.sleep() 3、threadObject.join(), 4. Condition.await(), 5. Lock.lockInterruptibly() 创作者的意图则是: 直接由编译器自己来处理中断请求: 抛出异常
park()与pakr(Object) 区别
LockSupport.park()和LockSupport.park(Object blocker)区别
LockSupport.park()和LockSupport.park(Object blocker)
功能似乎都是一样的,唯一的区别是后者利用blocker可以传递给开发人员更多的现场信息,可以查看到当前线程的阻塞对象,方便定位问题(摘自https://www.jianshu.com/p/ceb8870ef2c5 )
park(Object blocker)阻塞线程时添加附加信息到blocker,用来记录线程是被谁堵塞的,程序出现问题时候,通过线程监控分析工具可以找出问题所
许可默认是被占用的
LockSupport.park();
System.out.println("block.");
运行该代码,可以发现主线程一直处于阻塞状态。说明许可默认是被占用的,调用park()时获取不到许可,所以进入阻塞状态
注意,这点与Lock不同的, Lock.lock()第一次调用时是不会陷入阻塞的。
换个角度而言,park()方法的目的就是为了阻塞线程的,第一次调用park()当然要陷入阻塞。
Lock.lock是针对锁的获取操作,第一次当然能获取到锁,不会陷入阻塞
Lock是针对锁的,LockSupport是针对线程的操作,是完全不同范畴的两个东西,没任何可比性。
park与wait 相比的优势
郝斌: 我个人的理解,线程和锁是两个不同的概念,锁的粒度要大于线程,对线程操作不一定就牵扯到对锁的操作(单线程不牵扯到锁,多线程也不必然牵扯到锁,只有多线程必须互斥的操作同一个资源时才会牵扯到锁),但是对锁的操作必然会牵扯到对锁的操作。因为 我可以只是阻塞、唤醒线程。 但是锁则不通,如果获取锁失败则必然要阻塞线程。
所以从这个角度来讲,LockSupport是个单纯的针对线程的工具类,wait/notify虽然也是针对线程的操作,但是很不幸,它掺杂了锁的概念,或者说利用wait/notify想阻塞唤醒某个线程,都必须的先获取到一个用来当做锁的Object对象(语法上的要求就是:wait/notify必须的放在同步代码块中), 然后才可以操作。 wait/notify是个更古老的对线程操作方式,已不建议使用。 换句话说,LockSupport不需要锁就可以直接对线程操作,wait/notify需要借助锁才能对线程操作, 这里的操作指的是阻塞线程和唤醒线程,所以我们建议直接使用LockSupport来对线程操作,而不是使用wait/notify()。
1、 比wait/notify操作更简单,更符合人类的认知方式。LockSupport直接操作线程,而wait/notify则是需要一个Object对线程进行操作。
我们知道LockSupport阻塞和唤醒线程直接操作的就是线程,所以从语义上讲,它更符合常理,或者叫更符合语义。而Object的wait/notify它并不是直接对线程操作,它是被动的方法,它需要一个object来进行线程的挂起或唤醒,在调用对象的wait之前当前线程必须先获得该对象的监视器(synchronized),被唤醒之后需要重新获取到监视器才能继续执行。park和unpark不需要锁,它使用的是信号量许可机制。实际上现在很少能看到直接用wait/notify的代码了,因为它们本身的实现机制不一样,所以它们之间没有交集,也就是说LockSupport阻塞的线程,notify/notifyAll没法唤醒
2、 不依赖监视器(锁)。wait/notify需要在synchronized中进行调用,它首先需要获取到锁,才能进行下面的操作。而LockSupport则是直接进行调用,与锁没有任何关系。(synchronized与wait/notify配合使用,ReentrantLock与Condition配合使用。而Condition就是使用LockSupport实现等待与唤醒的)
park() 、park(Object)、unpark(Thread) 这三个方法可以放在同步代码中,也可以不放在同步代码块中,即:这三个方法和锁没有任何关系
比如下面代码也可以正确运行,输出结果为:OK
LockSupport.unpark(Thread.currentThread()); LockSupport.park(); System.out.println("OK");
LockSpport与Lock的区别
两个针对的操作对象不同, Lock针对的是锁操作,LockSupport针对的是线程操作。
Lock获取锁失败时要阻塞线程,阻塞线程调用的就是LockSupport.park()方法; Lock释放锁时调用的是LockSupport.unpark()方法。
更专业的说话是(摘自https://zhidao.baidu.com/question/1308095675498813619.html)
1、Java中的Lock是锁的接口,作用是提供锁特性,方法等操作行为的统一的描述。 2、Java中的LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport不用担心线程执行的顺序,unpark()可以写在park()之前,Lock的话,如果unlock()写在lock()之前,程序将会抛出异常
下面的是摘自网络的话,不成体系:
LockSupport面向的是线程,而Object和ReentrantLock.Condition都是典型的依赖一个对象实现锁机制,wait让线程阻塞前,必获取到同步锁。而LockSupport这个哥们比较牛逼,随时随地随便阻塞当前线程,你给它一个线程它就敢让那个线程阻塞。
LockSupport.park和unpark不需要在同步代码块中,wait和notify是需要的。 LockSupport的pork和unpark是针对线程的,而wait和notify是可以是任意对象。
LockSupport的许可机制
-
如果一个线程没有许可,那么它在调用park方法时就会被阻塞,直到有其它线程调用unpark方法给它发许可或者过时。
-
如果一个线程有许可的话,那么它在调用park方法时就会收回它那个许可,但是不会被阻塞(像是交出过路费就给你过路一样)。但是当它再次调用park方法时,因为许可已经被用掉了(没路费了),于是又成了第一种情况
-
许可默认是被占用的
-
一个线程一个时刻最多只能有一个许可,即使你多次调用unpark方法它也只能有一个许可
Thread t = Thread.currentThread(); LockSupport.unpark(t); LockSupport.unpark(t); LockSupport.park(); System.out.println("1"); // 会陷入阻塞,一个线程一个时刻最多只能有一个许可,即使你多次调用unpark方法它也只能有一个许可 LockSupport.park(); System.out.println("2");
先unpark后park 不会阻塞
线程先执行unpark,后执行park,则后执行的park()是不会陷入阻塞的。
目前在Java语言层面能实现阻塞唤醒的方式一共有4种:
-
suspend()与resume()组合、
-
Object.wait()与Objectn.notify()组合
-
Condition.await()与Condition.signal()组合
-
park与unpark组合。
其中:
1被废弃
2和3, 先执行notify/signal, 都会导致后执行的wait/await()陷入阻塞
4 则不存在这个问题,先执行unpark ,后执行park也不会陷入阻塞
更具体的讲:
其中suspend与resume因为存在无法解决的竟态问题而被Java废弃,同样,wait与notify也存在竟态条件,wait必须在notify之前执行,假如一个线程先执行notify再执行wait将可能导致一个线程永远阻塞(wait线程会陷入阻塞,并不会抛出异常,详细可以参见先notify()后wait()会导致wait线程陷入阻塞(注意:wait线程并不会抛出异常)),如此一来,必须要提出另外一种解决方案,就是park与unpark组合,它位于juc包下,应该也是因为当时编写juc时发现java现有方式无法解决问题而引入的新阻塞唤醒方式,由于park与unpark使用的是许可机制,许可最大为1,所以unpark与park操作不会累加,而且unpark可以在park之前执行,unpark先执行,后面park将不阻塞。
线程池
概述
线程池中一般都有几个活跃的线程,当需要使用线程时从池子中随便拿一个空闲线程使用,当完成工作时,不是关闭线程而是把这个线程退回到池中,方便后续使,创建线程变成了从线程池中获取空闲线程,关闭线程变成了向池中归还线程。
一个线程池包括以下四个基本组成部分:
-
线程池管理器:用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;
-
工作线程:线程池中的线程,工作线程是一个可以循环执行任务的线程,在没有任务时将等待
-
任务接口:每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;
-
任务队列:用于存放待处理的任务,提供一种缓冲机制
Leader-follower 线程模型
在Leader-follower线程模型中每个线程有三种模式,leader,follower, processing。
在Leader-follower线程模型一开始会创建一个线程池,并且会选取一个线程作为leader线程,leader线程负责监听网络请求,其它线程为follower处于waiting状态,当leader线程接受到一个请求后,会释放自己作为leader的权利,然后从follower线程中选择一个线程进行激活,然后激活的线程被选择为新的leader线程作为服务监听,然后老的leader则负责处理自己接受到的请求(现在老的leader线程状态变为了processing),处理完成后,状态从processing转换为follower。可知这种模式下接受请求和进行处理使用的是同一个线程,这避免了线程上下文切换和线程通讯数据拷贝。
线程池生命周期
线程池生命周期包括
-
RUNNING: 接收新的任务并处理队列中的任务,当创建线程池后,初始时,线程池处于RUNNING状态,此时线程池中的任务为0
-
SHUTDOWN: 如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕
-
STOP: 如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
-
TIDYING: 已经调用了 shutdown()或者shutdownNow()并且所有的任务都已经终止时,线程池会变为TIDYING状态。接着会执行terminated()函数。
-
TERMINATED: 执行完terminated()之后,就会由 TIDYING -> TERMINATED,线程池被设置为TERMINATED状态
转换成TIDYING状态的线程会运行terminated方法。执行完terminated()方法之后,所有等待在awaitTermination()就会返回。
线程池大小的设置
1、计算密集型: 顾名思义就是应用需要非常多的CPU计算资源,在多核CPU时代,我们要让每一个CPU核心都参与计算,将CPU的性能充分利用起来,这样才算是没有浪费服务器配置,如果在非常好的服务器配置上还运行着单线程程序那将是多么重大的浪费。对于计算密集型的应用,完全是靠CPU的核数来工作,所以为了让它的优势完全发挥出来,避免过多的线程上下文切换,比较理想方案是:
线程数 = CPU核数+1,也可以设置成CPU核数*2,但还要看JDK的版本以及CPU配置(服务器的CPU有超线程)。
一般设置CPU * 2即可。
2、IO密集型 我们现在做的开发大部分都是WEB应用,涉及到大量的网络传输,不仅如此,与数据库,与缓存间的交互也涉及到IO,一旦发生IO,线程就会处于等待状态,当IO结束,数据准备好后,线程才会继续执行。因此从这里可以发现,对于IO密集型的应用,我们可以多设置一些线程池中线程的数量,这样就能让在等待IO的这段时间内,线程可以去做其它事,提高并发处理效率。那么这个线程池的数据量是不是可以随便设置呢?当然不是的,请一定要记得,线程上下文切换是有代价的。目前总结了一套公式,对于IO密集型应用: 线程数 = CPU核心数/(1-阻塞系数) 这个阻塞系数一般为0.8~0.9之间,也可以取0.8或者0.9。 套用公式,对于双核CPU来说,它比较理想的线程数就是20,当然这都不是绝对的,需要根据实际情况以及实际业务来调整:final int poolSize = (int)(cpuCore/(1-0.9))
针对于阻塞系数,《Programming Concurrency on the JVM Mastering》即《Java 虚拟机并发编程》中有提到一句话:
对于阻塞系数,我们可以先试着猜测,抑或采用一些性能分析工具或java.lang.management API 来确定线程花在系统/IO操作上的时间与CPU密集任务所耗的时间比值。
优点
-
减小内存开销,加快执行速度 ,线程的创建和销毁的开销是巨大的,线程过多会造成内存溢出,释放线程也会给GC带来很大的压力。而通过线程池的重用大大减少了这些不必要的开销,当然既然少了这么多消费内存的开销,其线程执行速度也是突飞猛进的提升。 当任务到达时,任务可以不需要等到线程创建就能立即执行 在开发服务器端软件项目时,软件经常需要处理执行时间很短而数目却非常巨大的请求,如果为每一个请求创建一个新的线程,会导致性能上的瓶颈,因为线程对象的创建和销毁需要JVM频繁地进行处理,如果请求的执行时间很短,可能花在创建和销毁线程对象上的时间大于真正执行任务的时间,若这样,则系统性能大幅降低。
-
加强对线程的管理,线程池可以提供定时、定期、单线程、并发数控制、延时执行、等功能。比如通过ScheduledThreadPool线程池来执行S秒后,每隔N秒执行一次的任务
关系图
Executor 、ExecutorService、AbstractExecutorService、ThreadPoolExecutor、Executors
虽然接口ExecutorService添加了若干个方法的定义,但还是不能实例化,那么就要看一下它的唯一子实现类AbstractExecutorService,但AbstractExecutorService依然是抽象类,无法实例化, ThreadPoolExecutor 是AbstractExecutorService的子类,可以实例化,但ThreadPoolExecutor在使用上并不是那么方便,在实例化时需要传入很多个参数,还要考虑线程的并发数等与线程池运行效率有关的参数,所以官方建议使用Executors工厂类来创建线程池对像
Executor
Executor接口就只有一个方法:
public interface Executor { void execute(Runnable command); }
ExecutorService
ExecutorService是Executor的子接口,在内部添加了比较多的方法
Executors.newXXXXXThreadPool()返回的类型都是 ExecutorService
ExecutorService.submit()调用后,不等该方法执行结束,下面的代码就会立即执行
ExecutorService 接口中的方法包括:
关闭线程池
awaitTermination
awaitTermination 是用来检测线程池是否关闭的,本身并没有关闭线程池的功能。
boolean awaitTermination(long timeout, TimeUnit unit);
API: Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first
如果线程池已经调用过了shutdown()或者shutdownnow(),并且所有提交的任务都已经执行完毕,则本方法会立即返回true。
如果到了制定的时间任务还没有执行完毕 则返回false
如果当前线程被中断,也会返回false
haobin: 经测试, awaitTermination 是没有终止线程池的功能,换而言之,如果没有调用 shutdown()和shutdownnow(),只是调用了awaitTermination,则线程池是不会关闭的。
awaitTermination()是个同步阻塞方法,本方法不执行结束,后面的语句不会执行
shutdown()和shutdownNow()都不是阻塞方法,换而言之,这2个方法执行后不会等待线程池的关闭,而是立即会返回。
如果想判断线程池是否已经关闭,请使用awaitTermination
一般和shutdown方法配合使用,具体方式如下:
// 优雅关闭线程池,然后每隔1秒检测一次 判断线程池是否关闭 executorService.shutdown(); while (!executorService.awaitTermination(1, TimeUnit.SECONDS)) { System.out.println("线程池没有关闭"); ................... ................... }
附录:假设执行的是:awaitTermination(100, TimeUnit.SECONDS),如果线程池在5秒内就终止了,则此方法就只等待5秒就会返回结果true,如果线程池一直在运行,则此方法也最多等待100秒就会返回false
强制关闭线程池
// 优雅关闭线程池,然后每隔1秒检测一次 判断线程池是否关闭,超过10秒后如果还没有关闭,则强制关闭 executorService.shutdown(); int count = 0; while (!executorService.awaitTermination(1, TimeUnit.SECONDS)) { System.out.println("线程池没有关闭"); count++; // 循环检测超过10 次,则要取消任务,强制关闭线程池 if (count >= 10) { if (! fixedThreadPool.isTerminated()) { // 本代码对死循环没用, 但是 对 子线程的 Thread.currentThread().isInterrupted() 确实有效果的 fixedThreadPool.shutdownNow(); System.out.printf("强制关闭"); } } ................... ................... }
shutdownNow
List<Runnable> shutdownNow();
API: Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution. This method does not wait for actively executing tasks to terminate. Use awaitTermination to do that.
shutdown()和shutdownNow()都不是阻塞方法,换而言之,这2个方法执行后不会等待线程池的关闭,而是立即会返回。
shutdown和shutdownNow的区别有三点
-
shutdwon()会继续执行等待任务,也会继续执行正在执行的任务; shutdownNow()则会尝试终止正在执行的任务,不会执行等待任务。
-
shutdown()无返回值, shutdwonNow()返回List<Runnable>,即返回等待任务列表
-
shutdown()方法不会对线程执行中断操作,但是shutdownNow()却会对线程执行中断操作。这个可以参加 com.haobin.multiplythread.threadpool.shutdown.pack001.MultiplyTaskShutdownTest001
shutdown和shutdownNow()都不会终止循环的,如果希望终止循环,我觉得可以这样写
shutdown:
// shutdown: ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1); List<Future<Integer>> futureList = new ArrayList<>(3); for (long i=0; i<3; ++i) { Future future = fixedThreadPool.submit(new TaskDemo()); futureList.add(future); } fixedThreadPool.shutdown(); futureList.forEach(f -> f.cancel(true));
shutdownNow:
// shutdown: ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1); List<Future<Integer>> futureList = new ArrayList<>(3); for (long i=0; i<3; ++i) { Future future = fixedThreadPool.submit(new TaskDemo()); futureList.add(future); } fixedThreadPool.shutdownNow();
任务线程可以这样写
public class TaskDemo implements Callable<Long> { @Override public Long call() throws Exception { while(3 > 2 && !Thread.currentThread().isInterrupted() ) { ....... ....... } return 1L; } }
shutdown
void shutdown();
shutdown方法:平滑的关闭ExecutorService,当此方法被调用时,ExecutorService停止接收新的任务并且等待已经提交的任务(包含提交正在执行和提交未执行)执行完成。当所有提交任务执行完毕,线程池即被关闭。换而言之,执行shutdown()之后新提交的任务是不会执行的,但是正在执行的任务和执行shutdown()之前提交的等待执行的任务还是会继续执行的,原因不言而喻。
shutdown()和shutdownNow()都不是阻塞方法,换而言之,这2个方法执行后不会等待线程池的关闭,而是立即会返回。
isShutdown
boolean isShutdown();
当调用shutdown()或shutdownNow()方法后,无论提交的任务是否执行完毕,调用本方法都将会返回为true。 非阻塞方法
isTerminated
boolean isTerminated();
当调用shutdown()或者shutdownNow()方法后,并且所有提交的任务完成后才会返回为true;此方法返回true才说明线程池关闭!
非阻塞方法.
API 文档说明:
Returns true if all tasks have completed following shut down. Note that isTerminated is never true unless either shutdown or shutdownNow was called first.
判断线程池是否关闭
如何关闭一个线程池,并判断该线程池是否真正关闭
executorService.showdown(); while (!executorService.awaitTermination(1, TimeUnit.SECONDS)) { System.out.println("线程池没有关闭"); ........... ........... }
参见 com.haobin.multiplythread.threadpool.shutdow.pack001.TestShutdownThreadPool001
Future#cancel
boolean cancel(boolean mayInterruptIfRunning)
本方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
是如何中断一个线程的,内部原理不太清楚,但是我猜测是:
应该只是发一个中断信号,可以中断阻塞中的操作。
不过要注意,对于while(true) {..........} 这种占用CPU的非阻塞式操作,Future#cancel()是中断不掉的 ,也即线程依旧在跑,占用着线程池资源。
这个可以参见 com.haobin.multiplythread.threadpool.shutdown.pack001.MultiplyTaskShutdownTest001
注意两点
-
线程池资源有限,有些任务会submit()不进去,抛异常:java.util.concurrent.RejectedExecutionException
-
只要submit()成功的,无论是线程正在执行,或是在BlockingQueue中等待执行,future.cancel()操作均可中断掉线程。也即,与其真正执行并无关系,阻塞中或等待被调度执行中,都将被中断。
参见 com.haobin.multiplythread.threadpool.pack002.FutureTest
Executors
概述
很多 Executors.newXXXThreadExecutor()的内部很多都是调用了 ThreadPoolExecutor 类的构造方法
对于一般的应用,我们不用通过构造函数来创建线程池,而是用一些封装过的工具方法,这些方法设置了大多数参数的缺省值。只有对线程池的特性有更高的要求时,才直接使用构造函数。
Executors中的newFixedThreadPool、newFixedThreadPool、newCachedThreadPool全部是借助ThreadPoolExecutor来实现的
虽然接口ExecutorService添加了若干个方法的定义,但还是不能实例化,那么就要看一下它的唯一子实现类AbstractExecutorService,但AbstractExecutorService依然是抽象类,无法实例化, ThreadPoolExecutor是AbstractExecutorService的子类,可以实例化,但ThreadPoolExecutor在使用上并不是那么方便,在实例化时需要传入很多个参数,还要考虑线程的并发数等与线程池运行效率有关的参数,所以官方建议使用Executors工厂类来创建线程池对像
线程池与异常
线程池异常的处理
有关线程池异常的处理 可以参见 【多线程并发心得 【新版样式】.doc】
ExecutorService
submit和execute方法执行的任务如果抛出了异常,线程池都会回收当前线程然后执行其他任务。 它们唯一的区别是:submit只有在调用future.get()方法时才会打印异常信息,execute是立即抛出异常信息。
参见 com.haobin.multiplythread.threadpool.pack001.TestThreadPool001_002
线程池中的如果某个工作任务抛出了异常,则线程池会回收这个线程,然后会让这个线程继续执行其他的任务,比如线程池中有三个线程t1 t2 t3, t1执行某个任务失败,则t1会回收后继续执行其他的线程
ScheduledExecutorService
通过ScheduledExecutorService执行的周期任务,如果任务执行过程中抛出了异常,那么ScheduledExecutorService就会停止执行任务,即:线程池虽然没有关闭,但是却不会再周期地执行该任务了。所以你如果想保住任务都一直被周期执行,那么catch一切可能的异常。
ExecutorService.submit( ) 中写try ...catch...是没用的
代码
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
创建一个含有n个线程的线程池,如果程序要运行的线程个数少于n,则这n个线程立即直接运行,如果线程个数大于n,则超出n的线程将等待前面线程运行完毕后才能继续运行,即:程序中线程同时运行的个数无法大于n。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
另外发现,运行此方法后,程序会一直运行下去,不会停止,即便线程都结束了,程序依然会运行下去
参考com.haobin.multiplythread.executors.newfixedpool.pack001.TestExecutorsNewFixedPool001
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); } public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory)); }
使用newSingleThreadExecutor()方法可以创建单一线程池,单一线程池可以实现以队列的方式来执行任务
newCachedThreadPool
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); } public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
创建自动增加或减少容量的线程池
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小 使用Executors类的newCachedThreadPool()方法创建的是无界线程池,可以进行线程自动回收。所谓的无界线程池就是池中存放线程个数是理论上的Integer.MAX_VALUE最大值。
SynchronousQueue 与 newCachedThreadPool
SynchronousQueue
newScheduledThreadPool
创建一个大小无限的线程池。此方法的返回值是ScheduledExecutorService接口,ScheduledExecutorService接口中有定时延迟调度的方法
此线程池支持定时以及周期性执行任务的需求
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize); } public static ScheduledExecutorService newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); }
示例:
public class ScheduledExecutorTest implements Runnable { private String jobName = ""; public ScheduledExecutorTest(String jobName) { super(); this.jobName = jobName; } @Override public void run() { System.out.println("execute " + jobName); } public static void main(String[] args) { ScheduledExecutorService service = Executors.newScheduledThreadPool(10); long initialDelay1 = 1; long period1 = 1; // 从现在开始1秒钟之后,每隔1秒钟执行一次job1 service.scheduleAtFixedRate( new ScheduledExecutorTest("job1"), initialDelay1, period1, TimeUnit.SECONDS); long initialDelay2 = 1; long delay2 = 1; // 从现在开始2秒钟之后,每隔2秒钟执行一次job2 service.scheduleWithFixedDelay( new ScheduledExecutorTest("job2"), initialDelay2, delay2, TimeUnit.SECONDS); } }
ScheduledExecutorService
scheduleAtFixedRate 和 scheduleWithFixedDelay
scheduleAtFixedRate ,是以上一个任务开始的时间计时,period时间过去后,检测上一个任务是否执行完毕,如果上一个任务执行完毕,则当前任务立即执行,如果上一个任务没有执行完毕,则需要等上一个任务执行完毕后立即执行。
scheduleWithFixedDelay,是以上一个任务结束时开始计时,delay时间过去后,立即执行。
通过ScheduledExecutorService执行的周期任务,如果任务执行过程中抛出了异常,那么ScheduledExecutorService就会停止执行任务,且也不会再周期地执行该任务了。所以你如果想保住任务都一直被周期执行,那么catch一切可能的异常。
public ScheduledFuture<?> scheduleAtFixedRate( Runnable command, long initialDelay, long period, TimeUnit unit); public ScheduledFuture<?> scheduleWithFixedDelay( Runnable command, long initialDelay, long delay, TimeUnit unit);
submit execute比较
区别
-
接收的参数不同, execute只能接受Runnable, submit可以接受Runnable也可以接受Callable
// submit 也可以调用Runnable接口 Future future = executorService.submit(() -> { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } ); // future.get()会暂停五秒 future.get();
-
submit都是有返回值的,而execute则没有返回值。 用到返回值的例子,比如说应用中有很多个做validation的task,用户希望所有的task执行完,然后每个task告诉主程序执行结果,是成功还是失败,如果是失败,原因是什么。然后就可以把所有失败的原因综合起来发给调用者。
-
Callable接口允许抛出异常,但是Runnable则不允许抛出异常 这意味着submit既可以自己处理异常也可以把异常抛给调用者处理,而Runnable只能自己处理异常,无法把异常抛给调用者来处理 。
源代码
ExecutorService接口有如下方法:
<T> Future<T> submit(Callable<T> task); Future<?> submit(Runnable task); <T> Future<T> submit(Runnable task, T result); void execute(Runnable command);
Callable 和 Runnable 接口源代码
@FunctionalInterface public interface Callable<V> { V call() throws Exception; } @FunctionalInterface public interface Runnable { void run(); }
Callable API:
A task that returns a result and may throw an exception. Implementors define a single method with no arguments called call.The Callable interface is similar to Runnable, in that(因为) both are designed for classes whose instances are potentially executed by another thread. A Runnable, however, does not return a result and cannot throw a checked exception.The Executors class contains utility methods to convert from other common forms to Callable classes
ThreadPoolExecutor
概述
ThreadPoolExecutor是ExecutorService接口的实现类
很多 Executors.newXXXThreadExecutor()的内部很多都是调用了 ThreadPoolExecutor 类的构造方法。比如 Executors中的newFixedThreadPool、newFixedThreadPool、newCachedThreadPool全部是借助ThreadPoolExecutor来实现的。
对于一般的应用,我们不用通过构造函数来创建线程池,而是用一些封装过的工具方法,这些方法设置了大多数参数的缺省值。只有对线程池的特性有更高的要求时,才直接使用构造函数。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); } public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory); }
构造方法
public ThreadPoolExecutor( int corePoolSize, 1 int maximumPoolSize, 2 long keepAliveTime, 3 TimeUnit unit, 4 BlockingQueue<Runnable> workQueue, 5 RejectedExecutionHandler handler) 6
线程池按以下行为执行任务(摘自网络) 当线程数小于核心线程数时,创建线程。 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。 当线程数大于等于核心线程数,且任务队列已满 若线程数小于最大线程数,创建线程 若线程数等于最大线程数, 则执行拒绝策略(默认的是丢弃任务,抛出异常 )
corePoolSize
线程池维护的核心线程数。
池中所保存的线程数,包括空闲线程,也就是核心池的大小。·
为什么这里说核心线程数而不是最小线程数是因为在线程池被创建后,并不会直接创建corePoolSize个线程,而是等任务到来时临时创建。等按照需要创建了corePoolSize个线程之后,这些数量的线程即使闲置,也不会被线程池收回。这时即可以将这个值理解为线程池维护的最小线程数了。
核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新线程来处理任务,而不是直接交给现有的线程处理。核心线程只有在 allowCoreThreadTimeout 被设置为true时才会超时退出,默认情况下不会退出,allowCoreThreadTimeout 为ture时,最极端情况下线程数量可以为退化为0个。
补充: allowCoreThreadTimeout 是java.util.concurrent.ThreadPoolExecutor中的是一个私有属性
maximumPoolSize:
线程池中允许的最大线程数。
当线程数大于或等于核心线程时,会把任务压入任务队列,当任务队列已满时,会创建新的线程,当线程池中线程数量达到maxPoolSize时。会执行拒绝策略!
keepAliveTime:
当线程池中的线程数量大于corePoolSize,多出那部分数量的线程空闲keepAliveTime后会被收回
当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果 allowCoreThreadTimeout 设置为true,则所有线程均会退出直到线程数量为0。
unit
keepAliveTime的时间单位。可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:NANOSECONDS、MICROSECONDS、MILLISECONDS、SECONDS
BlockingQueue
当任务数量超过额定线程数时,将任务缓存在 BlockingQueue 之中。
BlockingQueue 只是一个接口,常用的实现类有 LinkedBlockingQueue 和 ArrayBlocking-Queue。LinkedBlockingQueue的好处在于没有大小限制,优点是队列容量非常大,所以执行execute()不会抛出异常,而线程池中运行的线程数也永远不会超过corePoolSize值,因为其他多余的线程被放入LinkedBlockingQueue队列中,keepAliveTime参数也就没有意义了。
5个实现类
BlockingQueue的五大实现
ArrayBlockingQueue
长度需要事先指定
基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长的数组,以便缓存队列中的数据对象,其内部没实现读写分离,也就意味着生产和消费者不能完全并行。长度是需要定义的,可以指定先进先出或者先进后出,因为长度是需要定义的,所以也叫有界队列,在很多场合非常适合使用。
从Executors 的源代码来看,貌似没有任何线程池是使用的这个队列
LinkedBlockingQueue
大小没有限制
LinkedBlockingQueue的好处在于没有大小限制,优点是队列容量非常大,所以执行execute()不会抛出异常,而线程池中运行的线程数也永远不会超过corePoolSize值,因为其他多余的线程被放入LinkedBlockingQueue队列中,keepAliveTime参数也就没有意义了。
基于链表的阻塞队列,同ArrayBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),LinkedBlockingQueue之所以能够高效地处理并发数据,是因为其内部实现采用分离锁(读写分离两个锁),从而实现生产者和消费者操作完全并行运行。
LinkedBlockingDeque的构造方法也可以指定大小,当超过了指定的大小就会报错!
public LinkedBlockingDeque(int capacity)
Executors.newFixedThreadPool()和Executors.newSingleThreadExecutor() 都是使用的LinkedBlockingDeque队列
SynchronousQueue
一种没有缓冲的队列,生产者产生的数据直接会被消费者获取并且立刻消费。
是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。队列本身不存储任何元素,可以认为SynchronousQueue是一个缓存值为1的阻塞队列,但是 isEmpty()方法永远返回是true,remainingCapacity() 方法永远返回是0,remove()和removeAll() 方法永远返回是false,iterator()方法永远返回空,peek()方法永远返回null。
SynchronousQueue 与 newCachedThreadPool
应用场景一:SynchronousQueue 与 newCachedThreadPool
ThreadPoolExecutor.newCachedThreadPool() 内部就是使用 SynchronousQueue 作为缓存队列的。
ThreadPoolExecutor在使用SynchronousQueue作为工作队列时,客户端代码向线程池提交任务时,而线程池中又没有空闲的线程能够从SynchronousQueue队列实例中取一个任务,那么相应的offer方法调用就会失败(即任务没有被存入工作队列)。此时,ThreadPoolExecutor会新建一个新的工作者线程用于对这个入队列失败的任务进行处理(假设此时线程池的大小还未达到其最大线程池大小)。
所以,使用SynchronousQueue作为工作队列,工作队列本身并不限制待执行的任务的数量。但此时需要限定线程池的最大大小为一个合理的有限值,而不是Integer.MAX_VALUE,否则可能导致线程池中的工作者线程的数量一直增加到系统资源所无法承受为止。
使用SynchronousQueue的目的就是保证“对于提交的任务,如果有空闲线程,则使用空闲线程来处理;否则新建一个线程来处理任务”
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
应用场景二:SynchronousQueue 可以作为直接立即交付的通信管道
SynchronousQueue 可以作为通信管道
一段写入数据后,另一端立马可以做出反应读取数据。
如果以洗盘子的比喻为例,那么这就相当于没有盘架,而是将洗好的盘子直接放入下一个空闲的烘干机中。这种实现队列的方式看似很奇怪,但由于可以直接交付工作,从而降低了将数据从生产者移动到消费者的延迟。(在传统的队列中,在一个工作单元可以交付之前,必须通过串行方式首先完成入列[Enqueue]或者出列[Dequeue]等操作。)
直接交付方式还会将更多关于任务状态的信息反馈给生产者。当交付被接受时,它就知道消费者已经得到了任务,而不是简单地把任务放入一个队列——这种区别就好比将文件直接交给同事,还是将文件放到她的邮箱中并希望她能尽快拿到文件。
示例代码:
public class SynchronousQueueExample { static class SynchronousQueueProducer implements Runnable { protected BlockingQueue<String> blockingQueue; final Random random = new Random(); public SynchronousQueueProducer(BlockingQueue<String> queue) { this.blockingQueue = queue; } @Override public void run() { while (true) { try { String data = UUID.randomUUID().toString(); System.out.println("Put: " + data); blockingQueue.put(data); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } static class SynchronousQueueConsumer implements Runnable { protected BlockingQueue<String> blockingQueue; public SynchronousQueueConsumer(BlockingQueue<String> queue) { this.blockingQueue = queue; } @Override public void run() { while (true) { try { String data = blockingQueue.take(); System.out.println(Thread.currentThread().getName() + " take(): " + data); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { final BlockingQueue<String> synchronousQueue = new SynchronousQueue<String>(); SynchronousQueueProducer queueProducer = new SynchronousQueueProducer( synchronousQueue); new Thread(queueProducer).start(); SynchronousQueueConsumer queueConsumer1 = new SynchronousQueueConsumer( synchronousQueue); new Thread(queueConsumer1).start(); SynchronousQueueConsumer queueConsumer2 = new SynchronousQueueConsumer( synchronousQueue); new Thread(queueConsumer2).start(); } }
我们发现: 插入数据的线程和获取数据的线程,交替执行
PriorityBlockingQueue
大小没有限制
基于优先级别的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定,也就是说传入队列的对象必须实现Comparable接口),在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。
从Executors 的源代码来看,貌似没有任何线程池是使用的这个队列
DelayQueue
大小没有限制
带有延迟时间的Queue,其中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue中的元素必须先实现Delayed接口,DelayQueue是一个没有大小限制的队列,应用场景很多,比如对缓存超时的数据进行移除、任务超时处理、空闲连接的关闭等等。
从Executors 的源代码来看,貌似没有任何线程池是使用的这个队列
拒绝策略
当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略。
线程池中,有三个重要的参数,决定影响了拒绝策略:
-
corePoolSize - 核心线程数,也即最小的线程数。
-
workQueue - 阻塞队列 。
-
maximumPoolSize - 最大线程数
当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,当线程数扩充到 maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池的拒绝策略了。
四种拒绝策略
拒绝策略提供顶级接口 RejectedExecutionHandler ,其中方法 rejectedExecution 即定制具体的拒绝策略的执行逻辑
jdk默认提供了四种拒绝策略:
AbortPolicy
abortPolicy - 丢弃任务,并抛出异常
线程池默认的拒绝策略 丢弃任务,抛出异常:RejectedExecutionException 。
这意味着如果我们的程序不处理抛出的异常的话,将会打断程序的执行,如果代码编写不当,会导致后续线程池的任务无法执行。
比如:
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 1 ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workQueue, handler); for(i=0; i<10000; i++) { // 如果没有try.catch处理异常信息的话,会中断调用者的处理流程,后续任务得不到执行(跑不完10000个)。 // 这个只是从语法的角度来看,后续循环无法执行,线程池本身依然还处于运行中的。 executor.execute(new Thread(() -> log.info(Thread.currentThread().getName() + " is running"))); }
DiscardPolicy
直接丢弃,其他啥都没有
DiscardOldestPolicy
替换最旧的任务
当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入
CallerRunsPolicy
重试
这个是来着不拒策略.如果有要执行的任务队列已满,此时若还有任务提交且线程池还没有停止,则直接运行任务的run方法
这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功或者线程池被关闭了才会取消执行。
Java手册对这个策略的描述是:
handler for rejected tasks that runs the rejected task directly in the calling thread of the execute method, unless the executor has been shut down, in which case the task is discarded.
源代码类
CountDownLatch
CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、ConcurrentHashMap和BlockingQueue,它们都存在于java.util。 concurrent包下。CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
CountDownLatch的伪代码如下所示:
-
Main thread start
-
Create CountDownLatch for N threads
-
Create and start N threads
-
Main thread wait on latch , 代码是: countDownLatch.await();
-
N threads completes there tasks are returns 代码是: countDownLatch.countDown();
-
Main thread resume execution
工作原理
public CountDownLatch(int count) ;
构造器中的计数值(count)实际上就是闭锁需要等待的线程数量。这个值只能被设置一次,而且CountDownLatch没有提供任何机制去重新设置这个计数值。
参考代码
geym.conc.ch3.synctrl.TestCountDownLatchDemo
public class TestCountDownLatchDemo implements Runnable { static final CountDownLatch countDownLatch = new CountDownLatch(10); static final TestCountDownLatchDemo demo = new TestCountDownLatchDemo(); @Override public void run() { try { //模拟检查任务 Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "check complete"); countDownLatch.countDown(); // 22行 Thread.sleep(3000); System.out.println(Thread.currentThread().getName() + " 线程结束!"); // 25 } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { exec.submit(demo); } //等待检查 只要其他线程执行完countDownLatch.countDown() 计数器为0,本方法机会立即返回,不用等到子线程结束 // 具体到这里就是:第十个线程执行完 22代码后,本方法会立即返回去执行43行的代码,而不用等到25行的结束,即43行会先于25行执行 countDownLatch.await(); //发射火箭 System.out.println("Fire!"); //43 exec.shutdown(); } }
CopyOnWriteArrayList
内部是个连续数组,更新、 删除、增加元素时都会复制数组成新的拷贝,然后对新的拷贝进行更新或删除操作,最后再把新的拷贝设置为当前类对象的有效数组。
更新、 删除、增加操作 都会加锁,读操作不会加锁,这会导致读操作读出的数据可能不是最新实时数据,即读操作是弱一致性的。
示例
/** * <pre> * 内部是个连续数组,更新、 删除、增加元素时都会复制数组成新的拷贝,然后对新的拷贝进行更新或删除操作, * 最后再把新的拷贝设置为当前类对象的有效数组。 * 更新、 删除、增加操作 都会加锁,读操作不会加锁,这会导致读操作读出的数据可能不是最新实时数据,即读操作是弱一致性的。 * Auther: haobin * Date: 2019/7/21 0021 * </pre> */ public class TestCopyOnWriteArrayList001 { public static void main(String[] args) throws Exception{ CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>(); copyOnWriteArrayList.add("one"); copyOnWriteArrayList.add("two"); copyOnWriteArrayList.add("three"); copyOnWriteArrayList.add("four"); copyOnWriteArrayList.add("five"); Thread t1 = new Thread(()->{ copyOnWriteArrayList.set(0, "first"); copyOnWriteArrayList.remove(2); }); Iterator<String> it = copyOnWriteArrayList.iterator(); t1.start(); t1.join(); // 这里读出的数据依然是 旧的数据 while (it.hasNext()){ System.out.println(it.next()); } System.out.println("**********************\n\n"); // 这里读出的是t1线程操作之后的最新数据 Iterator<String> it2 = copyOnWriteArrayList.iterator(); while (it2.hasNext()){ System.out.println(it2.next()); } } } 输出: one two three four five ********************** first two four five
CompletableFuture
概述:
好的网址:
https://colobu.com/2016/02/29/Java-CompletableFuture/
CompletionStage
CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段 一个阶段的计算执行可以是一个Function,Consumer或者Runnable。比如:stage.thenApply(x -> square(x)).thenAccept(x -> System.out.print(x)).thenRun(() -> System.out.println()) 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发
Runnable Consumer BiConsumer Function BiFunction
Runnable类型的参数会忽略计算的结果(无输入参数无返回值),
Consumer是纯消费计算结果(接收1个值不返回值),
BiConsumer会组合另外一个CompletionStage纯消费(接收2个值不返回值),
Function会对计算结果做转换(接收1个值返回另一个值),
BiFunction会组合另外一个CompletionStage的计算结果做转换(接收2个值返回另一个值)。
jdk 源代码如下:
public interface BiConsumer<T, U> { void accept(T t, U u); } public interface Consumer<T> { void accept(T t); } public interface BiFunction<T, U, R> { R apply(T t, U u); } public interface Runnable { public abstract void run(); } public interface Function<T, R> { R apply(T t); } public interface BiFunction<T, U, R> { R apply(T t, U u); }
CompletableFuture
CompletableFuture 实现了Futer和CompletionStage接口,因此它可能代表一个明确完成的Future,也有可能代表一个完成阶段( CompletionStage ),它支持在计算完成以后触发一些函数或执行某些动作。
Future可以代表在另外的线程中执行的一段异步代码,但是你也可以在本身线程中执行:
public static CompletableFuture<Integer> compute() { final CompletableFuture<Integer> future = new CompletableFuture<>(); return future; }
上面的代码中future
没有关联任何的Callback
、线程池、异步任务等,如果客户端调用future.get
就会一致傻等下去。你可以通过下面的代码完成一个计算,触发客户端的等待
f.complete(100);
当然你也可以抛出一个异常,而不是一个成功的计算结果:
f.completeExceptionally(new Exception());
创建
有五种方式,
第一种,new 不推荐
new CompletableFuture<>() 不推荐
//1 创建一个带result的CompletableFuture CompletableFuture<String> future = CompletableFuture.completedFuture("result"); future.get(); // 阻塞中。。。。。 //2 默认创建的CompletableFuture是没有result的,这时调用future.get()会一直阻塞下去知道有result或者出现异常 future = new CompletableFuture<>(); try { future.get(1, TimeUnit.SECONDS); } catch (Exception e) { // 。。。。。 } // 给future填充一个result future.complete("result"); assert "result".equals(future.get()); //3 给future填充一个异常 future = new CompletableFuture<>(); // 本方法并不会让当前执行该语句的线程终止,本方法只是终止了completableFuture.get()和join()的阻塞,抛出的异常会被completableFuture.get()和join()捕获到,CompletableFuture.complete()、CompletableFuture.completeExceptionally只能被调用一次 future.completeExceptionally(new RuntimeException("exception")); try { future.get(); } catch (Exception e) { assert "exception".equals(e.getCause().getMessage()); }
上面的示例是自己设置future的result,一般情况下我们都是让其他线程或者线程池来执行future这些异步任务。
后四种runAsync和supplyAsync
除了直接创建CompletableFuture 对象外(不推荐这样使用),还可以使用如下4个方法创建CompletableFuture 对象
这四个方法都是static方法
如果入参不带executor,则默认使用ForkJoinPool.commonPool()作为执行异步任务的线程池;否则使用executor执行任务。
// runAsync是Runnable任务,不带返回值的,如果入参有executor,则使用executor来执行异步任务, public static CompletableFuture<Void> runAsync(Runnable runnable) public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) // supplyAsync是带返回结果的异步任务 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
【各种方法的比较】:
thenApply 、thenAccept 、whenComplete、handle都是非static方法,都是在上一个CompletableFuture运算结束或者抛出异常后才可能执行的。
声明:
可以处理异常指的是上一个CompletableFuture抛出异常后,本方法可以捕获到异常执行
不可以处理异常指的是上一个CompletableFuture抛出异常后,本方法会跳过不会得到执行。
可以转换指的是: 本方法不但可以return,而且return的值的类型可以和上一个CompletableFuture运算结果的类型不一致。
不可以转化指的是: 本方法要么不可以return, 要么return的值的类型必须得和上一个CompletableFuture运算结果的类型一致
thenApply 可以转换运算结果,但是无法处理异常
thenAccept 接收上一个处理结果,但是自己无法返回任何处理结果(这意味着整个completableFuture.get()将不会有返回值了),适合只消费但不会把运算结果移交到下一个步骤的场合。
whenComplete() 可以处理异常,但是处理完异常后异常会继续抛出,
whenComplete() 不能写return,因为whenComplete()默认 会返回上一个CompletableFuture的处理结果给,这意味着whenComplete()无法转换输出结果
handle() 既可以处理异常,处理完之后异常不会再继续抛出。
handle()也可以转换输出结果
exceptionally()只有在抛出异常后才会执行,也必须得返回值,但是返回值的类型必须得和上一个CompletableFutuer运算结果的类型一致,否则编译会报错
handle 转换加处理异常
郝斌:handle既可以处理异常(即异常不会继续抛出),也可以接收上一个运算结果再次运算后返回另一种类型的运算结果
handle()近似于whenComplete()+转换
之所以说近似而不是等价于,是因为:
whenComplete()相比较hand(), whenComplete()可以处理异常,但是不具备转换处理结果的功能(无法添加return 语句),handle则既可以处理异常,也可以转换处理结果(可以return 不同于上一个CompletableFutuer的运算结果的数据类型)
whenComplete() 处理完异常之后异常会继续抛出,handle()处理完异常后异常不会再继续抛出。
即: whenComplete不能转换,处理异常后异常会继续抛出。handle()可以转换运算结果,处理异常后异常不会再继续抛出。
可以参见 whenComplete的示例
thenApply, handle可以通过return返回不同于上一个CompletableFutuer的运算结果, 而且不但可以返回值不一样,返回值的类型也可以不一样
thenApply, handle的区别是:当CompletableFuture运算出现异常时,handle可以捕获异常进行处理,thenApply则不会(即:当CompletableFuture运算出现异常时,thenApply将不会执行)。
/** thenApply, handle的区别是:当CompletableFuture运算出现异常时,handle可以捕获异常进行处理,thenApply则不会。 即当CompletableFuture运算出现异常时,thenApply将不会执行。 另外从本程序编译来看,貌似handle()不允许手动throw出异常 */ @Test public void testThenApplyHandle() throws Exception { CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getId() + " hello world"); // 抛出异常后, thenApply不会执行 程序会直接跳到20行执行 int i = 1 / 0; return "result"; }).thenApply((r1) -> { System.out.println(Thread.currentThread().getId() + " r1 = " + r1); return "aaa"; }).thenApply((r2) -> { System.out.println(Thread.currentThread().getId() + " r2 = " + r2); return "bbb"; }).handle((r, e)->{ //20行 如果不再继续抛出异常,则23行永远都不会得到执行 System.out.println("handle: r " + r + ", e= " + e); // if (null != e) { throw e; 编译报错 // 貌似handle()不允许手动throw出异常 // Student s = null; // s.setAge(11); // } return "Exception has be already handled completely"; }).exceptionally(e->{ // 23 System.out.println("exceptionally: " + e); return "asdas"; //exception的返回值必须的和上一个语句保持一致 }).get(); } /** 在JDK 1.8的运行结果是: 12 hello world handle: r null, e= java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero */
handle方法会处理正常计算值和异常,因此它可以屏蔽异常,避免异常继续抛出。
handle的形参是BiFunction类型,BiFunction的源代码如下:
public interface BiFunction<T, U, R> { R apply(T t, U u); }
handle的调用方式类似于:
completableFutuer.handle( (r, e) -> { 。。。。。。 。。。。。。 return XXX; // XXX可以是任意类型,即XXX的类型可以和r的类型不一样,即xxx的类型可以和上一个CompletableFutuer的运算结果的类型不一样 } );
handle的第一个参数r用来接收上一个CompletableFutuer的运算结果,第二个e参数用来接收异常信息。
这两个参数只能有一个会存在有效值,即上一个CompletableFutuer出现异常时,第二个参数有值。第二个参数为null,否则第一个参数有值,第二个人参数为null, return的返回值充当了第三参数。
如果上一个CompletableFutuer运算抛出了异常,本handle处理完之后异常不再继续抛出!
示例:
/** * thenApply, handle可以通过return返回不同于上一个CompletableFutuer的运算结果, * 而且不但可以返回值不一样,返回值的类型也可以不一样 * * exceptionally()可以有返回值,但是返回值的类型必须得和上一个CompletableFutuer运算结果的类型一致,否则编译会报错 * * handle()相当于whenComplete()+转换 * * handle的第一个参数r用来接收上一个CompletableFutuer的运算结果,第二个e参数用来接收异常信息。 * 这两个参数只能有一个会存在有效值,即上一个CompletableFutuer出现异常时,第二个参数有值。 * 第二个参数为null,否则第一个参数有值,第二个人参数为null, return的返回值充当了第三参数。 * 如果上一个CompletableFutuer运算抛出了异常,本handle处理完之后异常不再继续抛出! * @throws Exception */ @Test public void testHandle() throws Exception { String result = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getId() + " hello world"); int i = 1 / 0; return 1; }).handle((t, u) -> { System.out.println("t1 = " + t + ", u1 = " + u); // return ""; //ok return new Student("zhangsan", 11, 23.f); }).handle((t, u) -> { System.out.println("t2 = " + t + ", u2 = " + u); // return ""; //ok return "asd"; }) // 除非上一个handle抛出异常,否则本exceptionally永远都不会得到执行 .exceptionally(e -> { System.out.println("exceptionally: " + e.getMessage()); // 这里return return "ss"; // 如果18行-21行生效,则此语句将会编译报错,提示你“String cannot be converted to float” }).get(); System.out.println(Thread.currentThread().getId() + " f1()方法结束了 result = " + String.valueOf(result)); } /** 在JDK 1.8的运行结果是: 12 hello world t1 = null, u1 = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero t2 = Student{name='zhangsan', age=11, socre=23.0}, u2 = null 1 f1()方法结束了 result = asd */
thenApply 转换
如果CompletableFuture执行出现了异常,thenApply和thenAppept 都不会被执行
handle、thenApply和thenAppept都是对上一个CompletableFuture执行完的结果进行某些操作
thenApplyAsync默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。
thenApply相当于回调函数(callback)
thenAccept和thenRun都是无返回值的。如果说thenApply是不停的输入输出的进行生产,那么thenAccept和thenRun就是在进行消耗。它们是整个计算的最后两个阶段。
thenApply有返回值,thenAccept 和 thenRun 没有返回值
thenAccept接收上一阶段的输出作为本阶段的输入,thenRun没有输入参数
同样是执行指定的动作,同样是消耗,二者也有区别:
thenAccept接收上一阶段的输出作为本阶段的输入 thenRun根本不关心前一阶段的输出,根本不不关心前一阶段的计算结果,因为它不需要输入参数
handle方法会处理正常计算值和异常,因此它可以屏蔽异常,避免异常继续抛出。
thenApply方法只是用来处理正常值,因此一旦有异常就会抛出
thenApply
, handle
当计算完成的时候,会生成新的计算结果
whenComplete 当计算完成的时候,返回同样的计算结果
源代码:
public CompletableFuture<Void> thenRun(Runnable action) { return uniRunStage(null, action); } public CompletableFuture<Void> thenAccept(Consumer<? super T> action) { return uniAcceptStage(null, action); } public <U> CompletableFuture<U> thenApply( Function<? super T,? extends U> fn) { return uniApplyStage(null, fn); }
thenAppept 纯消费
只接受上一个处理的处理结果,但是自身不会返回任何运算结果
当计算完成的时候,thenApply,handle会生成新的计算结果() ,whenComplete返回同样的计
算结果
thenAccept 和 thenRun
相同点:
都无返回值
不同点:
thenAccept()接收上一个处理的处理结果,即有输入参数
thenRun()不接收上一个处理的处理结果,即没有有输入参数
CompletableFuture还提供了一种处理结果的方法,只接受上一个处理的结果,但是不会再返回结果,即:纯消费
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
这会直接导致
CompletableFuture.supplyAsync(() -> { 。。。。。。 。。。。。。 return "result"; }).thenAccept((r1) -> { 。。。。。。 }).get(); //此时的get无任何返回值
比如下面的写法编译就会报错:
@Test public void testThenAccept001() throws Exception { String result = CompletableFuture.supplyAsync(() -> { return "result"; }).thenAccept((r1) -> { System.out.println(Thread.currentThread().getId() + " r1 = " + r1); }).get(); //编译报错, get()方法此时无返回值 提示: Incompatible types. Found: 'java.lang.Void', required: 'java.lang.String' }
但是下面的程序却是正确的:
@Test public void testThenAccept002() throws Exception { Integer result = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getId() + " hello world"); return "result"; }).thenAccept((r1) -> { // r1的中为result System.out.println(Thread.currentThread().getId() + " r1 = " + r1); }).thenAccept((r2) -> { // r2为null 因为thenAccept()无返回值 System.out.println(Thread.currentThread().getId() + " r2 = " + r2); }).thenAccept((r3) -> { // r3为null 因为thenAccept()无返回值 System.out.println(Thread.currentThread().getId() + " r3 = " + r3); }).thenApply((r4) -> { //16行 r4为null 因为thenAccept()无返回值 System.out.println(Thread.currentThread().getId() + " r4 = " + r4); return 1; }).thenApply((r5) -> { //19行 r5为1 System.out.println(Thread.currentThread().getId() + " r5 = " + r5); return 2; }).get(); // 返回2, 如果把16-19行的这两个thenApply()注释掉,则编译出错,此时get方法时无返回值的 System.out.println(Thread.currentThread().getId() + " f1()方法结束了 result = " + String.valueOf(result)); } /* 在JDK 1.8的运行结果是: 12 hello world 12 r1 = result 12 r2 = null 12 r3 = null 12 r4 = null 12 r5 = 1 1 f1()方法结束了 result = 2 */
thenCombine整合两个计算结果
thenCombine整合两个计算结果
public <U,V> CompletableFuture<V> thenCombine( CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn) { return biApplyStage(null, other, fn); }
whenComplete
当CompletableFuture
的计算结果完成,或者抛出异常的时候,我们可以执行特定的whenComplete()
whenComplete 和 handle的两个不同点:
-
对异常的处理不同 whenComplete() 处理完异常之后异常会继续抛出,handle()处理完异常后异常不会再继续抛出。
-
对返回值处理不同 whenComplete()貌似不能改变上一个CompletableFutuer的运算结果,因为whenComplete()中不能添加返回值。换句话说,当计算完成的时候,返回上一个CompletableFutuer的运算结果 handle()中必须的要有return,且return的值类型可以和上一个CompletableFutuer的运算结果不一致,即handle()可以转换运算结果
whenComplete 和 handle的相同点:
当上一个CompletableFutuer运行抛出异常时,这两个方法都可以捕获到异常,只不过捕获到异常后的处理有些不同罢了。
thenApply和whenComplete的区别
thenApply无法捕获到异常,即上一个CompletableFutuer运行抛出异常后,thenApply方法将会被跳过, whenComplete 则不会
exceptionally()可以有返回值,但是返回值的类型必须得和上一个CompletableFutuer运算结果的类型一致,否则编译会报错
thenApply, handle可以通过return返回不同于上一个CompletableFutuer的运算结果,而且不但可以返回值不一样,返回值的类型也可以不一样。
示例1
public void testWhenComplete002() { String result = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } return "s1"; }).whenComplete((s, t) -> { System.out.println(s); System.out.println(t); // return ""; //这样写会编译报错, 提示 Unexpected return value // int i = 1/0; }).exceptionally(e -> { System.out.println(e.getMessage()); return "hello world"; }).join(); System.out.println("result = " + result); }
示例2
whenComplete() 处理完异常之后异常会继续抛出,handle()处理完异常后异常不会再继续抛出。
@Test public void testWhenComplete002() { Integer result = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } int i = 1 / 0; //9 抛出异常后程序会跳过11行,直接转到14行执行 return "s1"; }).thenApply((r1) -> { //11 System.out.println(Thread.currentThread().getId() + " r1 = " + r1); return 2; }).whenComplete((r, t) -> { //14 whenComplete()执行完之后异常会继续抛出,即 17行的exceptionally会继续执行 System.out.println("whenComplete , r = " + r + ", t = " + t); }).exceptionally(e -> { //17 System.out.println("exceptionally = " + e.getMessage()); return 1; }).join(); System.out.println("result = " + result); } /* 在JDK 1.8的运行结果是: whenComplete , r = null, t = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero exceptionally = java.lang.ArithmeticException: / by zero result = 1 */ @Test public void testWhenComplete003() { Integer result = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } int i = 1 / 0; // 抛出异常 return "s1"; }).thenApply((r1) -> { // 不会被执行 System.out.println("thenApply, r1 = " + r1); return 2; }).whenComplete((r, t) -> {//异常会继续抛出 System.out.println("whenComplete , r = " + r + ", t = " + t); }).handle((r, t) -> { //异常不会再抛出,所以下面的exceptionally不会再执行 System.out.println("handle , r = " + r + ", t = " + t); return 123; // }).exceptionally(e -> { System.out.println("exceptionally = " + e.getMessage()); return 1; }).join(); System.out.println("result = " + result); } /* 在JDK 1.8的运行结果是: whenComplete , r = null, t = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero handle , r = null, t = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero result = 123 */ @Test public void testWhenComplete004() { Integer result = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } int i = 1 / 0; // 抛出异常 return "s1"; }).thenApply((r1) -> { // 不会被执行 System.out.println("thenApply, r1 = " + r1); return 2; }).whenComplete((r, t) -> {//异常会继续抛出 System.out.println("whenComplete , r = " + r + ", t = " + t); }).handle((r, t) -> {//异常不会再抛出,所以下面的whenComplete的t为null System.out.println("handle , r = " + r + ", t = " + t); return 123; // }).whenComplete((r, t) -> { System.out.println("whenComplete , r = " + r + ", t = " + t); }).exceptionally(e -> { // 不会被执行 System.out.println("exceptionally = " + e.getMessage()); return 1; }).join(); System.out.println("result = " + result); } /** 在JDK 1.8的运行结果是: whenComplete , r = null, t = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero handle , r = null, t = java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero whenComplete , r = 123, t = null result = 123 */
示例3
源代码如下:
public CompletableFuture<T> whenComplete( BiConsumer<? super T, ? super Throwable> action) { return uniWhenCompleteStage(null, action); } public CompletableFuture<T> whenCompleteAsync( BiConsumer<? super T, ? super Throwable> action) { return uniWhenCompleteStage(asyncPool, action); } public CompletableFuture<T> whenCompleteAsync( BiConsumer<? super T, ? super Throwable> action, Executor executor) { return uniWhenCompleteStage(screenExecutor(executor), action); }
exceptionally
如果上一个CompletableFuture抛出异常,则exceptionally()方法会被调用,否则该方法不会被调用
exceptionally()必须得返回值,但是返回值的类型必须得和上一个CompletableFutuer运算结果的类型一致,否则编译会报错
示例1
@Test public void testException002() throws Exception { CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getId() + " hello world"); // 抛出异常后, thenApply不会执行 程序会直接跳到20行执行 int i = 1 / 0; return "result"; }).thenApply((r1) -> { System.out.println(Thread.currentThread().getId() + " r1 = " + r1); return "aaa"; }).thenApply((r2) -> { System.out.println(Thread.currentThread().getId() + " r2 = " + r2); return "bbb"; }).exceptionally(e->{ // 20 System.out.println("exceptionally: " + e); return 123; //编译报错: int cannot be converted to String }).get(); }
示例2
-
/** * thenApply, handle可以通过return返回不同于上一个CompletableFutuer的运算结果, * 而且不但可以返回值不一样,返回值的类型也可以不一样 * * exceptionally()可以有返回值,但是返回值的类型必须得和上一个CompletableFutuer运算结果的类型一致,否则编译会报错 * * @throws Exception */ @Test public void testHandle() throws Exception { String result = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getId() + " hello world"); return 1; }).handle((t, u) -> { System.out.println("t = " + t + ", u = " + u); // return ""; //ok return new Student("zhangsan", 11, 23.f); }).handle((t, u) -> { System.out.println("t = " + t + ", u = " + u); // return ""; //ok return "asd"; }) // .handle((t, u) -> { //18 // System.out.println("t = " + t + ", u = " + u); // return 123.56f; // }) //21 .exceptionally(e -> { System.out.println(e.getMessage()); // 这里return return "ss"; // 如果18行-21行生效,则此语句将会编译报错,提示你“String cannot be converted to float” }).get(); System.out.println(Thread.currentThread().getId() + " f1()方法结束了 result = " + String.valueOf(result)); }
completeExceptionally(Throwable ex)
本方法并不会让当前执行该语句的线程终止,本方法只是终止了completableFuture.get()和join()的阻塞,抛出的异常会被completableFuture.get()和join()捕获到 CompletableFuture.complete()、CompletableFuture.completeExceptionally只能被调用一次
@Test public void f8() throws Exception { CompletableFuture<String> completableFuture = new CompletableFuture<>(); new Thread(() -> { // 模拟执行耗时任务 System.out.println("task doing..."); try { Thread.sleep(3000); int i = 1 / 0; } catch (Exception e) { // 告诉completableFuture任务发生异常了,此异常会被抛出,可以被completableFuture.get()和join()捕获到 completableFuture.completeExceptionally(e); // 本语句会继续输出 System.out.println("completableFuture.complete(\"ok\"); 语句后面的语句正在执行。。。。。"); } // 告诉completableFuture任务已经完成 completableFuture.complete("ok"); }).start(); System.out.println("1111"); // 获取任务结果,如果没有完成会一直阻塞等待 String result = completableFuture.get(); System.out.println("计算结果:" + result); } /* 在JDK 1.8的运行结果是: 运行结果是: 1111 task doing... completableFuture.complete("ok"); 语句后面的语句正在执行。。。。。 java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357) at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1887) at com.haobin.multiplythread.completablefuture.test003.CompletaleFutureTest003.f8(CompletaleFutureTest003.java:223) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.junit.runner.JUnitCore.run(JUnitCore.java:160) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: java.lang.ArithmeticException: / by zero at com.haobin.multiplythread.completablefuture.test003.CompletaleFutureTest003.lambda$f8$14(CompletaleFutureTest003.java:211) at com.haobin.multiplythread.completablefuture.test003.CompletaleFutureTest003$$Lambda$1/1971489295.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) Process finished with exit code -1 */
thenAcceptBoth
thenAcceptBoth以及相关方法提供了类似的功能,当两个CompletionStage都正常完成计算的时候,就会执行提供的action,它用来组合另外一个异步的结果。
runAfterBoth
runAfterBoth是当两个CompletionStage都正常完成计算的时候,执行一个Runnable,这个Runnable并不使用计算的结果。
AnyOf allOf
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
这两个方法用来组合多个CompletableFuture。
allOf方法是当所有的CompletableFuture都执行完后执行计算。 anyOf方法是当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同。
anyOf接受任意多的CompletableFuture但是applyToEither只是判断两个CompletableFuture,
anyOf返回值的计算结果是参数中其中一个CompletableFuture的计算结果,applyToEither返回值的计算结果却是要经过fn处理的。当然还有静态方法的区别,线程池的选择等
示例:
@Test public void testAnyOf() throws Exception{ Random rand = new Random(); CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000 + rand.nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } return 100; }); CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000 + rand.nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } return 200; }); CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000 + rand.nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } return "abc"; }); CompletableFuture<String> future4 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000 + rand.nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } return "def"; }); //CompletableFuture<Void> f = CompletableFuture.allOf(future1,future2); CompletableFuture<Object> f = CompletableFuture.anyOf(future1,future2, future3,future4); System.out.println(f.get()); }
AtomicXXX
AtomicReference
https://segmentfault.com/a/1190000015831791?utm_source=tag-newest
设置值的方法有三个
// 这是一个非原子操作方法 public final void set(V newValue) ; public final V getAndSet(V newValue); public final boolean compareAndSet(V expectedValue, V newValue);
getAndSet
AtomicReference#getAndSet(java.lang.Object) 返回的旧的对象 和 实参设置的新的对象 是两个不同的内存对象
/** * 本代码段 证明了 java.util.concurrent.atomic.AtomicReference#getAndSet(java.lang.Object) * 返回的旧的对象 和 实参设置的新的对象 是两个不同的内存对象 */ Person person = new Person("Tom", 18); AtomicReference<Person> aRperson = new AtomicReference<Person>(person); // true System.out.println(aRperson.get() == person); AtomicReference<Person> aRperson1 = aRperson; Person p2 = aRperson.getAndSet(new Person("Tom1", aRperson.get().getAge() + 1)); // false System.out.println(aRperson1.get() == p2);
compareAndSet
摘自 https://segmentfault.com/a/1190000015831791?utm_source=tag-newest
/** * <pre> * 最终打印“2000”。 * 该示例并没有使用锁,而是使用自旋+CAS的无锁操作保证共享变量的线程安全。1000个线程, * 每个线程对金额增加1,最终结果为2000,如果线程不安全,最终结果应该会小于200 * Auther: haobin * Date: 19-8-5 * </pre> */ public class AtomicRefTest { public static void main(String[] args) throws InterruptedException { AtomicReference<Integer> ref = new AtomicReference<>(1000); List<Thread> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { Thread t = new Thread(new Task(ref), "Thread-" + i); list.add(t); t.start(); } for (Thread t : list) { t.join(); } System.out.println(ref.get()); // 打印2000 } } class Task implements Runnable { private AtomicReference<Integer> ref; Task(AtomicReference<Integer> ref) { this.ref = ref; } @Override public void run() { /** * 注意,此种写法,会导致 ref.compareAndSet 最多只会成功执行一次 */ for (; ; ) { //自旋操作 Integer oldV = ref.get(); // 不成功,则继续循环,成功则break 跳出for循环 if (ref.compareAndSet(oldV, oldV + 1)) // CAS操作 break; } } }
网文一
https://www.liangzl.com/get-article-detail-7830.html
赋值操作不是线程安全的。若想不用锁来实现,可以用AtomicReference<V>这个类,实现对象引用的原子更新。
使用场景:一个线程使用student对象,另一个线程负责定时读表,更新这个对象。那么就可以用AtomicReference这个类。
java.util.concurrent.atomic.AtomicReference<V> 类的名字。
AtomicLong LongAdder LongAccumulator
Long
Long 执行++ 不是原子操作,在多线程下是不安全的,所以出现了AtomicLong
AtomicLong
AtomicLong内部是使用Unsafe, CAS来设置value的值得,多线程下可以使用AtomicLong,但是效果低下,因为多个线程同时竞争value,会导致很多线程竞争失败,竞争失败的线程会继续循环去竞争,这会浪费很多CPU资源。
比如:
AtomicLong.incrementAndGet() 调用的是: public final long incrementAndGet() { // VALUE是 AtomicLong中value属性的偏移量,U是Unsafe类型变量 return U.getAndAddLong(this, VALUE, 1L) + 1L; } // 继续看源代码, 这是一个死循环,竞争失败,会一直竞争下去 @HotSpotIntrinsicCandidate public final long getAndAddLong(Object o, long offset, long delta) { long v; do { v = getLongVolatile(o, offset); } while (!weakCompareAndSetLong(o, offset, v, v + delta)); return v; } // 最终调用的是: compareAndSetLong @HotSpotIntrinsicCandidate public final boolean weakCompareAndSetLong(Object o, long offset, long expected, long x) { return compareAndSetLong(o, offset, expected, x); }
LongAdder
通俗但是不准确的讲:LongAdder内部使用了多个value,不同的线程操作不同的value,然后把所有的value的值加起来就是最终的结果。这样的话较少了竞争value失败的可能性,属于用空间换取时间的做法。
AtomicLong是所有的线程竞争同一个value
LongAdder是所有的线程竞争不同的value(更准确的讲师:Cell)
LongAccumulator
严格讲,LongAdder只是LongAccumulator的一个特列,
LongAdder的初始值只能是0,无法指定累加工作。
LongAccumulator则可以指定初始值,累加工作也可以自己你指定(比如每次累加为相减,相乘等等都可以),longAccumulator中没有increment()方法也没有add()方法,但是有accumulate()方法
示例
package com.haobin.automatic.lang.pack001; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAccumulator; import java.util.concurrent.atomic.LongAdder; /** * <pre> * AtomicLong LongAdder LongAccumulator 的用法 * Auther: haobin * Date: 2019/7/20 0020 * </pre> */ public class TestLongAccumulator001 { public static void main(String[] args) { LongAdder longAdder = new LongAdder(); AtomicLong atomicLong = new AtomicLong(); LongAccumulator longAccumulator = new LongAccumulator( (long left, long right) -> left+right+5 ,-2); // AtomicLong 多线程下可以使用,但是效果不高 System.out.println( atomicLong ); // 0 System.out.println( atomicLong.incrementAndGet() ); // 1 System.out.println( atomicLong.incrementAndGet() ); // 2 System.out.println("-------------\n\n"); // longAdder多线程下可以使用, 效果比AtomicLong高,但是无法指定初始值和每次累加的详细规则 System.out.println( longAdder.longValue() ); // 0 longAdder.increment(); System.out.println( longAdder.longValue() ); // 1 longAdder.increment(); System.out.println( longAdder.longValue() ); // 2 System.out.println("-------------\n\n"); // longAccumulator 中没有increment()方法,也没有add()方法,但是有accumulate()方法 // longAccumulator 可以指定初始值, 也可以指定每次累加的详细规则 System.out.println( longAccumulator.longValue() ); // -2 longAccumulator.accumulate(1); System.out.println( longAccumulator.longValue() ); // 4 longAccumulator.accumulate(1); System.out.println( longAccumulator.longValue() ); // 10 } }
网文
AQS和Java线程池中都大量用到了中断,主要的作用是唤醒线程、取消任务和清理(如ThreadPoolExecutor的shutdown方法)AQS中的acquire
方法也有中断和不可中断两种。 其中对于InterruptedException
如何处理最重要的一个原则就是Don't swallow interrupts,一般两种方法:
-
继续设置interrupted status
-
抛出新的InterruptedException
try { ……… } catch (InterruptedException e) { // Restore the interrupted status Thread.currentThread().interrupt(); // or thow a new //throw new InterruptedException(); }
AQS的acquire
就用到了第一种方法。
https://www.cnblogs.com/zhengbin/p/6435732.html
AQS
我们最常用的大概就是ReentrantLock和CountDownLatch了。ReentrantLock提供了对代码块的并发访问控制,也就是锁,说是锁,但其实并没有用到关键字synchronized
,这么神奇?其实其内部就是基于同步器来实现的,本文结合ReentrantLock的使用来分析同步器独占锁的原理。
AbstractQueuedSynchronizer
概述
AbstractQueuedSynchronizer(AQS)是一个同步器框架,在实现锁的时候,一般会实现一个继承自AQS的内部类sync,作为我们的自定义同步器。AQS内部维护了一个state成员和一个队列。其中state标识了共享资源的状态,队列则记录了等待资源的线程
Node
volatile Node prev:CLH队列的 前驱节点,当节点加入同步队列时被设置
volatile Node next:CLH队列, 当前节点的后继节点
volatile Thread thread:这个节点所代表的线程
Node nextWaiter:Node既可以作为同步队列节点使用,也可以作为Condition的等待队列节点使用。在作为同步队列节点时,nextWaiter可能有两个值:EXCLUSIVE、SHARED标识当前节点是独占模式还是共享模式;在作为等待队列节点使用时,nextWaiter保存后继节点
AQS中维护着两个队列 (两个队列都是由Node组成),一个队列就是CLH锁算法中的那个队列(我将其称之为CLH队列),另一个是Condition队列(下文再讲Condition队列是用来做什么的)。
waitStatus
volatile int waitStatus:该变量用来表征节点所处的等待状态,可分为以下几种
-
CANCLLED:值为1,由于在同步队列中等待的线程等待超时或者中断,需要从同步队列中取消等待,节点进入该状态将不再变化, 代表线程已经被取消
-
SIGNAL:值为-1,节点的后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,当前节点处于SIGNAL状态将会通知后继节点,使后继节点的线程得以运行,代表后续节点需要唤醒
-
CONDITION:值为-2,该状态表示节点处于等待队列中,线程调用wait()方法处于阻塞状态,等待Condition条件,当其他线程对Condition调用了signal()方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中,代表线程在condition queue中,等待某一条件
-
PROPAGATE:值为-3,与共享模式有关,处于该状态的节点在共享模式下处于可运行状态,并且该节点获取同步状态后会无条件的向下一个节点传播下去,代表后续结点会传播唤醒的操作,共享模式下起作用
-
INITIAL:值为0,初始状态
队列
当新增一个线程时,实际上是在队列尾部新增加一个节点,调用LockSupport.park方法。
当唤醒一个线程时,实际上是删除head所指向的节点,调用LockSupport.unPark方法。
初始化队列(此时node==head==tail)
state
state 我郝斌的个人理解是:多个线程共享一个Lock时,这多个线程是共享一个stae的
/** * The synchronization state. */ private volatile int state;
acquire 和 tryAccquire 的 应该都是state,
比如ReentrantLock的lock()方法最终会调用acquire方法,那么:
-
线程1去lock(),执行acquire,发现state=0,因此有资格执行lock()的动作,将state设置为1,返回true
-
线程2去lock(),执行acquire,发现state=1,因此没有资格执行lock()的动作,返回false
acquire方法
acquire方法在几次尝试获得锁失败后成功地将前驱节点(前驱节点存储的自然是前驱线程)的waitStatus设置为SIGNAL,然后阻塞自己。之后在某个时刻会被前继线程唤醒,然后有经过几次争抢后可能会成功地获得锁。
它首先调用tryAquire去获取共享资源,如果失败,调用addWaiter将当前线程放入等待队列,返回持有当前线程的Node对象,然后调用acquireQueued方法来监视等待队列并获取资源。acquireQueued方法会阻塞,直到成功获取。注意,acquire方法不能及时响应中断,只能在成功获取锁之后,再来处理。中断当前线程的操作抛出的异常在acquireQueued方法中被捕获,外部调用者没能看到这个异常,因此调用selfInterrupt来重置中断标识。
public final void acquire(int arg) { // 调用tryAcquire方法,试图获取锁,当获取不到锁,执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg) if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 中断当前线程, jdk 1.8 对应的代码就只有一句:Thread.currentThread().interrupt() selfInterrupt(); }
tryAcquire
java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
尝试获取锁,具体算法是:
如果state为0,且wait队列为空,则令state为1,设置占用排它锁的线程是当前线程,返回true
如果state大于0,说明锁已经被某个线程持有了
如果持有锁的线程是当前线程,则令state加1,返回true
如果持有锁的线程不是当前线程,则返回false
protected final boolean tryAcquire(int acquires) { // 获取当前线程 final Thread current = Thread.currentThread(); // 得到 lock的state属性值,注意多个线程共有一个lock对象,该lock对象的state属性定义是:private volatile int state; 这意味着多个线程都在竞争同一个state int c = getState(); if (c == 0) { /* hasQueuedPredecessors() API: Queries whether any threads have been waiting to acquire longer than the current thread, true表示有其他线程比当前线程等锁的时间更长,反之返回false 如果hasQueuedPredecessors()为true的话则进行CAS设置state的值为acquires(acquires值是1,这里为什么不直接setState? 因为可能同时有多个线程同时在执行到此处,所以用CAS来执行),设置成功,则说明当前线程已经成功持有了lock的锁 state为0时, 等待队列是可能不为空的,比如某个线程刚刚释放了锁但是等待队列中还有其他线程在等待锁,这时候state的值就是0 */ if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 设置占用排它锁的线程是当前线程,这是独占排他方式的,其他线程调用getExclusiveOwnerThread()返回的将是这里设置的实参值 setExclusiveOwnerThread(current); return true; } } // 如果state不为0说明 lock已经被某个线程持有了,current == getExclusiveOwnerThread() 说明持有该锁的线程就是当前线程(因为ReentrantLock锁支持可重入),则把state的值加一 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); // 持有该锁的线程就是当前线程,不会存在切换到其他线程去执行的可能性(我猜:线程可以切换,只不过切换过去了代码无法执行而已),所以这里就没有使用CAS设置 state值,而是直接设置state的值 setState(nextc); return true; } return false; }
acquireQueued
在队列中的节点通过此方法获取锁,对中断不敏感
acquireQueued主要做了两件事
-
使当前节点的上一个节点的状态为SIGNAL状态(待通知,即:表示这个结点的继任结点被阻塞了,到时需要通知它)
-
阻塞当前节点的线程
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { // node.predecessor() 表示获取形参node的前驱节点,形参node是刚刚插入到等待队列尾部的那个节点,此节点绑定了一个等待锁的线程 final Node p = node.predecessor(); /** 如果当前节点的上个节点如果是头结点,并且可以获取锁,则切换头结点 */ // 8 行 if (p == head && tryAcquire(arg)) { // 设置node为头结点,删除掉原来的头结点 setHead(node); p.next = null; // help GC failed = false; return interrupted; } /** // 如果前驱节点不是头结点或者锁获取失败(比如锁已被其他线程占用尚未释放),则程序会执行到这里,首先,第一次循环设置当前节点的前一个节点的waitStatus为SIGNAL(待通知) 此时返回false,然后程序会继续进行第二次循环,第二次循环执行parkAndCheckInterrupt,挂起当前线程LockSupport.park(this); 当其他线程唤醒了当前线程后(一般唤醒的是head后面离head最近的),当前线程 */ if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
addWaiter
在队列中新增一个节点,addWaiter保证了,节点一定会被插入到队列中
private Node addWaiter(Node mode) { /** node具体内容如下:waitStatus = 0, prev = null, next = null, thread = 当前线程, nextWaiter = null */ Node node = new Node(Thread.currentThread(), mode); Node pred = tail; // 如果队列不为空(tail不为null)的话,则调用一个CAS函数试图将node插入等待队列的尾部 if (pred != null) { node.prev = pred; // 如果有另外一个线程此处抢先更新队列的尾节点,CAS操作将会失败,这时会调用enq方法,继续试图将node放入队列,所以enq方法写成了一个死循环,这个叫做自旋 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } // 队列为空或者在插入节点失败时, 调用此方法来插入节点 enq(node); return node; }
enq
enq函数主要为一个for循环,enq函数首先判断队列是否为空,如果队列为空,则构造一个空节点,并将队列的首节点和尾节点设置为该空节点,这时同步队列将不再为空,进入else语句,通过CAS设置同步队列的队尾为新添加的节点,对于队首的空节点,由于不用去竞争同步状态,因此不影响后面的线程获取同步状态。for循环中的任何一步CAS操作失败,则将不断的在for循环里重试,直到成功为止,可以看出,enq(final Node node)函数将并发添加节点的请求通过CAS变得“串行化”了。
enq方法会循环检测队列,如果队列为空,则调用CAS函数初始化队列(此时node==head==tail),否则调用CAS函数将node放入队列尾。注意,这两个CAS是由AQS提供的原子操作。如果CAS失败,enq会继续循环检测,直到成功将node入列。enq方法的这种方式有一个专用的名词:CAS自旋,这种方式在AQS中有多处应用。这里有一个隐含的知识点,即tail是一个volatile成员,确保某个线程更新队列后对其他线程的可见性。
注意:队列为空的时候,第一个线程进入队列的情况有点 tricky:第一个发现队列为空并初始化队列(head节点)的线程不一定优先拿到资源。head节点被初始化后,当前线程需要下一次旋转才有机会进入队列(???郝斌:why),在这期间,完全有可能半路杀出程咬金,将当前线程与它初始化出的head节点无情分开。我们来总结一下,当队列只有一个节点时(head=tail),有两种情况:第一种是这个队列刚刚被初始化,head并没有持有任何线程对象。这个状态不会持续太久,初始化队列的线程有很大机会在下次自旋时把自己接到队尾。第二种情况是,所有等待线程都已经获得资源并继续执行下去了,队列仅有的节点是最后一个获取共享资源的线程,等到下一个线程到达等待队列并将它踢出队列之后,它才有机会被回收。
//注意: 进入此方法时 队列可能不为空 // 通过循环+CAS在队列中成功插入一个节点后返回 private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { /* 如果队列为空,说明当前线程是第一个等待获取锁的线程,先会new出一个node,然后把head==tail 都指向这个新的node,注意,这个新的node是作为等待队列的头结点存在的,该node没有绑定任何线程, 然后进行第二次循环, 第二次将会执行8行else里面的代码 */ if (compareAndSetHead(new Node())) tail = head; } else {// 8 行 /* else 的代码含义是: 把形参node插入到队列的尾部,形参node绑定了当前线程 */ node.prev = t; // CAS设置tail为node,成功后把老的tail也就是t连接到node if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
shouldParkAfterFailedAcquire
如果当前线程(Node节点封装了该线程)的前驱节点的waitStatus
是singal ( -1),则直接返回true
是1,则把队列中的该前驱节点删除掉,循环再判断前驱节点,删除所有的waitStatus为1的节点,知道遇到了一个waitStatus为非1的节点,则终止循环,然后返回true
是0,则将前继节点predpred的waitStatus设置为SIGNAL
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release to signal it, so it can safely park. */ return true; if (ws > 0) { /* Predecessor was cancelled. Skip over predecessors and indicate retry. node.prev = pred = pred.prev; 的意思是 删除node的前驱节点,把node和node原来的前驱节点的前驱节点相连 do while 循环的意思是:如果发现前继被CANCELLED了,则会跳过前继,一直找到第一个没有被CANCELLED的节点作为自己的前继 */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* waitStatus must be 0 or PROPAGATE. Indicate that we need a signal, but don't park yet. Caller will need to retry to make sure it cannot acquire before parking. 当前继节点的waitStatus为0 将前继节点predpred的waitStatus设置为SIGNAL */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
release
public final boolean release(int arg) { // 首先调用tryRelease试图释放共享资源 if (tryRelease(arg)) { Node h = head; /* 检测自己的waitStatus是否为SIGNAL,如果是的话,调用unparkSuccessor唤醒队列中的下一个线程。独占模式下,waitStatus!=0与waitStatus==-1与waitStatus==SINGNAL都是等价(这里waitStatus不会为CANCELLED,因为已经获取资源了)。如果不为SIGNAL,说明如果有下个等待线程,它正在自旋。所以直接返回true即可 */ if (h != null && h.waitStatus != 0) // 唤醒离head最近的第一个等待线程 unparkSuccessor(h); return true; } return false; }
unparkSuccessor
/** * Wakes up node's successor, if one exists. * 唤醒离node最近的下一个非等待线程(就是waitStatus是 非CANCLLED 离node最近的那个线程), 具体唤醒代码是:LockSupport.unpark(Thread) 这里的形参 node ,在ReentrantLock.unlock()中实际调用的就是等待队列的head节点, 即ReentrantLock.unlock()会触发把等待队列的head发送给本方法 * @param node the node 在ReentrantLock.unlock()中实际调用的就是等待队列的head节点 */ private void unparkSuccessor(Node node) { int ws = node.waitStatus; // 尝试将node的等待状态置为0,这样的话,后继竞争线程可以有机会再尝试获取一次锁。获取锁的代码可以参考 java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; /* 如果node的下一个节点为空(不代表tai也一定为空) 或者 node的下一个节点的waitStatus是取消 则从tail向前循环查找最接近node的非取消节点,并把此节点存入s中 要从tail向前查找最接近node的非取消节点,而不是直接从node向后找到, 因为如果读到s == null,不代表node就为tail。 更具体的说: 不妨考虑到如下场景: 1. node某时刻为tail 2. 有新线程通过addWaiter中的if分支或者enq方法添加自己 3. compareAndSetTail成功 4. 此时这里的Node s = node.next读出来s == null,但事实上node已经不是tail,它有后继了! */ // 2019-06-12 haobin: s.waitStatus > 0 的含义不太懂。。。。。 if (s == null || s.waitStatus > 0) { s = null; // 从tail向前查找最接近node的非取消节点,并将此节点保存到s中,然后终止循环 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } // 唤醒s节点, s代表的是 最接近形参node的waitStatus是 CANCELLED 的节点 if (s != null) LockSupport.unpark(s.thread); }
AQS CLH 区别
AQS中对CLH算法的实现与标准的CLH算法有什么异同?
-
CLH是一种自旋锁算法(在得到锁之前会不停地自旋),而AQS会在几次自旋失败后就将线程阻塞,这是为了避免不必要地占用CPU;
-
CLH是自旋在前继节点的标志位上的,而AQS是自旋在p == head上面(即不停地判断前继节点是否是头节点),只有在发现前继节点是头节点时,才会通过tryAcquire尝试获得锁
ReentrantLock
网文
https://blog.csdn.net/tutoumeiqian/article/details/81191596
https://www.jianshu.com/p/fadac70b2b1c
https://www.jianshu.com/p/67117db5b426
https://www.jianshu.com/p/52b07c88605e
https://www.cnblogs.com/zhanjindong/p/java-concurrent-package-aqs-overview.html
https://blog.csdn.net/zzti_erlie/article/details/80036829
http://www.importnew.com/26284.html
https://www.cnblogs.com/go2sea/p/5618628.html
流程图
List
概述
ArrayList线程是不安全的,
Collections.synchronizedList(List<T> list) 和 CopyOnWriteArrayList() 是线程安全的
synchronizedList VS CopyOnWriteArrayList
Collections.synchronizedList 和 CopyOnWriteArrayList 都可能出现脏读,两者在add remove 时 都会加锁。区别是synchronizedList 是直接对数组操作,CopyOnWriteArrayList 是拷贝一份新得数组进行操作。
Collections.synchronizedList(List<T> list) 相比较 CopyOnWriteArrayList,占用内存少,但是运行速度低。如果想避免脏读,则必须自己在遍历时加锁。
CopyOnWriteArrayList 适合读多写少的长度,读出的数据有可能是脏数据。消耗内存大。因为在读的时候读的是旧集合,写的时候不会锁住旧的集合。 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器
ArrayList线程是不安全的 举例
/** * List<Integer> values = Collections.synchronizedList( new ArrayList<>() ); // ok 9999 * List<Integer> values = new CopyOnWriteArrayList<>() ; // ok 9999 * List<Integer> values = new ArrayList<>() ; // error 返回值小于9999 说明ArrayList 线程不安全 */ List<Integer> values = Collections.synchronizedList( new ArrayList<>() ) ; IntStream.range(1, 10000).parallel().forEach(values::add); System.out.println(values.size());
Collections.synchronizedList(List<T> list)
参考地址:
https://www.jianshu.com/p/6455a4e66e14
Collections.synchronizedList(List<T> list) 相比较 CopyOnWriteArrayList,占用内存少,但是运行速度低,如果想避免脏读,则必须自己在遍历时加锁。
Collections.synchronizedList(List<T> list) 得到的是一个线程安全的List,内部对大部分方法都加了synchronzied锁,get set add remove 等大部分方法都加锁了,但是遍历listIterator()方法却没有枷锁,源代码明确指出:遍历listIterator()方法必须要调用方自己枷锁(Must be manually synched by user), java.util.Collections#synchronizedList(java.util.List<T>) 方法注释上也明确指定了遍历 的示例代码
//java.util.Collections#synchronizedList(java.util.List<T>) List list = Collections.synchronizedList(new ArrayList()); ... synchronized (list) { Iterator i = list.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); }
java.util.Collections.SynchronizedList 的 get set add remove 等大部分方法都加锁了,源代码如下:
public E get(int index) { synchronized (mutex) {return list.get(index);} } public E set(int index, E element) { synchronized (mutex) {return list.set(index, element);} } public void add(int index, E element) { synchronized (mutex) {list.add(index, element);} } public E remove(int index) { synchronized (mutex) {return list.remove(index);} } public int indexOf(Object o) { synchronized (mutex) {return list.indexOf(o);} } public int lastIndexOf(Object o) { synchronized (mutex) {return list.lastIndexOf(o);} } public boolean addAll(int index, Collection<? extends E> c) { synchronized (mutex) {return list.addAll(index, c);} } public ListIterator<E> listIterator() { return list.listIterator(); // Must be manually synched by user }
CopyOnWriteArrayList
CopyOnWriteArrayList从1.9开始, 把java.util.concurrent.CopyOnWriteArrayList#lock属性 由ReentrantLock类型改为了Object类型, 具体可以参见 “多线程并发心得 【新版样式】.doc” Lock VS Synchronized的劣势
CopyOnWriteArrayList是在执行修改操作时,copy一份新的数组进行相关的操作,在执行完修改操作后将原来集合指向新的集合来完成修改操作。它在执行add方法和remove方法的时候,分别创建了一个当前数组长度+1和-1的数组,将数据copy到新数组中,然后执行修改操作。修改完之后调用setArray方法来指向新的数组。在整个过程中是使用锁来保证不会有多个线程同时copy一个新的数组,从而造成的混乱。并且使用volatile修饰数组来保证修改后的可见性。读写操作互不影响,所以在整个过程中整个效率是非常高的
CopyOnWriteArrayList 在add remove 的方法上依然会加锁,但是加锁后内部会copy生成一个心得数组来进行操作,最后由新的数组替换原来的数组。
CopyOnWriteArrayList 依然会出现脏读的问题。
// 保证了线程的可见性 private transient volatile Object[] array; /** * The lock protecting all mutators. (We have a mild preference * for builtin monitors over ReentrantLock when either will do.) */ final transient Object lock = new Object(); // 每增加一个元素都要copy一遍旧数组中的元素,生成一个新数组 public boolean add(E e) { synchronized (lock) { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); //将原数组指向新的数组 return true; } } public E remove(int index) { synchronized (lock) { Object[] elements = getArray(); int len = elements.length; E oldValue = elementAt(elements, index); int numMoved = len - index - 1; if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); //将原数组指向新的数组 } return oldValue; } } // 将原数组指向新的数组 final void setArray(Object[] a) { array = a; }
Set
线程安全的Set
CopyOnWriteArraySet
Collections.newSetFromMap()
Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(64));