传统线程同步通信技术

先看一个问题:

有两个线程,子线程先执行10次,然后主线程执行5次,然后再切换到子线程执行10,再主线程执行5次……如此往返执行50次。

看完这个问题,很明显要用到线程间的通信了, 先分析一下思路:首先肯定要有两个线程,然后每个线程中肯定有个50次的循环,因为每个线程都要往返执行任务50次,主线程的任务是执行5次,子线程的任务是执行10次。线程间通信技术主要用到 wait() 方法和 notify() 方法。wait() 方法会导致当前线程等待,并释放所持有的锁,notify() 方法表示唤醒在此对象监视器上等待的单个线程。下面来一步步完成这道线程间通信问题。

首先不考虑主线程和子线程之间的通信,先把各个线程所要执行的任务写好:

public class TraditionalThreadCommunication {

    public static void main(String[] args) {
        //开启一个子线程
        new Thread(new Runnable() {

            @Override
            public void run() {
                for(int i = 1; i <= 50; i ++) {

                    synchronized (TraditionalThreadCommunication.class) {
                        //子线程任务:执行10次
                        for(int j = 1;j <= 10; j ++) {
                            System.out.println("sub thread sequence of " + j + ", loop of " + i);
                        }
                    }
                }

            }
        }).start();

        //main方法即主线程
        for(int i = 1; i <= 50; i ++) {

            synchronized (TraditionalThreadCommunication.class) {
                //主线程任务:执行5次
                for(int j = 1;j <= 5; j ++) {
                    System.out.println("main thread sequence of " + j + ", loop of " + i);
                }
            }
        }
    }
}

如上,两个线程各有50次大循环,执行50次任务,子线程的任务是执行10次,主线程的任务是执行5次。为了保证两个线程间的同步问题,所以用了 synchronized 同步代码块,并使用了相同的锁:类的字节码对象。这样可以保证线程安全。但是这种设计不太好,就像我在上一节的死锁中写的一样,我们可以把线程任务放到一个类中,这种设计的模式更加结构化,而且把不同的线程任务放到同一个类中会很容易解决同步问题,因为在一个类中很容易使用同一把锁。所以把上面的程序修改一下:

public class TraditionalThreadCommunication {

    public static void main(String[] args) {
        Business bussiness = new Business(); //new一个线程任务处理类
        //开启一个子线程
        new Thread(new Runnable() {

            @Override
            public void run() {
                for(int i = 1; i <= 50; i ++) {
                    bussiness.sub(i);
                }

            }
        }).start();

        //main方法即主线程
        for(int i = 1; i <= 50; i ++) {
            bussiness.main(i);
        }
    }

}
//要用到的共同数据(包括同步锁)或共同的若干个方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性。
class Business {

    public synchronized void sub(int i) {

        for(int j = 1;j <= 10; j ++) {
            System.out.println("sub thread sequence of " + j + ", loop of " + i);
        }
    }

    public synchronized void main(int i) {

        for(int j = 1;j <= 5; j ++) {
            System.out.println("main thread sequence of " + j + ", loop of " + i);
        }
}

经过这样修改后,程序结构更加清晰了,也更加健壮了,只要在两个线程任务方法上加上 synchronized 关键字即可,用的都是 this 这把锁。但是现在两个线程之间还没有通信,执行的结果是主线程循环执行任务50次,然后子线程再循环执行任务50次,原因很简单,因为有 synchronized 同步。

下面继续完善程序,让两个线程之间完成题目中所描述的那样通信:

public class TraditionalThreadCommunication {

    public static void main(String[] args) {
        Business bussiness = new Business(); //new一个线程任务处理类
        //开启一个子线程
        new Thread(new Runnable() {

            @Override
            public void run() {
                for(int i = 1; i <= 50; i ++) {
                    bussiness.sub(i);
                }

            }
        }).start();

        //main方法即主线程
        for(int i = 1; i <= 50; i ++) {
            bussiness.main(i);
        }
    }

}
//要用到共同数据(包括同步锁)或共同的若干个方法应该归在同一个类身上,这种设计正好体现了高雷剧和程序的健壮性。
class Business {
    private boolean bShouldSub = true;

    public synchronized void sub(int i) {
        while(!bShouldSub) { //如果不轮到自己执行,就睡
            try {
                this.wait(); //调用wait()方法的对象必须和synchronized锁对象一致,这里synchronized在方法上,所以用this
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        for(int j = 1;j <= 10; j ++) {
            System.out.println("sub thread sequence of " + j + ", loop of " + i);
        }
        bShouldSub = false; //改变标记
        this.notify(); //唤醒正在等待的主线程
    }

    public synchronized void main(int i) {
        while(bShouldSub) { //如果不轮到自己执行,就睡
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        for(int j = 1;j <= 5; j ++) {
            System.out.println("main thread sequence of " + j + ", loop of " + i);
        }
        bShouldSub = true; //改变标记
        this.notify(); //唤醒正在等待的子线程
    }
}

首先,先不说具体的程序实现,就从结构上来看,已经体会到了这种设计的好处了:主函数里不用修改任何东西,关于线程间同步和线程间通信的逻辑全都在 Business 类中,主函数中的不同线程只需要调用放在该类中对应的任务即可。体现了高类聚的好处。
  
再看一下具体的代码,首先定义一个 boolean 型变量来标识哪个线程该执行,当不是子线程执行的时候,它就睡,那么很自然主线程就执行了,执行完了,修改了 bShouldSub 并唤醒了子线程,子线程这时候再判断一下 while 不满足了,就不睡了,就执行子线程任务,同样地,刚刚主线程修改了 bShouldSub 后,第二次循环来执行主线程任务的时候,判断 while 满足就睡了,等待子线程来唤醒。这样逻辑就很清楚了,主线程和子线程你一下我一下轮流执行各自的任务,这种节奏共循环50次。
  
另外有个小小的说明:这里其实用 if 来判断也是可以的,但是为什么要用 while 呢?因为有时候线程会假醒(就好像人的梦游,明明正在睡,结果站起来了),如果用的是if的话,那么它假醒了后,就不会再返回去判断if了,那它就很自然的往下执行任务,好了,另一个线程正在执行呢,啪叽一下就与另一个线程之间相互影响了。但是如果是while的话就不一样了,就算线程假醒了,它还会判断一下 while 的,但是此时另一个线程在执行啊,bShouldSub 并没有被修改,所以还是进到 while 里了,又被睡了~所以很安全,不会影响另一个线程!官方 JDK 文档中也是这么干的。

线程间通信就总结到这吧~若有错误,欢迎指正,我们一起进步。

原文地址:https://www.cnblogs.com/eson15/p/10229789.html

时间: 2024-10-04 22:45:37

传统线程同步通信技术的相关文章

Java多线程基础(四)Java传统线程同步通信技术

Java多线程基础(四)Java传统线程同步通信技术 编写代码实现以下功能 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次. 分析 1)子线程循环10次与主线程循环100次必须是互斥的执行,不能出现交叉,下面代码中通过synchronized关键字实现此要求: 2)子线程与主线程必须交替出现,可以通过线程同步通信技术实现,下面代码中通过bShouldSub变量实现此要求: 其他需要注意的地方 1)其中business变量必须声

4.传统线程同步通信技术

1 /** 2 * 传统线程同步通信技术 3 * 4 * ******************************************* 5 * 经验: 6 * 要用到共同数据(包括共同锁)或共同算法的若干个方法应该 7 * 归在用一个类身上,这种设计正好体现了高内聚和程序的健壮性. 8 * 9 * ******************************************* 10 * 11 * @author LiTaiQing 12 */ 13 public class T

JAVA 并发编程-传统线程同步通信技术(四)

首先介绍几个概念: wait()方法 wait()方法使得当前线程必须要等待,等到另外一个线程调用notify()或者notifyAll()方法. 当前的线程必须拥有当前对象的monitor,也即lock,就是锁. 线程调用wait()方法,释放它对锁的拥有权,然后等待另外的线程来通知它(通知的方式是notify()或者notifyAll()方法),这样它才能重新获得锁的拥有权和恢复执行. 要确保调用wait()方法的时候拥有锁,即,wait()方法的调用必须放在synchronized方法或s

【java并发】传统线程同步通信技术

先看一个问题: 有两个线程,子线程先执行10次,然后主线程执行5次,然后再切换到子线程执行10,再主线程执行5次--如此往返执行50次. 看完这个问题,很明显要用到线程间的通信了, 先分析一下思路:首先肯定要有两个线程,然后每个线程中肯定有个50次的循环,因为每个线程都要往返执行任务50次,主线程的任务是执行5次,子线程的任务是执行10次.线程间通信技术主要用到wait()方法和notify()方法.wait()方法会导致当前线程等待,并释放所持有的锁,notify()方法表示唤醒在此对象监视器

【Java多线程与并发库】4.传统线程同步通信技术

我们先通过一道面试题来了解传统的线程同步通信. 题目:子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程又循环100次,如此循环50次,请写出程序. 我没有看答案,先用自己的思路写了一段代码,有一些是借鉴传统的“生产者与消费者”的多线程模型写出来的:[java] view plain copy 在CODE上查看代码片派生到我的代码片package cn.edu.hpu.test; /** * 要求的操作: * 子线程循环10次,接着主线程循环100次,接着又回

Java多线程与并发库高级应用-传统线程同步通信技术

面试题: 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着又 主线程循环100次,如此循环50次,请写出程序 /** * 子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着又 主线程循环100次,如此循环50次,请写出程序 * * @author Administrator * */ public class TraditionalThreadCommunication { public static void main(String[] arg

(黑马Java多线程与并发库高级应用)04 传统线程同步通信技术

子线程10次,然后主线程100次,然后子线程10次,然后主线程100次.循环50次 package cn.itcast.heima2; public class TraditionalThreadComunication { public static void main(String[] args) { // TODO Auto-generated method stub Business business = new TraditionalThreadComunication().new B

JAVA 并发编程-线程同步通信技术(Lock和Condition)(十)

在之前的博客中已经介绍过线程同步通信技术<JAVA 并发编程-传统线程同步通信技术(四)>,上篇是使用的synchronized,wait,notify来实现,今天我们使用的是Lock和Condition,下面我们结合两者对比来学习. 简单的Lock锁应用: /** * 简单Lock的应用 * @author hejingyuan * */ public class LockTest { public static void main(String[] args) { new LockTest

线程同步通信技术(三)

一. 线程通信: 在同步方法中,线程之间的通信主要依靠以下三个方法来实现: 1. wait() 调用该方法会使当前线程暂停执行并释放对象锁,让其他线程可以进入Synchronized代码块,当前线程放入对象等待池中. 2. notify() 调用该方法会从对象等待池中移走任意一个线程 3. notifyAll() 调用该方法会从对象等待池中移走所有等待的线程. 二. 一道面试题: 子线程循环10次,接着主线程循环100次, 接着又回到子线程循环10次,接着再回到主线程循环100次,如此循环50次