Java中的线程--线程的互斥与同步通信

  Java中的线程之前也提到过,但是还是想再详细的学习一下,跟着张孝祥老师,系统的再学习一下。

一、线程中的互斥

线程安全中的问题解释:线程安全问题可以用银行中的转账

例题描述:

线程A与线程B分别访问同一个对象的方法,这样就会存在线程安全的问题,方法的作用是打印出字符串中的每一个字符,方法如下:

1 public void output(String name) {
2   int len = name.length();
3   for (int i = 0; i < len; i++) {
4      System.out.print(name.charAt(i));
5   }
6   System.out.println();
7 }

线程A和线程B代码如下:(直接写在了init()方法中了)

 1     private void init() {
 2         outputer outputer = new outputer();
 3         new Thread(new Runnable() {
 4             @Override
 5             public void run() {
 6                 while (true) {
 7                     try {
 8                         Thread.sleep(10);
 9                     } catch (InterruptedException e) {
10                         e.printStackTrace();
11                     }
12                     outputer.output("songshengchao");
13                 }
14             }
15         }).start();
16 
17         new Thread(new Runnable() {
18             @Override
19             public void run() {
20                 while (true) {
21                     try {
22                         Thread.sleep(10);
23                     } catch (InterruptedException e) {
24                         e.printStackTrace();
25                     }
26                     outputer.output("songxiaochao");
27                 }
28             }
29         }).start();
30     }

测试一下,肯定会出现线程不安全的问题,这是母庸质疑的事实,测试代码如下:

1     public static void main(String[] args) {
2         new TraditionalThreadSynchronized().init();
3     }

三种解决办法,代码如下:

 1 public class outputer {
 2         public void output(String name) {
 3             int len = name.length();
 4             synchronized (this) { // 传进来当前调用方法的对象,要求线程用的是同一个对象
 5             //synchronized (outputer.class) { // 这样和outputer3方法达到线程互斥
 6                 for (int i = 0; i < len; i++) {
 7                     System.out.print(name.charAt(i));
 8                 }
 9                 System.out.println();
10             }
11         }
12 
13         // 方法上的锁对象用的就是this当前对象
14         public synchronized void output2(String name) {
15             int len = name.length();
16             for (int i = 0; i < len; i++) {
17                 System.out.print(name.charAt(i));
18             }
19             System.out.println();
20         }
21 
22         // output3 想和output方法达到线程互斥
23         public static synchronized void output3(String name) {
24             int len = name.length();
25             for (int i = 0; i < len; i++) {
26                 System.out.print(name.charAt(i));
27             }
28             System.out.println();
29         }
30     }

注意:至于第三种静态的synchronized方法,在和第一个方法共用时,第一种方法一定是用当前对象的class字节码文件,才能确保两个方法用的同一个对象。

 二、线程互斥与通信的经典面试题

面试题:子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着在主线程循环100次,如此循环50次,程序如何写???

经验之谈,设计思想,设计思路:

要用到共同数据(包括同步锁)的若干个方法应该归在同一个类上,这种设计正好提现了程序的高内聚与健壮性

思路:先写主线程与子线程的循环,然后在考虑轮流执行。先考虑循环,代码如下:

 1 public class TraditionalThreadCommunication {
 2 
 3     public static void main(String[] args) {
 4 
 5         new Thread(new Runnable() {
 6 
 7             @Override
 8             public void run() {
 9                 for (int i = 1; i <= 50; i++) {
10                     synchronized (TraditionalThreadCommunication.class) {
11                         for (int j = 1; j <= 10; j++) {
12                             System.out.println("sub thread sequece of" + j + ", loop of " + i);
13                         }
14                     }
15 
16                 }
17             }
18         }).start();
19 
20         // 本身main方法就是主线程,直接可以写循环代码
21         for (int i = 1; i <= 50; i++) {
22             synchronized (TraditionalThreadCommunication.class) {
23                 for (int j = 1; j <= 100; j++) {
24                     System.out.println("main thread sequece of" + j + ", loop of " + i);
25                 }
26             }
27         }
28 
29     }
30 }

代码优化,用面向对象的思想,将那些代码放到一个公共的类中,然后执行类中的不同方法,优化成一个公共的类,代码如下:

 1 public class Business {
 2 
 3     public synchronized void sub(int i){
 4         for (int j = 1; j <= 10; j++) {
 5             System.out.println("sub thread sequece of" + j + ", loop of " + i);
 6         }
 7     }
 8     
 9     public synchronized void main(int i){
10         for (int j = 1; j <= 100; j++) {
11             System.out.println("main thread sequece of" + j + ", loop of " + i);
12         }
13     }
14 }
15 
16 ----------------------------------------------------------------------------------------------
17 
18 public class TraditionalThreadCommunication {
19 
20     
21     public static void main(String[] args) {
22         Business business = new Business();
23         new Thread(new Runnable() {
24 
25             @Override
26             public void run() {
27                 for (int i = 1; i <= 50; i++) {
28                     business.sub(i);
29                 }
30             }
31         }).start();
32 
33         // 本身main方法就是主线程,直接可以写循环代码
34         for (int i = 1; i <= 50; i++) {
35             business.main(i);
36         }
37 
38     }
39 
40 }

最终的完整代码如下(详细注释):

 1 public class Business {
 2 
 3     // 是否是子线程执行 默认子线程先执行
 4     private boolean bShouldSub = true;
 5 
 6     public synchronized void sub(int i) {
 7         // 不是子线程应该执行 让给主线程 子线程执行等待的方法
 8         while (!bShouldSub) {
 9             try {
10                 this.wait();
11             } catch (InterruptedException e) {
12                 e.printStackTrace();
13             }
14         }
15         for (int j = 1; j <= 10; j++) {
16             System.out.println("sub thread sequece of" + j + ", loop of " + i);
17         }
18         // 子线程执行完毕后 让给主线程执行
19         bShouldSub = false;
20         // 唤醒主线程
21         this.notify();
22     }
23 
24     public synchronized void main(int i) {
25         // 是子线程应该执行 让给子线程执行 主线程执行等待的方法
26         while (bShouldSub) {
27             try {
28                 this.wait();
29             } catch (InterruptedException e) {
30                 e.printStackTrace();
31             }
32         }
33         for (int j = 1; j <= 100; j++) {
34             System.out.println("main thread sequece of" + j + ", loop of " + i);
35         }
36         // 主线程执行费完毕后 交给子线程执行
37         bShouldSub = true;
38         // 唤醒子线程
39         this.notify();
40     }
41 }
42 ------------------------------------------------------------------------------------------------
43 
44 public class TraditionalThreadCommunication {
45 
46     public static void main(String[] args) {
47         Business business = new Business();
48         new Thread(new Runnable() {
49 
50             @Override
51             public void run() {
52                 for (int i = 1; i <= 50; i++) {
53                     business.sub(i);
54                 }
55             }
56         }).start();
57 
58         // 本身main方法就是主线程,直接可以写循环代码
59         for (int i = 1; i <= 50; i++) {
60             business.main(i);
61         }
62 
63     }
64 
65 }
原文地址:https://www.cnblogs.com/ssh-html/p/10940790.html