Java---多线程03:线程间通信

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zzg19950824/article/details/79439564

线程是操作系统中的独立个体,而通信使他们成为一个整体,拥有了协作的可能。

这一篇只要有以下几个部分要梳理出来:

  1. 使用wait/notify实现线程间的通信
  2. 方法join的使用
  3. ThreadLocal的使用

1. wait/notify的使用

在不使用这两个关键字的时候,我们可以思考一下这种通信方式:

存在A,B两个线程,A在做一个递增操作,B做一个While死循环判断操作,当线程A执行到5后,线程B判断发现size=5了,于是线程B执行里面的语句。

这就是线程间通信中一个简陋且有弊端的通信方式,要通过While不断循环。

而wait/notify关键字就是为了解决这种情况,实现通信。

方法 作用
wait 等待,使当前执行线程处在等待状态(“预执行序列”),会在wait()所在代码行出停止执行,直到被通知或者被中断,该方法是Object类的方法。
notify 通知,用来通知可能等待该对象对象锁的线程,并且让他获得该对象的锁

上代码演示一下:

//mythread02
    private  Object lockObject;
    public MyThread02(Object lockObject){
        this.lockObject=lockObject;
    }

    @Override
    public void run(){
        synchronized (lockObject) {
            for (int i = 0; i < 10; i++) {
                if (i==5) {
                    lockObject.notify();
                    System.out.println("发出通知");
                }
                System.out.println(i);

            }
        }
    }
//Mythread
private  Object lockObject;
public Mythread(Object lockObject){
        this.lockObject=lockObject;
    }
    @Override
    public void run(){
        try {
            synchronized (lockObject) {
                System.out.println("wait begin"+System.currentTimeMillis());
                lockObject.wait();
                System.out.println("wait end"+System.currentTimeMillis());

            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
//主要类:
Object lockObject = new Object();
        Mythread mythread =new Mythread(lockObject);
        mythread.setName("001");
        MyThread02 mythread2 =new MyThread02(lockObject);
        mythread2.setName("002");
        Thread mythread01 =new Thread(mythread);
        mythread01.start();
        Thread.sleep(50);
        Thread mythread02 =new Thread(mythread2);


//测试结果:
wait begin1520172946893
0
1
2
3
4
发出通知
5
6
7
8
9
wait end1520172946941

注意看:在发出通知后,等到notify的线程执行完了,wait所在的才继续执行。

其实wait(),notify()是个很简单的概念,这里就不过分的细说了,直接说说一些要点。

  • wait(),notify()必须获得对象锁,即只能在同步方法或者同步代码块中使用
  • 使用wait()后,当前线程会释放锁
  • 在从wait()返回前(即继续执行时),线程会和其他线程竞争锁
  • 在notify()通知后,当前线程并不会马上获得锁,等notify()的线程执行完,线程才会释放锁,此时其他线程才会竞争锁
  • notify()的唤醒是随机唤醒,是从处在等待该锁的线程里面唤醒一个。
  • notifyAll()是唤醒所有的线程,然后,同时竞争,优先级最高的最先执行。
  • interrupt()方法碰到wait()方法会出现异常
  • wait(long)方法用于等待某一时间内是否有线程对锁进行唤醒,否则自动唤醒
  • 注意不要通知过早,打乱程序正常的运行逻辑

2- 线程的阻塞,就绪,等待

这里写图片描述

  • 新建一个线程后,调用其start()方法,系统会为此线程分配CPU资源,使其处在可运行状态(就绪状态),直到线程抢到CPU资源,即可处在运行状态
  • 运行与可运行时相互切换的,这涉及到CPU资源的合理分配。以下五种情况会使线程进入可运行状态(就绪状态)
    1.调用sleep()后超过了设定的时间
    2.线程调用的阻塞IO已经返回,阻塞方法已经执行完毕
    3.线程成功的获得了试图同步的监视器
    4.线程正在等待通知,而通知此时发布
    5.处于挂起的线程调用了resume()方法
  • 暂停(阻塞):暂停有以下几种可能出现
    1.线程调用了sleep方法,自动放弃资源
    2.线程调用了阻塞式IO,返回前不可获得资源
    3.线程试图获得一个对象监视器,但是,别人在用
    4.线程等待某个通知
    5.suspend()方法挂起
  • -

3 - join()方法的使用

方法join的作用是使所属的线程对象X正常执行run()方法中的任务,而使当前线程Z进行无限期的阻塞,等待线程X销毁后再执行Z后面的代码,。

join与Sychronized的区别在于,join在内部使用使用wait()进行等待,而Sychronized使用的是对象监视器原理。

//main
public class JoinMain {
    public static void main(String[] args){

        try {
            JoinThread threadJoin =new JoinThread();
            threadJoin.start();
            threadJoin.join(2000);
            //threadJoin.sleep(2000);
            System.out.println("end   time="+System.currentTimeMillis());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
//Thread
public class JoinThread extends Thread{

    @Override
    public void run(){
        try {
            System.out.println("success");
            System.out.println("begin timer"+System.currentTimeMillis());
            Thread.sleep(5000);
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }

    }
}

可以看到运行结果和sleep的效果一样,都是暂停了两秒:
这里写图片描述

注意,这里我没有使用join()而是join(long)
他们两个的区别在于:前者是等待执行完,而join(long)是等待一段时间,如果期间执行完了,则直接马上执行后面的,如果没有执行完,则继续执行。

join(long)与sleep(long)的区别:

join(long)在内部使用wait方法,所以join方法可以释放锁而sleep方法并不会释放锁。

注意join方法后面的代码的代码可能会提前运行:

4- 类ThreadLocal简介

类ThreadLocal主要用于解决每个线程可以绑定自己自的问题,ThreadLocal就是一个全局存放数据的盒子,里面存放每个线程私有数据。实现了每个线程都有自己的共享变量。
先看怎么用

public class ThreadLocalMain {
    public static ThreadLocal t1 = new ThreadLocal();
    public static void main(String[] args){
        if(t1.get()==null){
            System.out.println("开始t1为:"+t1.get());
            t1.set("初始化");
            System.out.println("t1为:"+t1.get());
        }
    }
}

这里写图片描述

很明显,就像静态变量一样使用,但是!!ThreadLocal的静态变量并不是通用的,如果多个线程同时对t1进行了设置,但是他们设置的都是自己的t1.
就像沙盒一样,每个线程,他们都属于一个容器,同一个类,但是他们拥有自己的独立的静态变量存储空间。

public class ThreadLocalTest extends Thread{

    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                Tools.t2.set("ThreadTest:----"+(i+1));
                System.out.println("ThreadTest:----"+Tools.t2.get());
                Thread.sleep(200);
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
    }
}

public class ThreadLocal02 extends Thread{
    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                Tools.t2.set("Thread02:----"+(i+1));
                System.out.println("Thread02:----"+Tools.t2.get());
                Thread.sleep(200);
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
    }
}
    public static void main(String[] args){
//      if(t1.get()==null){
//          System.out.println("开始t1为:"+t1.get());
//          t1.set("初始化");
//          System.out.println("t1为:"+t1.get());
//      }
        ThreadLocalTest test =new ThreadLocalTest();
        ThreadLocal02 test02 =new ThreadLocal02();
        test.start();
        test02.start();
    }

这里写图片描述
这个叫线程变量的隔离性。

同样,子类和父类同样有隔离性。
这个可以用InheritableThreadLocal来解决
(继承即可)

5- 总结

线程间通信基本上就这些了,后面还有线程的lock的使用。

图片参考于:
http://blog.csdn.net/Prepared/article/details/71616709

内容参考:
《java多线程编程核心技术》

猜你喜欢

转载自blog.csdn.net/zzg19950824/article/details/79439564