线程间通信(二)—— 方法join的使用

  在很多情况下,主线程穿件并启动子线程,如果子线程中药进行大量的耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join方法了。方法join的作用是等待线程对象销毁。

1.学习方法join前的铺垫

创建项目joinTest1,类MyThread.java代码如下:

public class MyThread extends Thread {
    public void run() {
        try {
            int secondValue = (int) (Math.random() * 10000);
            System.out.println(secondValue);
            Thread.sleep(secondValue);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

类Test.java代码如下:

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("我想当threadTest对象执行完毕后我再执行");
        System.out.println("但上面代码中的sleep 中的值应该写多少呢");
        System.out.println("答案是:不能确定");
    }
}

运行结果如下所示:

我想当threadTest对象执行完毕后我再执行
但上面代码中的sleep 中的值应该写多少呢
答案是:不能确定
6921

方法sleep()中的值不能确定

2.使用join()方法解决

  方法join可以解决这个问题。创建项目joinTest2,类MyThread.java代码如下:

public class MyThread extends Thread {
    public void run() {
        try {
            int secondValue = (int) (Math.random() * 10000);
            System.out.println(secondValue);
            Thread.sleep(secondValue);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

类Teas. java代码如下:

public class Test {
    public static void main(String[] args) {
        try {
            MyThread myThread = new MyThread();
            myThread.start();
            myThread.join();
            System.out.println("我想当threadTest对象执行完毕后我再执行");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行结果如下:

5394
我想当threadTest对象执行完毕后我再执行

&emps;&emps;方法join的作用是使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后再继续执行线程z后面的代码。
&emps;&emps;方法join具有使线程排队运行的作用,有些类似同步的运行效果。join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized关键字使用的是”对象监视器“原理做为同步。

3.方法join与异常

&emps;&emps;在join过程中,如果当前线程对象被中断,则当前线程出现异常。
创建项目joinException,类ThreadA.java代码如下:

public class ThreadA extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            String str = new String();
            Math.random();
        }
    }
}

类ThreadB.java代码如下:

public class ThreadB extends Thread{
    @Override
    public void run() {
        try{
            ThreadA a = new ThreadA();
            a.start();
            a.join();
            System.out.println("线程B在run end 处打印");
        }catch (InterruptedException e){
            System.out.println("线程B在异常处打印");
            e.printStackTrace();
        }

    }
}

类ThreadC.java代码如下:

public class ThreadC extends Thread {
    private ThreadB threadB;

    public ThreadC(ThreadB threadB) {
        this.threadB = threadB;
    }

    @Override
    public void run() {
        threadB.interrupt();
    }
}

运行类Run.java代码如下:

public class ThreadC extends Thread {
    private ThreadB threadB;

    public ThreadC(ThreadB threadB) {
        this.threadB = threadB;
    }

    @Override
    public void run() {
        threadB.interrupt();
    }
}

运行结果如下所示:

线程B在异常处打印
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Thread.join(Thread.java:1280)
	at java.lang.Thread.join(Thread.java:1354)
	at ThreadB.run(ThreadB.java:7)

出现异常

&emps;&emps;说明方法join()和interrupt()方法如果彼此遇到,则会出现异常,但是进程的按钮还是呈”红色“,原因是线程ThreadA还在继续运行,并未出现异常时是正常执行的状态。

4.方法join(long)的使用

&emps;&emps;方法join(long)中的参数是设定等待的时间。
&emps;&emps;将join(2000)改为sleep(2000)运行结果还是等待了2秒。那二者有什么区别呢?其实主要的区别来自于这两个方法对同步的处理上。

5.方法join(long)与sleep(long)的区别

&emps;&emps;方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。
方法join(long)源代码如下:

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

&emps;&emps;从源码中可以了解到,当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。
而Thread.sleep(long)方法却不释放锁。在下面的示例中将测试Thread.sleep(long)方法具有不释放锁的特点。
&emps;&emps;创建测试项目join_sleep_1,类ThreadA.java代码如下:

public class ThreadA extends Thread{
    private ThreadB b ;
    public ThreadA(ThreadB b){
        super();
        this.b = b;
    }
    @Override
    public void run() {
        try{
            synchronized (b){
                b.start();
                Thread.sleep(6000);
                //Thread.sleep()不释放锁!
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

类ThreadB.java代码如下:

public class ThreadB extends Thread{
    @Override
    public void run() {
        try{
            System.out.println("  b run begin time = "+System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("  b run   end time = "+System.currentTimeMillis());
        }catch (InterruptedException e){
            System.out.println("线程B异常");
            e.printStackTrace();
        }
    }
    synchronized public void bService(){
        System.out.println("打印了bService time = "+System.currentTimeMillis());
    }
}

类ThreadC.java代码如下:

public class ThreadC extends Thread{
    private ThreadB threadB;
    public ThreadC(ThreadB threadB){
        this.threadB = threadB;
    }

    @Override
    public void run() {
        threadB.bService();
    }
}

类Run.java的代码如下:

public class Run {
    public static void main(String[] args) {
        try{
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            Thread.sleep(1000);
            ThreadC c = new ThreadC(b);
            c.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行结果如下所示:

  b run begin time = 1657163763190
  b run   end time = 1657163768197
打印了bService time = 1657163769203

线程ThreadA不释放ThreadB的锁

&emps;&emps;由于线程ThreadA使用Thread.sleep(long)方法一直持有ThreadB对象的锁,时间达到6秒,所以线程ThreadC只有在ThreadA时间达到6秒后释放ThreadB的锁时,才可以调用ThreadB中的同步方法synchronized public void bService().
&emps;&emps;上面测试证明Thread.sleep(long)方法不释放锁。
&emps;&emps;接下来测试join()方法释放锁。
修改线程ThradA的代码如下:

public class ThreadA extends Thread{
    private ThreadB b ;
    public ThreadA(ThreadB b){
        super();
        this.b = b;
    }
    @Override
    public void run() {
        try{
            synchronized (b){
                b.start();
                b.join();
                for (int i = 0; i < Integer.MAX_VALUE; i++) {
                    String str = new String();
                    Math.random();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行结果如下所示:

  b run begin time = 1657164026011
打印了bService time = 1657164027013
  b run   end time = 1657164031017

&emps;&emps;由于线程ThreadA释放了ThreadB的锁,所以线程ThreadC可以调用ThreadB中的同步方法synchronized public void bService();
&emps;&emps;说明join(long)方法具有释放锁的功能。

6.方法join()后面的代码提前运行:出现意外

&emps;&emps;使用join方法时,如果不注意,可能会掉进“陷阱”里。
创建项目joinMoreTest,类ThreadA.java代码如下:

public class ThreadA extends Thread{
    private ThreadB b;
    public ThreadA(ThreadB b){
        super();
        this.b= b;
    }
    @Override
    public void run() {
        try{
            synchronized (b){
                System.out.println("begin A ThreadName = "+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("  end A ThreadName = "+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

类ThreadB.java代码如下

public class ThreadB extends Thread{
    @Override
    synchronized public void run() {
        try{
                System.out.println("begin B ThreadName = "+Thread.currentThread().getName()+"  "+System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("  end B ThreadName = "+Thread.currentThread().getName()+"  "+System.currentTimeMillis());

        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    synchronized public void bService(){
        System.out.println("打印了bService time = "+System.currentTimeMillis());
    }
}

创建运行类Run.java代码如下:

public class Run {
    public static void main(String[] args) {
        try{
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            b.start();
            b.join(2000);
            System.out.println("       main end "+System.currentTimeMillis());
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

运行结果会出现两种情况情况:
第一种:

begin A ThreadName = Thread-1  1657165355394
  end A ThreadName = Thread-1  1657165360403
begin B ThreadName = Thread-0  1657165360403
  end B ThreadName = Thread-0  1657165365410
         main end 1657165365410

第二种:

begin A ThreadName = Thread-1  1657165202566
  end A ThreadName = Thread-1  1657165207576
       main end 1657165207576
begin B ThreadName = Thread-0  1657165207576
  end B ThreadName = Thread-0  1657165212586

这个就是陷阱,就是join方法后面的代码提前运行了。

7.方法join()后面的代码提前运行:解释意外

修改Run.java代码如下:

public class Run {
    public static void main(String[] args) {
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            b.start();
            System.out.println("       main end "+System.currentTimeMillis());
        }
    }

运行结果如下:

       main end 1657165933768
begin A ThreadName = Thread-1  1657165933770
  end A ThreadName = Thread-1  1657165938770
begin B ThreadName = Thread-0  1657165938770
  end B ThreadName = Thread-0  1657165943774

多次运行,然后可以发现:main end 往往都会第一个打印。所以可以完全确定得出:方法join(2000)大部分是先运行的,也就是先抢到ThreadB的锁,然后快速进行释放。

运行结果解释(一)
运行结果:

begin A ThreadName = Thread-1  1657165202566
  end A ThreadName = Thread-1  1657165207576
       main end 1657165207576
begin B ThreadName = Thread-0  1657165207576
  end B ThreadName = Thread-0  1657165212586

解释:
1 ) b.join(2000)方法先抢到B锁,然后将B锁进行释放;
2 )ThreadA抢到锁,打印ThreadA 开始并且sleep(5000);
3 ) ThreadA打印ThreadA 结束,并释放锁;
4)这时join(2000)和ThreadB争抢锁,而join(2000)再次抢到锁,发现时间已过,释放锁后打印main end;
5 )ThreadB抢到锁打印ThreadB 开始;
6)5秒之后再打印ThreadB 结束。

运行结果解释(二)
运行结果:

begin A ThreadName = Thread-1  1657165355394
  end A ThreadName = Thread-1  1657165360403
begin B ThreadName = Thread-0  1657165360403
  end B ThreadName = Thread-0  1657165365410
         main end 1657165365410

解释:
1、b.join(2000)方法先抢到B锁, 然后将B锁进行释放;
2、T比eadA抢到锁,打印TeadAbegin并且sleep(5000);
3、ThreadA打印T缸eadAend, 并释放锁;
4、这时join(2000)和ThreadB争抢锁,而ThreadB抢到锁后执行sleep(5000)后释放锁;
5、main end在最后输出。


以上代码下载请点击该链接:https://github.com/Yarrow052/Java-package.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Python线程间通信可以通过多种方式实现。其一种常见的方法使用队列(Queue)对象来进行数据传递。队列提供了线程安全的操作,可以在多个线程之间安全地传递数据。 首先,需要导入threading和queue模块: import threading import queue 然后,可以创建一个队列对象,并在多个线程使用该队列进行通信。例如,一个线程可以将数据放入队列,另一个线程可以从队列获取数据。这样就实现了线程间的数据传递。 下面是一个简单的示例代码,演示了如何在两个线程间进行通信: ```python def producer(queue): # 生产者线程,向队列放入数据 while True: data = input("请输入要放入队列的数据:") queue.put(data) def consumer(queue): # 消费者线程,从队列获取数据 while True: data = queue.get() print("从队列获取到的数据为:", data) # 创建一个队列对象 q = queue.Queue() # 创建生产者线程和消费者线程 p = threading.Thread(target=producer, args=(q,)) c = threading.Thread(target=consumer, args=(q,)) # 启动线程 p.start() c.start() # 等待线程结束 p.join() c.join() ``` 在上面的代码,创建了一个队列对象q,并分别创建了一个生产者线程p和一个消费者线程c。生产者线程从用户输入获取数据,并将数据放入队列;消费者线程从队列获取数据,并打印出来。通过队列对象的put和get方法,实现了线程间的数据传递。 需要注意的是,队列对象提供了一些其他的方法,如put_nowait、get_nowait等,可以在队列满或空的情况下立即返回,而不是阻塞线程。此外,还可以使用线程锁(Lock)等机制来实现更复杂的线程间通信需求。 总结一下,Python线程间通信可以通过队列对象来实现,使用put和get方法来进行数据传递。同时,还可以使用线程锁等机制来处理更复杂的通信需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值