本文共 5174 字,大约阅读时间需要 17 分钟。
在之前的博客中已经介绍过线程同步通信技术《 并发编程-传统线程同步通信技术(四)》,上篇是使用的synchronized,wait,notify来实现,今天我们使用的是Lock和Condition,下面我们结合两者对比来学习。
简单的Lock锁应用:
- /**
- * 简单Lock的应用
- * @author hejingyuan
- *
- */
- public class LockTest {
- public static void main(String[] args) {
- new LockTest().init();
- }
- private void init(){
- final Outputer outputer = new Outputer();
- new Thread(new Runnable(){
- @Override
- public void run() {
- while(true){
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- outputer.output("zhangxiaoxiang");
- }
- }
- }).start();
- new Thread(new Runnable(){
- @Override
- public void run() {
- while(true){
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- outputer.output("lihuoming");
- }
- }
- }).start();
- }
- static class Outputer{
- Lock lock = new ReentrantLock();
- public void output(String name){
- int len = name.length();
- lock.lock();
- try{
- for(int i=0;i<len;i++){
- System.out.print(name.charAt(i));
- }
- System.out.println();
- }finally{
- lock.unlock();
- }
- }
- }
- }
Lock比传统线程模型中的Synchronied方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码段要实现同步互斥的效果,它们必须用同一个Lock对象,锁是在代表要操作的资源的类的内部方法中,而不是线程代码中.
注意:
和synchronized不同的是,在线程执行完以后,要关闭锁unlock(),如果不关闭,其他在等待的线程就永远被锁在外面了。因为synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否,而ReentrantLock使用代码实现的,系统无法自动释放锁,需要在代码中finally子句中显式释放锁lock.unlock();
结合前篇博客进行对比(同步通信):
实现效果:子线程循环10次,接着主线程循环100次,又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次
- public class ConditionCommunication {
- public static void main(String[] args) {
- final Business business = new Business();
- //创建了一个线程,并启动
- new Thread(
- new Runnable() {
- @Override
- public void run() {
- for(int i=1;i<=50;i++){
- business.sub(i);
- }
- }
- }
- ).start();
- //因为mian方法本身就占用一个线程,所以主线程不需要再new Thread
- for(int i=1;i<=50;i++){
- business.main(i);
- }
- }
- static class Business {
- Lock lock = new ReentrantLock();
- Condition condition = lock.newCondition();
- //决定是main执行还是sub执行
- private boolean bShouldSub = true;
- public void sub(int i){
- lock.lock();// 锁住了别的线程就不能进来了,包括下面的main()因为他们用的是同一把锁
- try{
- //bShouldSub==false时等待
- while(!bShouldSub){
- try {
- condition.await();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- for(int j=1;j<=10;j++){
- System.out.println("sub thread sequence of " + j + ",loop of " + i);
- }
- bShouldSub = false;
- condition.signal();
- }finally{
- lock.unlock();
- }
- }
- public void main(int i){
- lock.lock();
- try{
- //bShouldSub==true时等待
- while(bShouldSub){
- try {
- condition.await();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- for(int j=1;j<=100;j++){
- System.out.println("main thread sequence of " + j + ",loop of " + i);
- }
- bShouldSub = true;
- condition.signal();
- }finally{
- //如果中途抛出异常,那么这把锁就没有被解锁,别人就进不来了
- //所以写在finally里面
- lock.unlock();
- }
- }
- }
- }
在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现。
Condition实例实质上被绑定到一个锁上。要为特定Lock实例获得Condition 实例,请使用其newCondition() 方法。
注意:在等待Condition时,可能会发生"虚假唤醒"。
虚假唤醒(spuriouswakeup)在采用条件等待时,我们使用的是
while(条件不满足){
condition_wait(cond, mutex);
}
而不是:
If( 条件不满足 ){
Condition_wait(cond,mutex);
}
这是因为可能会存在虚假唤醒”spuriouswakeup”的情况。
也就是说,即使没有线程调用condition_signal,原先调用condition_wait的函数也可能会返回。此时线程被唤醒了,但是条件并不满足,这个时候如果不对条件进行检查而往下执行,就可能会导致后续的处理出现错误。
在系统设计时应该可以避免虚假唤醒,但是这会影响条件变量的执行效率,而既然通过while循环就能避免虚假唤醒造成的错误,因此程序的逻辑就变成了while循环的情况。
Condition可以控制多个线程之间的运行顺序
Condition和传统的线程通信没什么区别,Condition的强大之处在于它可以为多个线程间建立不同的Condition,这样可以控制多个线程之间的运行顺序。
例如:
应当先对线程1、线程2,建 condition对象 1: c_th1,对象 2: c_th2;
c_th1.await() // 阻塞写线程1
c_th2.signal() // 唤醒读线程2
-------
否则,多线程时,函数没有参数时,如何指定阻塞哪个,唤醒哪个呢,线程可能多于2个,总要有方法指定。
实现demo(有三个线程,想让线程1运行完以后运行线程2,线程2运行完以后运行线程3,线程3运行完以后又运行线程1):
- public class ThreeConditionCommunication {
- public static void main(String[] args) {
- final Business business = new Business();
- new Thread(
- new Runnable() {
- @Override
- public void run() {
- for(int i=1;i<=50;i++){
- business.sub2(i);
- }
- }
- }
- ).start();
- new Thread(
- new Runnable() {
- @Override
- public void run() {
- for(int i=1;i<=50;i++){
- business.sub3(i);
- }
- }
- }
- ).start();
- for(int i=1;i<=50;i++){
- business.main(i);
- }
- }
- static class Business {
- Lock lock = new ReentrantLock();
- Condition condition1 = lock.newCondition();
- Condition condition2 = lock.newCondition();
- Condition condition3 = lock.newCondition();
- private int shouldSub = 1;
- public void sub2(int i){
- lock.lock();
- try{
- while(shouldSub != 2){
- try {
- condition2.await();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- for(int j=1;j<=10;j++){
- System.out.println("sub2 thread sequence of " + j + ",loop of " + i);
- }
- shouldSub = 3;
- condition3.signal();
- }finally{
- lock.unlock();
- }
- }
- public void sub3(int i){
- lock.lock();
- try{
- while(shouldSub != 3){
- try {
- condition3.await();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- for(int j=1;j<=20;j++){
- System.out.println("sub3 thread sequence of " + j + ",loop of " + i);
- }
- shouldSub = 1;
- condition1.signal();
- }finally{
- lock.unlock();
- }
- }
- public void main(int i){
- lock.lock();
- try{
- while(shouldSub != 1){
- try {
- condition1.await();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- for(int j=1;j<=100;j++){
- System.out.println("main thread sequence of " + j + ",loop of " + i);
- }
- shouldSub = 2;
- condition2.signal();
- }finally{
- lock.unlock();
- }
- }
- }
- }
总结:
对于以上的两种实现线程同步通信的方式,Lock替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。而对比来说Lock(本身为一个对象)更加面向对象,Condition可以为多个线程间建立不同的Condition,这样可以控制多个线程之间的运行顺序。
转载地址:https://yuki-ho.blog.csdn.net/article/details/52316434 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!