Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文)


  • 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享给大家,本人的水平有限,如果我的分析或者结论有错误希望大家一定要帮我指出来,我好能改正和提高

一、对于线程同步和同步锁的理解(注:分享了三篇高质量的博客)


以下我精心的挑选了几篇博文,分别是关于对线程同步的理解和如何选择线程锁以及了解线程锁的作用范围。

<一>线程同步锁的选择

1. 这里我推荐下陆先生的Java代码质量改进之:同步对象的选择这篇博文。

2. 以上推荐的博文是以卖火车票为例,引出了非同步会导致的错误以及同步锁(监视器)应该如果选择,应该能够帮助大家理解同步锁。

<二>线程同伴锁用法及同步锁的作用范围

1. 这里我推荐下Java中synchronized同步锁用法及作用范围这篇博文。

2. 以上的博文将静态锁(字节码文件锁)和非静态锁(this)进行了对比,以及将线程非同步和线程同步下进行了对比,对大家了解线程锁的用法和作用范围有很大的帮助。

<三>对线程同步的理解

1. 这里我推荐下java中线程同步的理解(非常通俗易懂)这篇博文。

2. 以上推荐的博文以非常通俗易懂的观点解释了到时什么同步,将同步理解成了线程同步就是线程排队,而且举了一些日常生活中的例子来让大家理解到底什么是同伴。

<四>同步的作用场景

1. 并不是说同步在什么情况下都是好的,因为线程的同步会带来较低效率,因为线程同步就代表着线程要排队,即线程同步锁会带来的同步阻塞状态。

2. 因为CPU是随意切换线程的,当我们想让当前线程执行之后CPU不随意切换到其他线程,或者我们想要让某个线程的代码能够在完全执行之前不会被抢夺执行权,不会导致从而无法连续执行,那么我们就需要线程的帮助。

二、线程同步和线程通信的几个小细节


以下是我在学习线程同步时,遇到的小问题和我的小感悟

<一>线程sleep方法的基本用法和注意细节

    1.sleep方法的基本用法

Thread.sleep(long millis),传入毫秒数(1秒 = 1000毫秒),在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。(~注:Java技术文档的意思就是该线程休眠指定的毫秒数,而且休眠状态暂时失去CPU执行权,而且线程醒来后,该线程不会释放锁。)

  1. /**
  2. *
  3. * Thread.sleep的计时器用法
  4. *
  5. */
  6. public class ThreadSleepTest {
  7.  
  8. public static void main(String[] args) {
  9. new Thread() {
  10. @Override
  11. public void run() {
  12. int timeCount = 10;
  13. while (timeCount >= 0) {
  14. if (timeCount == 0) {
  15. System.out.println("新年快乐!~");
  16. break;
  17. }
  18. System.out.println("还剩" + timeCount-- + "秒");
  19. try {
  20. Thread.sleep(1000);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }
  26. }.start();
  27. }
  28.  
  29. }

    2.sleep方法使用的位置选择

我在使用sleep方法时发现,当sleep的位置不一致所放的位置不同时,线程所运行的结果也是大不相同的,以下的代码是为了举例子,并不是说这个同步代码块就是应这样写(其实这段代码这么写是有很大的问题的,因为同步资源的选择不准确),至于同步资源的选择我在第二个大问题会讲到。

      • A 以下的代码是sleep方法出现在了售票的代码块之前,这时出现了负票。(可能时间上也会导致差异,但是这里先不考虑时间时间因素,时间因素等下讲。)
  1. package javase.week4;
  2.  
  3. public class SellTrainTickets {
  4.  
  5. public static void main(String[] args) {
  6. new MyThread("窗口1").start();
  7. new MyThread("窗口2").start();
  8. new MyThread("窗口3").start();
  9. new MyThread("窗口4").start();
  10. }
  11.  
  12. }
  13.  
  14. class MyThread extends Thread {
  15.  
  16. static int tickets = 100;
  17.  
  18. public MyThread(String name) {
  19. super(name);
  20. }
  21.  
  22. @Override
  23. public void run() {
  24. while (tickets > 0) {//假设这已经减到了1 1>0 然后窗口1 窗口2 窗口3 窗口4 都进入循环
  25. try {
  26. Thread.sleep(20);
  27.  
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. synchronized (MyThread.class) {
  32. System.out.println(getName() + "卖出了第" + tickets-- + "张票!");//然后0 -1 -2 -3,这时就出现了负票
  33. }
  34. }
  35. }
  36. }

      • B 以下的代码是sleep方法出现在了售票的代码块之后,这里没有出现负票了,在票数100的情况下,而且时间是20毫秒的情况下,该段代码正好保证了时间点上的合理性,但是相同情况下sleep方法出现在输出售票之前的代码就会出现错误,即使改变时间和票数其sleep方法出现的位置错误,还是会导致了在票数为负的情况。(其实如果票数更改或者时间的改变也可能导致sleep方法出现在售票代码块之后的情况下负票的出现)
  1. public class SellTrainTickets {
  2.  
  3. public static void main(String[] args) {
  4. new MyThread("窗口1").start();
  5. new MyThread("窗口2").start();
  6. new MyThread("窗口3").start();
  7. new MyThread("窗口4").start();
  8. }
  9.  
  10. }
  11.  
  12. class MyThread extends Thread {
  13.  
  14. static int tickets = 100;
  15.  
  16. public MyThread(String name) {
  17. super(name);
  18. }
  19.  
  20. @Override
  21. public void run() {
  22. while (tickets > 0) {//假设这已经减到了0 5>0 然后窗口1 窗口2 窗口3 窗口4 都进入循环
  23. synchronized (MyThread.class) {
  24. System.out.println(getName() + "卖出了第" + tickets-- + "张票!");//然后 3 2 1 0
  25. }
  26. try {
  27. Thread.sleep(20);//此时票数等于0,这时窗口1 窗口2 窗口3 窗口4 都处于休眠,然后如果这里的时间合理的话,再次判断的话,正好在等于0的时候,都没有线程再次进入循环,也就不会出现负票了
  28.  
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }

      • C 总结下其实sleep方法出现的位置可能会影响到线程的结果,但是其实一般情况下是不会这么样去使用的,这里只是为了演示下sleep方法在位置不同的情况下出现的不同的结果,目的是为了让大家注意编程的细节。

     3.sleep方法的传入参数的选择

sleep方法的传入的毫秒数对于线程的运行结果是有较大的影响的,最直接简单的影响就是让运行延迟了,但是除了这个以外其实也让线程的运行结果发生了变化,顺便分享一篇一篇高质量的博文Sleep(0)的妙用

      • A  当传入的参数为100时,以下是代码演示和执行结果,几乎每一个窗口(线程)都可以抢夺到运行权,而且比较分散。
  1. public class TicketsThreadTest {
  2.  
  3. public static void main(String[] args) {
  4. new TicketThread("窗口1").start();
  5. new TicketThread("窗口2").start();
  6. new TicketThread("窗口3").start();
  7. new TicketThread("窗口4").start();
  8. }
  9.  
  10. }
  11.  
  12. class TicketThread extends Thread {
  13.  
  14. public TicketThread(String name) {
  15. super(name);
  16. }
  17.  
  18. private static int ticket = 100;
  19.  
  20. public void run() {
  21. while (true) {
  22. synchronized (TicketThread.class) {
  23. if (ticket <= 0) {
  24. break;
  25. }
  26. System.out.println(getName() + "卖出了第" + ticket-- + "张票!");
  27. try {
  28. Thread.sleep(100);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }
  35. }

      • B 当传入的参数为1时,以下是代码演示和执行结果,这次执行的效果就不是很好,并不是每一个线程都能很好的执行到,或者执行得不是很分散。
  1. public class TicketsThreadTest {
  2.  
  3. public static void main(String[] args) {
  4. new TicketThread("窗口1").start();
  5. new TicketThread("窗口2").start();
  6. new TicketThread("窗口3").start();
  7. new TicketThread("窗口4").start();
  8. }
  9.  
  10. }
  11.  
  12. class TicketThread extends Thread {
  13.  
  14. public TicketThread(String name) {
  15. super(name);
  16. }
  17.  
  18. private static int ticket = 100;
  19.  
  20. public void run() {
  21. while (true) {
  22. synchronized (TicketThread.class) {
  23. if (ticket <= 0) {
  24. break;
  25. }
  26. System.out.println(getName() + "卖出了第" + ticket-- + "张票!");
  27. try {
  28. Thread.sleep(1);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }
  35. }

      • C 总结以下时间参数对线程的结果的影响,以卖火车票为例,当我们在sleep方法中输入不同的参数,那么线程的运行结果就发生了变化,因为当我们给定的休眠期长了,那么线程的抢夺CPU执行权的速度就放缓了,此时运行的结果就变得比较分散,如果几乎没有休眠期那么抢到执行权的窗口(线程)可能还是处于领先优势,sleep方法其实让处于优先地位的暂时休眠让出了CPU执行权,然后sleep醒来又处于就绪状态来抢夺资源。这样不会让其他线程变成无法执行的尴尬境遇。当然后续可以使用wait和notify以及notifyAll的方法,让线程进行有规律地交替运行。

<二>明确需要同步的共享资源

       如果这里同步的是代码块不是代码方法,那么这里需要对要同步的共享资源的选择要准确,如果选择得不准确会导致结果不理想。

      • A 以下代码表示选择的共享代码块为售票的单个输出语句,此时可以看出结果,结果出现了负票
  1. public class TicketsThreadTest {
  2.  
  3. public static void main(String[] args) {
  4. new TicketThread("窗口1").start();
  5. new TicketThread("窗口2").start();
  6. new TicketThread("窗口3").start();
  7. new TicketThread("窗口4").start();
  8. }
  9.  
  10. }
  11.  
  12. class TicketThread extends Thread {
  13.  
  14. public TicketThread(String name) {
  15. super(name);
  16. }
  17.  
  18. private static int ticket = 100;
  19.  
  20. public void run() {
  21. while (true) {
  22. if (ticket <= 0) {
  23. break;
  24. }
  25. synchronized (TicketThread.class) {
  26. System.out.println(getName() + "卖出了第" + ticket-- + "张票!");
  27. }
  28. try {
  29. Thread.sleep(0);
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. }
  34. }
  35. }

      • B 以下代码表示选择的共享代码块是while循环的整个代码块,此时可以看出结果,结果没有出现负票,而且经过了多次尝试也没有出现
  1. public class TicketsThreadTest {
  2.  
  3. public static void main(String[] args) {
  4. new TicketThread("窗口1").start();
  5. new TicketThread("窗口2").start();
  6. new TicketThread("窗口3").start();
  7. new TicketThread("窗口4").start();
  8. }
  9.  
  10. }
  11.  
  12. class TicketThread extends Thread {
  13.  
  14. public TicketThread(String name) {
  15. super(name);
  16. }
  17.  
  18. private static int ticket = 100;
  19.  
  20. public void run() {
  21. while (true) {
  22. synchronized (TicketThread.class) {
  23. if (ticket <= 0) {
  24. break;
  25. }
  26. System.out.println(getName() + "卖出了第" + ticket-- + "张票!");
  27. try {
  28. Thread.sleep(0);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }
  35. }

      • C 总结在选择需要同步的代码块是一定要注意哪些代码块(资源是需要共享的),这里就要判断下这些代码是否是需要共享,将需要共享的资源用synchronized代码块包起来

<三>线程通信之while和if的选择

      • A 以下的代码使用的是if选择结构进行线程通信之间的判断,可以发现三个线程之间没有有规律地交替进行。
  1. package javase.week4;
  2.  
  3. /**
  4. *
  5. * 三个线程之间的通信使用if选择语句
  6. *
  7. */
  8. public class ComunicatedThreadTest {
  9. public static void main(String[] args) {
  10. Printer1121 p = new Printer1121();
  11. new Thread() {
  12. @Override
  13. public void run() {
  14. while (true) {
  15. try {
  16. p.print1();
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. }
  21. }
  22. }.start();
  23. new Thread() {
  24. @Override
  25. public void run() {
  26. while (true) {
  27. try {
  28. p.print2();
  29. } catch (Exception e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }.start();
  35.  
  36. new Thread() {
  37. @Override
  38. public void run() {
  39. while (true) {
  40. try {
  41. p.print3();
  42. } catch (Exception e) {
  43. e.printStackTrace();
  44. }
  45. }
  46. }
  47. }.start();
  48. }
  49.  
  50. }
  51.  
  52. class Printer1121 {
  53. private int flag = 1;
  54.  
  55. public void print1() throws Exception {
  56. synchronized (this) {
  57. if (flag != 1) {
  58. this.wait();
  59. }
  60. Thread.sleep(100);
  61. System.out.print(1);
  62. System.out.print(2);
  63. System.out.print(3);
  64. System.out.print(4);
  65. System.out.print(5);
  66. System.out.println();
  67. flag = 2;
  68. this.notifyAll();
  69. }
  70. }
  71.  
  72. public void print2() throws Exception {
  73. synchronized (this) {
  74. if (flag != 2) {
  75. this.wait();
  76. }
  77. Thread.sleep(100);
  78. System.out.print("a");
  79. System.out.print("b");
  80. System.out.print("c");
  81. System.out.print("d");
  82. System.out.print("e");
  83. System.out.println();
  84. flag = 3;
  85. this.notifyAll();
  86. }
  87. }
  88.  
  89. public void print3() throws Exception {
  90. synchronized (this) {
  91. if (flag != 3) {
  92. this.wait();
  93. }
  94. Thread.sleep(100);
  95. System.out.print("A");
  96. System.out.print("B");
  97. System.out.print("C");
  98. System.out.print("D");
  99. System.out.print("E");
  100. System.out.println();
  101. flag = 1;
  102. this.notifyAll();
  103. }
  104. }
  105. }

      • B 以下是用while对通信条件进行循环判断的,可以发现三个线程是有规律地循环进行运行的。
  1. package javase.week4;
  2. /**
  3. *
  4. * 三个线程之间的通信使用while循环判断语句
  5. *
  6. */
  7. public class ComunicatedThreadTest {
  8. public static void main(String[] args) {
  9. Printer1121 p = new Printer1121();
  10. new Thread() {
  11. @Override
  12. public void run() {
  13. while (true) {
  14. try {
  15. p.print1();
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. }
  21. }.start();
  22. new Thread() {
  23. @Override
  24. public void run() {
  25. while (true) {
  26. try {
  27. p.print2();
  28. } catch (Exception e) {
  29. e.printStackTrace();
  30. }
  31. }
  32. }
  33. }.start();
  34.  
  35. new Thread() {
  36. @Override
  37. public void run() {
  38. while (true) {
  39. try {
  40. p.print3();
  41. } catch (Exception e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }
  46. }.start();
  47. }
  48.  
  49. }
  50.  
  51. class Printer1121 {
  52. private int flag = 1;
  53.  
  54. public void print1() throws Exception {
  55. synchronized (this) {
  56. while (flag != 1) {
  57. this.wait();
  58. }
  59. Thread.sleep(100);
  60. System.out.print(1);
  61. System.out.print(2);
  62. System.out.print(3);
  63. System.out.print(4);
  64. System.out.print(5);
  65. System.out.println();
  66. flag = 2;
  67. this.notifyAll();
  68. }
  69. }
  70.  
  71. public void print2() throws Exception {
  72. synchronized (this) {
  73. while (flag != 2) {
  74. this.wait();
  75. }
  76. Thread.sleep(100);
  77. System.out.print("a");
  78. System.out.print("b");
  79. System.out.print("c");
  80. System.out.print("d");
  81. System.out.print("e");
  82. System.out.println();
  83. flag = 3;
  84. this.notifyAll();
  85. }
  86. }
  87.  
  88. public void print3() throws Exception {
  89. synchronized (this) {
  90. while (flag != 3) {
  91. this.wait();
  92. }
  93. Thread.sleep(100);
  94. System.out.print("A");
  95. System.out.print("B");
  96. System.out.print("C");
  97. System.out.print("D");
  98. System.out.print("E");
  99. System.out.println();
  100. flag = 1;
  101. this.notifyAll();
  102. }
  103. }
  104. }

      • C 这里进行下原因分析,为什么会出现这样的情况?首先wait方法在同步代码块里被调用了,那么此时调用者直接在wait处等待了,然后等待下次被notify或者notifyAll唤醒。而if选择结构在判断一次之后就顺序执行了,当线程被唤醒时,我们希望的是再次判断一次条件看能够继续进行,但是if无法做到,因为上次已经判断正确了,它只会向下继续执行此时就会又出现随意无规律交替运行,但是while是循环判断,即使判断过一次了,但是每次执行完它会再次判断,这时就会让三个线程的运行结果有规律了。

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇高质量的博文)的更多相关文章

  1. Java 多线程基础(五)线程同步

    Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...

  2. Java多线程基础:进程和线程之由来

    转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...

  3. Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会有解决很多问题]生产者消费者模型

    http://blog.csdn.net/a352193394/article/details/39503857  Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会 ...

  4. Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier

    Java多线程编程-(1)-线程安全和锁Synchronized概念 Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性 Java多线程编程-(3)-从一个错误的双重校验锁 ...

  5. “全栈2019”Java多线程第十三章:线程组ThreadGroup详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  6. “全栈2019”Java多线程第十一章:线程优先级详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. “全栈2019”Java多线程第九章:判断线程是否存活isAlive()详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  8. “全栈2019”Java多线程第五章:线程睡眠sleep()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  9. 1、Java多线程基础:进程和线程之由来

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

随机推荐

  1. Python线性表——单链表

    1. 线性表简介 线性表是一种线性结构,它是由零个或多个数据元素构成的有限序列.线性表的特征是在一个序列中,除了头尾元素,每个元素都有且只有一个直接前驱,有且只有一个直接后继,而序列头元素没有直接前驱 ...

  2. [BZOJ1045][HAOI2008]糖果传递 (环形均分纸牌)

    题意 有n个小朋友坐成一圈,每人有ai个糖果.每人只能给左右两人传递糖果.每人每次传递一个糖果代价为1. 思路 把|s[i]-s[k]|求和即可,s[i]是A的前缀和 s[k]为s数组的中位数时,总值 ...

  3. c#学习个人总结

    c#一门程序语言,我的专业课.有了一学期的接触对它的了解还只是皮毛,里面有许多的知识都有待了解学习和提高,熟练掌握程序的语法,能够熟练的调用,对于一些理论性的知识还需要今后更多的揣摩和研究,讲书本中的 ...

  4. CSS3_线性渐变_径向渐变----背景

    渐变的本质: 绘制一张背景图片,所以使用 background 或者 background-image background 的诸多属性,渐变都是可以使用的(repeat,position) 百分比: ...

  5. andorid简易定位

    权限: <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></use ...

  6. L1-016 查验身份证 (15 分)【考细心,考flag设置】

    一个合法的身份证号码由17位地区.日期编号和顺序编号加1位校验码组成.校验码的计算规则如下: 首先对前17位数字加权求和,权重分配为:{7,9,10,5,8,4,2,1,6,3,7,9,10,5,8, ...

  7. querySelector与getElementBy的区别

    1,querySelector() 方法返回匹配指定 CSS 选择器元素的第一个子元素 . 该方法只返回匹配指定选择器的第一个元素.如果要返回所有匹配元素,需要使用 querySelectorAll( ...

  8. RoR - Action Pack

    什么是Action Pack: Rest and Rails: Rest if all about resources rake routes:  print out all routes

  9. 解决nohup中不写入日志的问题

    (一)问题描述: nohup 你的程序命令 如: nohup python manage.py runserver 0.0.0.0:6789   (此shell窗口1不要关,另外开一个shell窗口2 ...

  10. 如何查看正在执行sql的语句及其父语句调用?如何查看正在执行SQL的具体参数值与执行计划?

    ---SQL Server查询正在执行的SQL语句及执行计划 select ds.session_id,dr.start_time,db_name(dr.database_id),dr.blockin ...