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

港控/mmm° 2022-05-30 04:05 444阅读 0赞

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

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

  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 通知,用来通知可能等待该对象对象锁的线程,并且让他获得该对象的锁

上代码演示一下:

  1. //mythread02
  2. private Object lockObject;
  3. public MyThread02(Object lockObject){
  4. this.lockObject=lockObject;
  5. }
  6. @Override
  7. public void run(){
  8. synchronized (lockObject) {
  9. for (int i = 0; i < 10; i++) {
  10. if (i==5) {
  11. lockObject.notify();
  12. System.out.println("发出通知");
  13. }
  14. System.out.println(i);
  15. }
  16. }
  17. }
  18. //Mythread
  19. private Object lockObject;
  20. public Mythread(Object lockObject){
  21. this.lockObject=lockObject;
  22. }
  23. @Override
  24. public void run(){
  25. try {
  26. synchronized (lockObject) {
  27. System.out.println("wait begin"+System.currentTimeMillis());
  28. lockObject.wait();
  29. System.out.println("wait end"+System.currentTimeMillis());
  30. }
  31. } catch (InterruptedException e) {
  32. // TODO Auto-generated catch block
  33. e.printStackTrace();
  34. }
  35. }
  36. //主要类:
  37. Object lockObject = new Object();
  38. Mythread mythread =new Mythread(lockObject);
  39. mythread.setName("001");
  40. MyThread02 mythread2 =new MyThread02(lockObject);
  41. mythread2.setName("002");
  42. Thread mythread01 =new Thread(mythread);
  43. mythread01.start();
  44. Thread.sleep(50);
  45. Thread mythread02 =new Thread(mythread2);
  46. //测试结果:
  47. wait begin1520172946893
  48. 0
  49. 1
  50. 2
  51. 3
  52. 4
  53. 发出通知
  54. 5
  55. 6
  56. 7
  57. 8
  58. 9
  59. 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使用的是对象监视器原理。

  1. //main
  2. public class JoinMain {
  3. public static void main(String[] args){
  4. try {
  5. JoinThread threadJoin =new JoinThread();
  6. threadJoin.start();
  7. threadJoin.join(2000);
  8. //threadJoin.sleep(2000);
  9. System.out.println("end time="+System.currentTimeMillis());
  10. } catch (InterruptedException e) {
  11. // TODO Auto-generated catch block
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. //Thread
  17. public class JoinThread extends Thread{
  18. @Override
  19. public void run(){
  20. try {
  21. System.out.println("success");
  22. System.out.println("begin timer"+System.currentTimeMillis());
  23. Thread.sleep(5000);
  24. } catch (Exception e) {
  25. // TODO: handle exception
  26. e.printStackTrace();
  27. }
  28. }
  29. }

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

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

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

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

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

4- 类ThreadLocal简介

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

  1. public class ThreadLocalMain {
  2. public static ThreadLocal t1 = new ThreadLocal();
  3. public static void main(String[] args){
  4. if(t1.get()==null){
  5. System.out.println("开始t1为:"+t1.get());
  6. t1.set("初始化");
  7. System.out.println("t1为:"+t1.get());
  8. }
  9. }
  10. }

![这里写图片描述][Image 1]

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

  1. public class ThreadLocalTest extends Thread{
  2. @Override
  3. public void run(){
  4. try {
  5. for (int i = 0; i < 10; i++) {
  6. Tools.t2.set("ThreadTest:----"+(i+1));
  7. System.out.println("ThreadTest:----"+Tools.t2.get());
  8. Thread.sleep(200);
  9. }
  10. } catch (Exception e) {
  11. // TODO: handle exception
  12. }
  13. }
  14. }
  15. public class ThreadLocal02 extends Thread{
  16. @Override
  17. public void run(){
  18. try {
  19. for (int i = 0; i < 10; i++) {
  20. Tools.t2.set("Thread02:----"+(i+1));
  21. System.out.println("Thread02:----"+Tools.t2.get());
  22. Thread.sleep(200);
  23. }
  24. } catch (Exception e) {
  25. // TODO: handle exception
  26. }
  27. }
  28. }
  29. public static void main(String[] args){
  30. // if(t1.get()==null){
  31. // System.out.println("开始t1为:"+t1.get());
  32. // t1.set("初始化");
  33. // System.out.println("t1为:"+t1.get());
  34. // }
  35. ThreadLocalTest test =new ThreadLocalTest();
  36. ThreadLocal02 test02 =new ThreadLocal02();
  37. test.start();
  38. test02.start();
  39. }

![这里写图片描述][Image 1]
这个叫线程变量的隔离性。

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

5- 总结

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

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

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

[Image 1]:

发表评论

表情:
评论列表 (有 0 条评论,444人围观)

还没有评论,来说两句吧...

相关阅读