一、首先介绍几个概念:
1.锁池
所有需要竞争同步锁的线程都会放在锁池当中,比如当前对象的锁已经被其中一个线程得到,则其他线程需要在这个锁池进行等待,当前面的线程释放同步锁后锁池中的线程去竞争同步锁,当某个线程得到后会进入就绪队列进行等待cpu资源分配。
2.等待池
当我们调用wait()方法后,线程会放到等待池当中,等待池的线程是不会去竞争同步锁。只有调用了notify()或notifyAll()后等待池的线程才会开始去竞争锁,notify()是随机从等待池选出一个线程放到锁池,而notifyAll()是将等待池的所有线程放到锁池当中
二、 wait()、sleep()、 join()和yield()区别
1、wait()
1.wait()必须在synchronized 修饰的同步代码块中调用
2.wait()会释放cpu资源和释放同步锁(类锁和对象锁)
3.调用wait()后必须调用notify()或notifyAll()后线程才会从等待池进入到锁池,当我们的线程竞争得到同步锁后就会重新进入绪状态等待cpu资源分配
4.wait()是Object类的方法
2、sleep()
1.sleep()会释放cpu资源,但是不会释放同步锁(类锁和对象锁)
2.sleep()是Thread类的方法
3.sleep()调用后线程会进入阻塞队列,时间到之后线程会进入就绪队列,重新去竞争cpu资源,而wait()方法不会。
3.yield()
yield()执行后线程直接进入就绪状态
4.join()
执行后线程进入阻塞状态,例如在线程B中调用线程A的join(),那线程B会进入到阻塞队列,直到join结束或中断线程B才开始进入阻塞队列
接下来验证一下sleep()会不会释放同步锁:
Test类中有个synchronized 修饰的方法和一个普通的方法,他们分别循环输出当前的线程名和方法名+i
创建俩个线程类,俩个线程类的run()方法操作都都是同一个Test对象,第一个线程类调用的是test对象的getMessage()方法,该方法是属于同步方法。第二个线程类调用test的getName()方法,该方法是一个普通方法。
最后我们在主函数中创建三个线程对象执行,前俩个对象的run()方法是调用test对象的getMessage()方法,后一个对象调用的是test的getName()方法。运行得到结果:
最后我们在主函数中创建三个线程对象执行,前俩个对象的run()方法是调用test对象的getMessage()方法,后一个对象调用的是test的getName()方法。运行得到结果:
仔细观察输出结果你会发现规律,先是第一个线程执行了getMessage()方法,之后调用sleep()让出cpu资源,这时候第三个线程得到cpu资源然后执行getName()方法,同样打印完后第三个线程又会让出cpu资源,然后俩个线程又继续竞争cpu资源。直到线程一和线程三执行完毕后线程二才开始执行。因此我们可以知道,sleep()方法调用的时候可以释放cpu资源给其他线程,但是不会释放同步锁(例子中线程1和线程2需要竞争获取对象锁,线程1先拿到了对象锁,所以线程2要等待线程1执行完毕释放对象锁后才能执行)。所以当我们在同步方法中调用sleep()方法后,其他线程依然访问不了这个同步方法。
接下来 验证wait()方法是否会释放同步锁:
我们修改Test类如上
创建俩个线程类,在主函数中调用:
输出结果:
第1个线程会先获取到同步锁,则第2个线程会进入“锁池”等待同步锁释放,第1个线程执行getMessage()方法判断当i=5时调用wait()方法,wait()方法释放cpu资源、释放同步锁,同时当前线程进入等待池。这时候我们的线程2获取到同步锁之后就会进入到就绪队列等待cpu分配资源然后开始执行,线程2调用getName()循环输出到i=5的时候同样调用wait()方法,此时线程2释放cpu资源、释放同步锁,然后进入等待池,接着调用notif()唤醒了线程1(即把线程1从等待池移动到了锁池),然后线程1执行循环结束后调用notif()唤醒线程2 。
注意:notif()方法要配合wait()方法使用,一般在wait()之后调用或者在线程结束时调用才会成功。