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

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


  • 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其余博客主的博客,而且将我我的的理解也分享给你们,本人的水平有限,若是个人分析或者结论有错误但愿你们必定要帮我指出来,我好能改正和提升

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


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

<一>线程同步锁的选择

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

2. 以上推荐的博文是以卖火车票为例,引出了非同步会致使的错误以及同步锁(监视器)应该若是选择,应该可以帮助你们理解同步锁。java


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

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

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

 

<三>对线程同步的理解

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

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

 

<四>同步的做用场景

1. 并非说同步在什么状况下都是好的,由于线程的同步会带来较低效率,由于线程同步就表明着线程要排队,即线程同步锁会带来的同步阻塞状态。this

2. 由于CPU是随意切换线程的,当咱们想让当前线程执行以后CPU不随意切换到其余线程,或者咱们想要让某个线程的代码可以在彻底执行以前不会被抢夺执行权,不会致使从而没法连续执行,那么咱们就须要线程的帮助。spa

 

2、线程同步和线程通讯的几个小细节


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

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

    1.sleep方法的基本用法.net

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是循环判断,即便判断过一次了,可是每次执行完它会再次判断,这时就会让三个线程的运行结果有规律了。
相关文章
相关标签/搜索