一、Object.wait() Condition.await() 和 Thread.sleep()的特点
1.类似的地方
1.wait() await() sleep() 都可以使当前线程休眠等待,放弃cpu的执行权
2.wait() await() sleep() 阻塞状态都可以被 Thread.interrupt()方法清除掉,并且抛出InterruptedException
3.wait() await() sleep() 都可以设定阻塞的时间
2.差异的地方
1.wait()只有在获得监视器的情况下才能调用,也就是得在当前线程进入到synchronized代码块以内才能调用,在synchronized外面调用wait() 会抛出java.lang.IllegalMonitorStateException ,Object.notify() , Object.notifyAll() 也会有这种情况。 await() 只有在Condition对象所属Lock对象获取锁成功的情况下才可以调用,否则也会抛出java.lang.IllegalMonitorStateException,Condition.signal() Condition.signalAll()也有这种情况。而 sleep() 在任何地方都可以调用
@Test
public void waitOutSynchronized(){Object lock = new Object();try {lock.wait(1000);} catch (InterruptedException e) {e.printStackTrace();}
}在synchronized外面调用 wait() 抛异常
java.lang.IllegalMonitorStateExceptionat java.lang.Object.wait(Native Method)at com.fll.test.multi_thread.MultiThreadTest.waitOutSynchronized(MultiThreadTest.java:21)@Test
public void waitInSynchronized(){Object lock = new Object();synchronized (lock) {try {lock.wait(1000);} catch (InterruptedException e) {e.printStackTrace();}}
}在synchronized里面调用 wait() 正常执行@Test
public void awaitOutLock(){Lock lock = new ReentrantLock();Condition condition = lock.newCondition();try {condition.await(1 , TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();}
}在 condition 所属Lock对象没有获取到锁的情况下调用 await() 抛异常
java.lang.IllegalMonitorStateExceptionat java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2153)at com.fll.test.multi_thread.MultiThreadTest.awaitOutLock(MultiThreadTest.java:47)@Test
public void awaitInLock(){Lock lock = new ReentrantLock();Condition condition = lock.newCondition();lock.lock();try {condition.await(1 , TimeUnit.SECONDS);} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}
}在 condition 所属Lock对象获取到锁的情况下调用 await() ,正常执行
2.如果在 synchronized 代码块内调用 wait() ,wait() 会释放监视器 ,其他线程就可以进入synchronized 代码块内进行执行 ,Condition对象所属Lock对象获取锁成功的情况下调用 await() 也会释放监视器,其他线程就可以进入Lock锁同步起来的代码块内进行执行,但是在上述两种情况下调用 sleep() 不会释放监视器,也就是如果在synchronized代码块内,或者Condition对象所属Lock对象获取锁成功的情况下 调用sleep(),那么监视器会一直被当前线程占用,其他线程无法进入到synchronized 代码块或者 Lock锁同步起来的代码块
关于这点这里可以验证一下
@Test
public void waitReleaseMonitor() throws InterruptedException {final Object lock = new Object();long currentTimeMillis = System.currentTimeMillis();Runnable task = new Runnable(){@Overridepublic void run() {String name = Thread.currentThread().getName();synchronized (lock) {try {System.out.println(name + ": 进入等待 " + ( System.currentTimeMillis() - currentTimeMillis ) );lock.wait();System.out.println();System.out.println(name + ": 被唤醒");for (int i = 0; i < 3; i++) {System.out.println(name + ": " + i);}} catch (InterruptedException e) {e.printStackTrace();}}}};Thread thread1 = new Thread(task);Thread thread2 = new Thread(task);Thread thread3 = new Thread(task);thread1.start();thread2.start();thread3.start();Thread.sleep(1000);synchronized (lock) {System.out.println(Thread.currentThread().getName() + ": 唤醒一个");lock.notify();System.out.println(Thread.currentThread().getName() + ": 唤醒一个");lock.notify();System.out.println(Thread.currentThread().getName() + ": 唤醒一个");lock.notify();}}Thread-0: 进入等待 0
Thread-1: 进入等待 0
Thread-2: 进入等待 0
main: 唤醒一个
main: 唤醒一个
main: 唤醒一个Thread-0: 被唤醒
Thread-0: 0
Thread-0: 1
Thread-0: 2Thread-2: 被唤醒
Thread-2: 0
Thread-2: 1
Thread-2: 2Thread-1: 被唤醒
Thread-1: 0
Thread-1: 1
Thread-1: 2可以看到三个线程依次都进入 synchronized 代码块,而不是等一个线程执行完出来
另一个线程再进入执行,说明 wait() 会释放监视器,释放之后,其他线程就可以进入
synchronized代码块了。这里还要注意一点,这里三个线程都被唤醒,但是三个线程
还是按照一个执行完,出了synchronized 代码块之后,另一个线程再开始执行,说明
wait() 被唤醒之后,还是要先获取监视器才开始执行,执行完释放监视器,另一个线程
再获取到监视器执行,直到所有线程执行完
@Test
public void awaitReleaseMonitor() throws InterruptedException {final Lock lock = new ReentrantLock();Condition condition = lock.newCondition();final long currentTimeMillis = System.currentTimeMillis();Runnable task = new Runnable(){@Overridepublic void run() {String name = Thread.currentThread().getName();lock.lock();try {System.out.println(name + ": 进入等待 " + ( System.currentTimeMillis() - currentTimeMillis ) );condition.await();System.out.println();System.out.println(name + ": 被唤醒");for (int i = 0; i < 3; i++) {System.out.println(name + ": " + i);}} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();}}};Thread thread1 = new Thread(task);Thread thread2 = new Thread(task);Thread thread3 = new Thread(task);thread1.start();thread2.start();thread3.start();Thread.sleep(1000);lock.lock();try {System.out.println(Thread.currentThread().getName() + ": 唤醒一个");condition.signal();System.out.println(Thread.currentThread().getName() + ": 唤醒一个");condition.signal();System.out.println(Thread.currentThread().getName() + ": 唤醒一个");condition.signal();}finally {lock.unlock();}
}Thread-0: 进入等待 9
Thread-1: 进入等待 9
Thread-2: 进入等待 9
main: 唤醒一个
main: 唤醒一个
main: 唤醒一个Thread-0: 被唤醒
Thread-0: 0
Thread-0: 1
Thread-0: 2Thread-1: 被唤醒
Thread-1: 0
Thread-1: 1
Thread-1: 2Thread-2: 被唤醒
Thread-2: 0
Thread-2: 1
Thread-2: 2可以看到三个线程依次都进入Lock锁同步起来的代码块,而不是等一个线程执行完出来
另一个线程再进入执行,说明 await() 会释放监视器,释放之后,其他线程就可以进入
Lock锁同步起来的代码块了。这里还要注意一点,这里三个线程都被唤醒,但是三个线程
还是按照一个执行完,出了Lock锁同步起来的代码块之后,另一个线程再开始执行,说明
await() 被唤醒之后,还是要先获取监视器才开始执行,执行完释放监视器,另一个线程
再获取到监视器执行,直到所有线程执行完
@Test
public void sleepNotReleaseMonitor() throws InterruptedException {final Lock lock = new ReentrantLock();long currentTimeMillis = System.currentTimeMillis();Runnable task = new Runnable(){@Overridepublic void run() {String name = Thread.currentThread().getName();lock.lock();try {System.out.println();System.out.println(name + ": 进入等待 " + ( System.currentTimeMillis() - currentTimeMillis ) );Thread.sleep(100);System.out.println(name + ": 睡眠时间到");for (int i = 0; i < 3; i++) {System.out.println(name + ": " + i);}} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();}}};Thread thread1 = new Thread(task);Thread thread2 = new Thread(task);Thread thread3 = new Thread(task);thread1.start();thread2.start();thread3.start();Thread.sleep(1000);}Thread-0: 进入等待 0
Thread-0: 睡眠时间到
Thread-0: 0
Thread-0: 1
Thread-0: 2Thread-2: 进入等待 108
Thread-2: 睡眠时间到
Thread-2: 0
Thread-2: 1
Thread-2: 2Thread-1: 进入等待 208
Thread-1: 睡眠时间到
Thread-1: 0
Thread-1: 1
Thread-1: 2可以看到三个线程都是一个线程进入Lock锁同步起来的代码块执行完出来,
另一个线程再进入执行完,即使在sleep()的时候,也没有释放监视器,等待
休眠时间结束,该线程继续执行,直到执行完,出了Lock锁同步起来的代码块,
其他线程才可以进入开始执行
二、synchronized 和 Lock 各自的特点
1.synchronized 是隐式获取监视器,释放监视器,Lock显示获取锁,释放锁
2.synchronized只能阻塞式获取监视器,Lock可以通过lock()方法阻塞式获取锁,也可以通过tryLock()非阻塞式获取锁,还可以tryLock(long time, TimeUnit unit)指定获取锁的时间,这种非阻塞的获取方式还可以根据返回结果判断是否获取锁成功
3.synchronized 只能指定一个监视器,也就是在synchronized内部只能调用一个监视器的 wait() notify() notifyAll() ,在多生产者多消费者案例中只能通过调用notifyAll()来保证,至少有一个对方线程被唤醒。Lock对象可以创建任意多个Condition(把原来的Object的 wait() notify() notifyAll() 封装到 Condition的 await() signal() signalAll() ) 与其关联,每个Condition对象都相当于一个单独的监视器这样获取到锁之后,可以调用与该锁关联的任意Condition对象的await() signal() signalAll(),与该锁关联的意思就是由该Lock锁对象通过newCondition()创建的Condition对象,这样在多生产者多消费者案例中可以使用一个锁创建两个Condition对象,一个作为生产者监视器,一个作为消费者监视器,这样生产者生产完想要唤醒一个消费者的时候,调用消费者监视器的signal()唤醒一个消费者,唤醒的消费者消费完以后,可以调用生产者监视器的signal()唤醒一个生产者,这样就避免了notifyAll()唤醒全部线程的系统资源浪费
4.如果嵌套使用synchronized可能会导致死锁,例如:两个线程 A线程持有 a 监视器,尝试获取 b 监视器,同时B线程持有 b 监视器,尝试获取 a 监视器,这种情况下除了重启虚拟机没有其他办法解除死锁。如果使用Lock锁实现同步 可以使用lockInterruptibly() 阻塞获取锁,如果嵌套使用也可能会死锁,但是可以使用 Thread.interrupt()方法中断掉lockInterruptibly()阻塞状态,并且抛出InterruptedException,从而解除死锁
5.synchronized 默认是非公平锁,无法实现公平锁,Lock 可以通过实现类 ReentrantLock 实现公平锁
6.读数据有可能不需要进行同步,synchronized不管是读数据还是写数据都必须先获取监视器,这样就会造成读数据的线程也只能被迫的等待获取监视器,效率低。 ReentrantReadWriteLock 这个实现类可以实现读写锁也就是两个线程如果都获取读锁,是不会阻塞的,都可以获取到锁,只有在有线程获取写锁的情况下,其他线程获取读锁才会阻塞,这样就可以提高效率