多线程

Java.Thread

程序是指令和数据的有序集合,其本身没有任何含义,是一个静态的概念

进程则是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位

通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义

线程是CPU调度和执行的单位

真正的多进程是多个CPU,即多核

如果是模拟的多线程,则在一个CPU情况下,在同一个时间点CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

 

核心概念

  • 线程就是独立的执行路径

  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程

  • main()称为主线程,为系统的入口,用于执行整个程序

  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相连的,先后顺序是不能人为干预的

  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发机制

  • 线程会带来额外的开销,如CPU调度时间,并发控制开销

  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

普通方法调用:只有一条主线程一条执行路径

多条执行路径:主线程和子线程并行交替执行

线程创建

Thread、Runnable、Callable

继承Thread类(重点)

实现Runnable接口(重点)

实现Callable接口(了解)

Thread

自定义线程类继承Thread类

重写run()方法,编写线程执行体

创建线程对象,调用start()方法启动线程

 1 package Thread;
 2 
 3 import OOP.Exception.Test;
 4 
 5 //创建线程方式1:继承Thread类,重写run()方法,调用start开启线程
 6 
 7 //总结:线程开启不一定立即执行,由CPU调度执行
 8 public class TestThread1 extends Thread{
 9     @Override
10     public void run() {
11         //run方法线程体
12         for (int i = 0; i < 2; i++) {
13             System.out.println("loading……");
14         }
15     }
16 
17     public static void main(String[] args) {
18         //main线程,主线程
19 
20         //创建一个线程对象
21         TestThread1 testThread1 = new TestThread1();
22 
23         //调用start方法开启线程
24         testThread1.start();
25 
26         //调用
27         for (int i = 0; i < 200; i++) {
28             System.out.println("主线程--"+i);
29         }
30     }
31 }
32 /*Output:
33 ……
34 主线程--71
35 主线程--72
36 主线程--73
37 loading……
38 loading……
39 主线程--74
40 主线程--75
41 主线程--76
42 主线程--77
43 主线程--78
44 主线程--79
45 主线程--80
46 ……
47  */

下载图片示例:

 1 package Thread;
 2  3 import org.apache.commons.io.FileUtils;
 4  5 import java.io.File;
 6 import java.io.IOException;
 7 import java.net.URL;
 8  9 //实现多线程同步下载图片
10 public class TestThread2 extends Thread{
11 12     private String url;//网络图片地址
13     private String name;//保存的文件名
14 15     public TestThread2(String url, String name) {
16         this.url = url;
17         this.name = name;
18     }
19 20     @Override
21     public void run() {
22         WebDownloader webDownloader = new WebDownloader();
23         webDownloader.downloader(url,name);
24         System.out.println("下载了文件名为:"+name);
25     }
26 27     public static void main(String[] args) {
28         TestThread2 t1 = new TestThread2("https://m4.publicimg.browser.qq.com/publicimg/pcqb/newtab_yunying/477x266_lssh_20210330.jpg","1.jpg");
29         TestThread2 t2 = new TestThread2("https://m4.publicimg.browser.qq.com/publicimg/pcqb/newtab_yunying/272X133_lssh.jpg","2.jpg");
30         TestThread2 t3 = new TestThread2("https://m4.publicimg.browser.qq.com/publicimg/pcqb/little_dldl.png","3.jpg");
31 32         t1.start();
33         t2.start();
34         t3.start();
35         /*Output:
36         下载了文件名为:2.jpg
37         下载了文件名为:3.jpg
38         下载了文件名为:1.jpg
39          */
40         //没有按照1-2-3的顺序,同时执行,大小较小的先下载完成,实现了多线程
41     }
42 }
43 44 class WebDownloader{
45     public void downloader(String url,String name){
46         //Commons IO是针对开发IO流功能的工具类库,网上下载Commons IP 2.8 导入lib库内
47         //FileUtils文件工具,复制utl到文件
48         try {
49             FileUtils.copyURLToFile(new URL(url),new File(name));
50         } catch (IOException e) {
51             e.printStackTrace();
52             System.out.println("IO异常,Downloader方法出现问题");
53         }
54     }
55 }

Runnable

定义MyRunnable类实现Runnable接口

实现run()方法,编写线程执行体

创建线程对象,调用start()方法启动线程

技巧,重写接口方法快捷键Ctrl+O(Override)

 1 package Thread;
 2  3 //创建线程方式2:实现Runnable接口,重写run方法,执行线程需要丢入Runnable接口实现类,调用start方法
 4 public class TestThread3 implements Runnable{
 5     @Override
 6     public void run() {
 7         //run方法执行体
 8         for (int i = 0; i < 10; i++) {
 9             System.out.println("Thread");
10         }
11     }
12 13     public static void main(String[] args) {
14         //创建Runnable接口的实现类对象
15         TestThread3 testThread3 = new TestThread3();
16         //创建线程代理对象,通过线程对象来开启线程
17 18         new Thread(testThread3).start();
19 //        等效于
20 //        Thread thread = new Thread(testThread3);
21 //        thread.start();
22 23         for (int i = 0; i < 1000; i++) {
24             System.out.println("loading...");
25         }
26     }
27 }
28 /*
29 Output:
30 ...
31 loading...
32 loading...
33 Thread
34 Thread
35 Thread
36 Thread
37 Thread
38 Thread
39 Thread
40 Thread
41 Thread
42 Thread
43 loading...
44 loading...
45 ...
46  */

下载图片示例:

 1 package Thread;
 2  3 import org.apache.commons.io.FileUtils;
 4  5 import java.io.File;
 6 import java.io.IOException;
 7 import java.net.URL;
 8  9 //实现多线程同步下载图片
10 public class TestThread2_Runnable implements Runnable{
11 12     private String url;//网络图片地址
13     private String name;//保存的文件名
14 15     public TestThread2_Runnable(String url, String name) {
16         this.url = url;
17         this.name = name;
18     }
19 20     @Override
21     public void run() {
22         WebDownloader1 webDownloader1 = new WebDownloader1();
23         webDownloader1.downloader(url,name);
24         System.out.println("下载了文件名为:"+name);
25     }
26 27     public static void main(String[] args) {
28         TestThread2 t1 = new TestThread2("https://m4.publicimg.browser.qq.com/publicimg/pcqb/newtab_yunying/477x266_lssh_20210330.jpg","1.jpg");
29         TestThread2 t2 = new TestThread2("https://m4.publicimg.browser.qq.com/publicimg/pcqb/newtab_yunying/272X133_lssh.jpg","2.jpg");
30         TestThread2 t3 = new TestThread2("https://m4.publicimg.browser.qq.com/publicimg/pcqb/little_dldl.png","3.jpg");
31 32         new Thread(t1).start();
33         new Thread(t2).start();
34         new Thread(t3).start();
35         /*Output:
36         下载了文件名为:2.jpg
37         下载了文件名为:3.jpg
38         下载了文件名为:1.jpg
39          */
40         //没有按照1-2-3的顺序,同时执行,大小较小的先下载完成,实现了多线程
41     }
42 }
43 44 class WebDownloader1{
45     public void downloader(String url,String name){
46         //Commons IO是针对开发IO流功能的工具类库,网上下载Commons IP 2.8 导入lib库内
47         //FileUtils文件工具,复制utl到文件
48         try {
49             FileUtils.copyURLToFile(new URL(url),new File(name));
50         } catch (IOException e) {
51             e.printStackTrace();
52             System.out.println("IO异常,Downloader方法出现问题");
53         }
54     }
55 }

小结:

  • 继承Thread类

    • 子类继承Thread类具备多线程能力

    • 启动线程:子类对象.start()

    • 不建议使用:避免OOP单继承局限性,类的单继承

  • 实现Runnable接口

    • 实现接口Runnable具有多线程能力

    • 启动线程:传入目标对象+Thread对象.start()

    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用,一份资源可以提供给多个代理,接口的多继承

龟兔赛跑测试

  1 package Thread;
  2   3 //龟兔赛跑
  4 public class Race implements Runnable{
  5     //胜利者
  6     private static String winner;
  7   8     @Override
  9     public void run() {
 10         for (int i = 0; i <= 100; i++) {
 11             //模拟兔子休息
 12             if(Thread.currentThread().getName().equals("兔子")&&i%20==0){
 13                 try {
 14                     Thread.sleep(1);
 15                 } catch (InterruptedException e) {
 16                     e.printStackTrace();
 17                 }
 18             }
 19  20             //判断比赛是否结束
 21             boolean flag = gameOver(i);
 22             //如果比赛结束了,就停止程序
 23             if (flag){
 24                 break;
 25             }
 26             //兔子速度更快
 27             if (Thread.currentThread().getName().equals("兔子")){
 28                 System.out.println("兔子跑了"+2*i+"步");
 29             }else{
 30                 System.out.println("乌龟跑了"+i+"步");
 31             }
 32         }
 33     }
 34  35     //判断是否完成比赛
 36     private boolean gameOver(int steps){
 37         if (winner!=null){
 38             return true;
 39         }
 40         if(steps>=100){
 41             winner = Thread.currentThread().getName();
 42             System.out.println("winner is "+winner);
 43             return true;
 44         }
 45         return false;
 46     }
 47  48     public static void main(String[] args) {
 49         Race race = new Race();
 50         new Thread(race,"兔子").start();
 51         new Thread(race,"乌龟").start();
 52     }
 53 }
 54 /*
 55 Output:
 56 乌龟跑了0步
 57 乌龟跑了1步
 58 乌龟跑了2步
 59 乌龟跑了3步
 60 乌龟跑了4步
 61 乌龟跑了5步
 62 乌龟跑了6步
 63 乌龟跑了7步
 64 乌龟跑了8步
 65 乌龟跑了9步
 66 乌龟跑了10步
 67 乌龟跑了11步
 68 乌龟跑了12步
 69 乌龟跑了13步
 70 乌龟跑了14步
 71 乌龟跑了15步
 72 乌龟跑了16步
 73 乌龟跑了17步
 74 乌龟跑了18步
 75 乌龟跑了19步
 76 乌龟跑了20步
 77 乌龟跑了21步
 78 乌龟跑了22步
 79 乌龟跑了23步
 80 乌龟跑了24步
 81 乌龟跑了25步
 82 乌龟跑了26步
 83 乌龟跑了27步
 84 乌龟跑了28步
 85 乌龟跑了29步
 86 乌龟跑了30步
 87 乌龟跑了31步
 88 乌龟跑了32步
 89 乌龟跑了33步
 90 乌龟跑了34步
 91 乌龟跑了35步
 92 乌龟跑了36步
 93 乌龟跑了37步
 94 乌龟跑了38步
 95 乌龟跑了39步
 96 乌龟跑了40步
 97 乌龟跑了41步
 98 兔子跑了0步
 99 兔子跑了2步
100 兔子跑了4步
101 兔子跑了6步
102 乌龟跑了42步
103 乌龟跑了43步
104 乌龟跑了44步
105 乌龟跑了45步
106 乌龟跑了46步
107 兔子跑了8步
108 兔子跑了10步
109 兔子跑了12步
110 乌龟跑了47步
111 兔子跑了14步
112 乌龟跑了48步
113 兔子跑了16步
114 兔子跑了18步
115 兔子跑了20步
116 兔子跑了22步
117 兔子跑了24步
118 乌龟跑了49步
119 兔子跑了26步
120 兔子跑了28步
121 兔子跑了30步
122 兔子跑了32步
123 兔子跑了34步
124 兔子跑了36步
125 兔子跑了38步
126 乌龟跑了50步
127 乌龟跑了51步
128 乌龟跑了52步
129 乌龟跑了53步
130 乌龟跑了54步
131 乌龟跑了55步
132 乌龟跑了56步
133 乌龟跑了57步
134 乌龟跑了58步
135 乌龟跑了59步
136 乌龟跑了60步
137 乌龟跑了61步
138 乌龟跑了62步
139 乌龟跑了63步
140 乌龟跑了64步
141 乌龟跑了65步
142 乌龟跑了66步
143 乌龟跑了67步
144 乌龟跑了68步
145 乌龟跑了69步
146 乌龟跑了70步
147 乌龟跑了71步
148 乌龟跑了72步
149 乌龟跑了73步
150 乌龟跑了74步
151 乌龟跑了75步
152 乌龟跑了76步
153 乌龟跑了77步
154 乌龟跑了78步
155 乌龟跑了79步
156 乌龟跑了80步
157 乌龟跑了81步
158 乌龟跑了82步
159 乌龟跑了83步
160 乌龟跑了84步
161 乌龟跑了85步
162 乌龟跑了86步
163 乌龟跑了87步
164 乌龟跑了88步
165 乌龟跑了89步
166 兔子跑了40步
167 兔子跑了42步
168 兔子跑了44步
169 兔子跑了46步
170 兔子跑了48步
171 兔子跑了50步
172 兔子跑了52步
173 兔子跑了54步
174 兔子跑了56步
175 兔子跑了58步
176 兔子跑了60步
177 兔子跑了62步
178 兔子跑了64步
179 兔子跑了66步
180 兔子跑了68步
181 兔子跑了70步
182 乌龟跑了90步
183 乌龟跑了91步
184 兔子跑了72步
185 乌龟跑了92步
186 兔子跑了74步
187 兔子跑了76步
188 兔子跑了78步
189 乌龟跑了93步
190 乌龟跑了94步
191 乌龟跑了95步
192 乌龟跑了96步
193 乌龟跑了97步
194 乌龟跑了98步
195 乌龟跑了99步
196 winner is 乌龟
197 */

实现Callable接口

 1 package Thread;
 2  3 import org.apache.commons.io.FileUtils;
 4  5 import java.io.File;
 6 import java.io.IOException;
 7 import java.net.URL;
 8 import java.util.concurrent.*;
 9 10 //线程创建方式:实现Callable接口
11 //好处:可以定义返回值,可以抛出异常
12 public class TestCallable implements Callable<Boolean>{
13     private String url;//网络图片地址
14     private String name;//保存的文件名
15 16     public TestCallable(String url, String name) {
17         this.url = url;
18         this.name = name;
19     }
20 21     @Override
22     public Boolean call() {
23         WebDownloader webDownloader = new WebDownloader();
24         webDownloader.downloader(url,name);
25         System.out.println("下载了文件名为:"+name);
26         return true;
27     }
28 29     public static void main(String[] args) throws ExecutionException, InterruptedException {
30         TestThread2 t1 = new TestThread2("https://m4.publicimg.browser.qq.com/publicimg/pcqb/newtab_yunying/477x266_lssh_20210330.jpg","1.jpg");
31         TestThread2 t2 = new TestThread2("https://m4.publicimg.browser.qq.com/publicimg/pcqb/newtab_yunying/272X133_lssh.jpg","2.jpg");
32         TestThread2 t3 = new TestThread2("https://m4.publicimg.browser.qq.com/publicimg/pcqb/little_dldl.png","3.jpg");
33 34         //创建执行服务
35         ExecutorService ser = Executors.newFixedThreadPool(3);
36 37         //提交执行
38         Future<Boolean> r1 = ser.submit(t1);
39         Future<Boolean> r2 = ser.submit(t2);
40         Future<Boolean> r3 = ser.submit(t3);
41 42         //获取结果
43         boolean rs1 = r1.get();
44         boolean rs2 = r2.get();
45         boolean rs3 = r3.get();
46 47         System.out.println(rs1);
48         System.out.println(rs2);
49         System.out.println(rs3);
50 51         //关闭服务
52         ser.shutdownNow();
53 54         /*Output:
55         下载了文件名为:2.jpg
56         下载了文件名为:3.jpg
57         下载了文件名为:1.jpg
58          */
59         //没有按照1-2-3的顺序,同时执行,大小较小的先下载完成,实现了多线程
60     }
61 }

静态代理

 1 interface Marry{
 2     void HappyMarry();
 3 }
 4  5 //真实角色,去结婚
 6 class You implements Marry{
 7     @Override
 8     public void HappyMarry() {
 9         System.out.println("要结婚了,开心!");
10     }
11 }
12 13 //代理角色,帮助结婚,实现类
14 class WeddingCompany implements Marry{
15     //代理的目标角色
16     private Marry target;
17 18     public WeddingCompany(Marry target) {
19         this.target = target;
20     }
21 22     @Override
23     public void HappyMarry() {
24         before();//代理可以做的事情
25         this.target.HappyMarry();//真实对象You
26         after();//代理可以做真实对象不做的事情
27     }
28 29     private void before(){
30         System.out.println("结婚之前,筹备婚礼");
31     }
32 33     private void after(){
34         System.out.println("结婚之后,收尾款");
35     }
36 }
37 38 //静态代理模式总结:
39 //真实对象和代理对象都要实现同一个接口
40 //代理对象要代理真实角色You
41 //好处:代理对象可以做很多真实对象做不了的事情,真实对象专注做自己的事情
42 public class staticProxy {
43     public static void main(String[] args) {
44         You you = new You();//你要结婚
45 46         WeddingCompany weddingCompany = new WeddingCompany(you);
47         weddingCompany.HappyMarry();
48     }
49 

Lambda表达式(JDK8新增特性)

避免匿名内部类过多,让代码看起来更简洁,去掉了没有意义的代码,只留下了核心的逻辑

其实质属于函数式编程的概念

(params)->expression[表达式]

(params)->statement[语句]

(params)->{statements}

new Thread(()->System.out.println("多线程学习")).start();

 

函数式接口:任何接口,只包含唯一一个抽象方法

如public interface Runnable{

public abstract void run();

}

对于函数式接口,可以通过Lambda表达式来创建该接口的对象

Runnable只有一个run方法,可以使用Lambda方法简化

逐渐简化的过程:

 1 //推导lambda表达式
 2 public class TestLambda1 {
 3     //3.静态内部类
 4     static class Like2 implements ILike{
 5         @Override
 6         public void lambda() {
 7             System.out.println("I like lambda2");
 8         }
 9     }
10 11     public static void main(String[] args) {
12         ILike like = new Like();
13         like.lambda();
14 15         like = new Like2();
16         like.lambda();
17 18         //4.局部内部类
19         class Like3 implements ILike{
20             @Override
21             public void lambda() {
22                 System.out.println("I like lambda3");
23             }
24         }
25 26         like = new Like3();
27         like.lambda();
28 29         //5.匿名内部类,没有类的名称,必须借助接口或者父类
30         like = new ILike() {
31             @Override
32             public void lambda() {
33                 System.out.println("I like lambda4");
34             }
35         };
36         like.lambda();
37 38         //6.用lambda简化(JDK8之后)
39         like = ()->{
40             System.out.println("I like lambda5");
41         };
42         like.lambda();
43     }
44 }
45 //1.定义一个函数式接口
46 interface ILike{
47     void lambda();//只包含一个抽象方法
48 }
49 //2.实现类
50 class Like implements ILike{
51     @Override
52     public void lambda() {
53         System.out.println("I like Lambda1");
54     }
55 }
56 public class TestLambda2 {
57     public static void main(String[] args) {
58         //1.lambda表示,简化实现类
59         ILove love = (int a)->{
60             System.out.println("I love you-->"+a);
61         };
62         love.love(520);
63 64         //简化1.参数类型可以去掉,多个参数要去掉就都去掉,多个参数加括号,参数类型不同也可以
65         love = (a)->{
66             System.out.println("I love you-->"+a);
67         };
68         love.love(520);
69 70         //简化2.简化括号
71         love = a->{
72             System.out.println("I love you-->"+a);
73         };
74         love.love(520);
75 76         //简化3.去掉花括号(只有1行代码时)
77         love = a-> System.out.println("I love you-->"+a);
78         love.love(520);
79     }
80 }
81 82 //简化前提:函数式接口 ,只能有一个方法
83 interface ILove{
84     void love(int a);
85 }

线程状态

方法说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程(尽量别使用)
boolean isAlive() 测试线程是否处于活动状态

停止线程

不推荐使用JDK提供的stop()、destroy()方法,已废弃

推荐线程自己停止下来

建议使用一个标志flag为终止变量。当flag=false,则终止线程进行

 1 //测试stop
 2 //1.建议线程正常停止-->利用次数,不建议死循环
 3 //2.建议使用标志位-->设置一个标志位
 4 //3.不要使用stop或者destroy等过时的或者JDK不建议使用的方法
 5  6 public class TestStop implements Runnable{
 7     //1.设置一个标识位
 8     private boolean flag = true;
 9     
10     @Override
11     public void run() {
12         int i = 0;
13         while (flag){
14             System.out.println("Loading...Thread"+i++);
15         }
16     }
17 18     //2.设置一个公开的方法停止线程,转换标志位
19     public void stop(){
20         this.flag = false;
21     }
22 23     public static void main(String[] args) {
24         TestStop testStop = new TestStop();
25 26         new Thread(testStop).start();
27 28         for (int i = 0; i < 1000; i++) {
29             System.out.println("main"+i);
30             if (i==900){
31                 //调用stop方法切换标志位,让线程停止
32                 testStop.stop();
33                 System.out.println("线程该停止了");
34             }
35         }
36     }
37 }

线程休眠sleep

  • sleep(时间)指定当前线程阻塞的毫秒数

  • sleep存在异常InterruptException

  • sleep时间达到后线程进入就绪状态

  • sleep可以模拟网络延时,倒计时等

  • 每一个对象都有一个锁,sleep不会释放锁

演示:计时

技巧:sleep需要检测抛出异常,快捷键Ctrl+Alt+T选择

抢票:

 1 //模拟网络延时
 2 public class TestSleep implements Runnable{
 3     //票数
 4     private int ticketNums = 10;
 5 
 6     @Override
 7     public void run() {
 8         while (true){
 9             if (ticketNums<=0){
10                 break;
11             }
12             //模拟延时
13             try {
14                 Thread.sleep(200);
15             } catch (InterruptedException e) {
16                 e.printStackTrace();
17             }
18 
19             System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"票");
20         }
21     }
22 
23     public static void main(String[] args) {
24         TestSleep ticket = new TestSleep();
25 
26         new Thread(ticket,"张三").start();
27         new Thread(ticket,"李四").start();
28         new Thread(ticket,"王五").start();
29     }
30 }

 

//倒计时10s:

获取当地时间:

 1 import java.text.SimpleDateFormat;
 2 import java.util.Date;
 3  4 //模拟倒计时
 5 public class TestSleep2 {
 6     public static void main(String[] args) {
 7 //
 8 //        try {
 9 //            tenDown();
10 //        } catch (InterruptedException e) {
11 //            e.printStackTrace();
12 //        }
13         //打印当前系统时间
14         Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间
15 16             while (true){
17             try {
18                 Thread.sleep(1000);
19                 System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
20                 startTime = new Date(System.currentTimeMillis());//更新当前时间
21             } catch (InterruptedException e) {
22                 e.printStackTrace();
23             }
24         }
25     }
26 27 //    public static void tenDown() throws InterruptedException{
28 //        int num = 10;
29 //        while (true){
30 //            Thread.sleep(1000);
31 //            System.out.println(num--);
32 //            if (num<=0){
33 //                break;
34 //            }
35 //        }
36 //    }
37 }

线程礼让yield

  • 礼让线程,让当前正在执行的线程暂停,但不堵塞

  • 将线程从运行状态转为就绪状态

  • 让CPU重新调度,礼让不一定成功,看CPU心情

 1 public class TestYield {
 2     public static void main(String[] args) {
 3         MyYield myYield = new MyYield();
 4  5         new Thread(myYield,"A").start();
 6         new Thread(myYield,"B").start();
 7     }
 8 }
 9 class MyYield implements Runnable{
10     @Override
11     public void run() {
12         System.out.println(Thread.currentThread().getName()+"线程开始执行");
13         //Thread.yield();//礼让
14         System.out.println(Thread.currentThread().getName()+"线程停止执行");
15     }
16 }
17 /*
18 Output:礼让成功时(yield未注释掉):
19 A线程开始执行
20 B线程开始执行
21 A线程停止执行
22 B线程停止执行
23 礼让不成功时(yield注释掉):
24 A线程开始执行
25 A线程停止执行
26 B线程开始执行
27 B线程停止执行
28  */

join

合并线程,待此线程执行完成后,再执行其他线程,其他线程堵塞

可以想象成插队,其他人只能等

join会使当前线程等待调用join的进程执行完毕后再继续往下执行

 1 //测试join方法//想象为插队
 2 public class TestJoin implements Runnable{
 3     @Override
 4     public void run() {
 5         for (int i = 0; i < 2; i++) {
 6             System.out.println("线程vip来了-->"+i);
 7         }
 8     }
 9 
10     public static void main(String[] args) throws InterruptedException{
11         TestJoin testJoin = new TestJoin();
12         Thread thread = new Thread(testJoin);
13         //主线程
14         for (int i = 0; i < 10; i++) {
15             if (i==5){
16                 //启动插队线程
17                 thread.start();
18                 thread.join();//开始插队
19             }
20             System.out.println("主线程main"+i);
21         }
22     }
23 }
24 /*
25 Output:
26 主线程main0
27 主线程main1
28 主线程main2
29 主线程main3
30 主线程main4
31 线程vip来了-->0
32 线程vip来了-->1
33 主线程main5
34 主线程main6
35 主线程main7
36 主线程main8
37 主线程main9
38  */

线程状态观测state

Thread.state线程状态

线程可以处于以下状态之一:

  • NEW 尚未启动的线程

  • RUNNABLE 在Java虚拟机中执行的线程

  • BLOCKED 被阻塞等待监视器锁定的线程

  • WAITING 正在等待另一个线程执行特定动作的线程

  • TIMED_WAITING 正在等待另一个线程执行动作达到制定等待时间的线程

  • TERMINATED 已退出的线程

一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态

 1 //观察测试线程的状态
 2 public class TestState {
 3     public static void main(String[] args) throws InterruptedException {
 4         Thread thread = new Thread(()->{
 5             for (int i = 0; i < 5; i++) {
 6                 try {
 7                     Thread.sleep(1000);
 8                 } catch (InterruptedException e) {
 9                     e.printStackTrace();
10                 }
11             }
12             System.out.println("///////////////");
13         });
14 15         //观察状态
16         Thread.State state = thread.getState();
17         System.out.println(state);//NEW
18 19         //观察启动后
20         thread.start();
21         state = thread.getState();
22         System.out.println(state);//RUN
23 24         while (state!= Thread.State.TERMINATED){
25             //只要线程不终止,就一直输出状态
26             Thread.sleep(100);
27             state = thread.getState();//更新线程状态
28             System.out.println(state);//输出状态
29         }
30 31         //thread.start();//TERMINATED死亡的进程不能再次启动
32     }
33 }
34 /*
35 Output:
36 NEW
37 RUNNABLE
38 TIMED_WAITING
39 TIMED_WAITING
40 TIMED_WAITING
41 TIMED_WAITING
42 TIMED_WAITING
43 TIMED_WAITING
44 TIMED_WAITING
45 TIMED_WAITING
46 TIMED_WAITING
47 TIMED_WAITING
48 ///////////////
49 TERMINATED
50  */

线程优先级

Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调整哪个线程来执行。

  • 线程的优先级用数字表示,范围从1~10

    • Thread.MIN_PRIORITY = 1;

    • Thread.MAX_PRIORITY = 10;

    • Thread.NORM_PRIORITY = 5;

  • 使用以下方式改变或获取优先级

    • getPriority()、setPriority(int xx)

 1 public class TestPriority {
 2     public static void main(String[] args) {
 3         //主线程默认优先级
 4         System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
 5         MyPriority myPriority = new MyPriority();
 6 
 7         Thread t1 = new Thread(myPriority);
 8         Thread t2 = new Thread(myPriority);
 9         Thread t3 = new Thread(myPriority);
10         Thread t4 = new Thread(myPriority);
11         Thread t5 = new Thread(myPriority);
12         Thread t6 = new Thread(myPriority);
13 
14         //先设置优先级,再启动
15         t1.start();
16 
17         t2.setPriority(1);
18         t2.start();
19 
20         t3.setPriority(4);
21         t3.start();
22 
23         t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY = 10
24         t4.start();
25 
26         t5.setPriority(8);
27         t5.start();
28 
29         t6.setPriority(6);
30         t6.start();
31     }
32 }
33 
34 class MyPriority implements Runnable{
35     @Override
36     public void run() {
37         System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
38     }
39 }
40 /*
41 Output:
42 main-->5
43 Thread-0-->5
44 Thread-2-->4
45 Thread-3-->10
46 Thread-1-->1
47 Thread-4-->8
48 Thread-5-->6
49  */

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了。这都是看CPU的调度

守护进程daemon

线程分为用户线程和守护线程

虚拟机必须确保用户线程执行完毕

虚拟机不用等待守护线程执行完毕

如:后台记录操作日志,监控内存,垃圾回收等待等

 1 //测试守护进程
 2 //上帝守护你
 3 
 4 public class TestDaemon {
 5     public static void main(String[] args) {
 6         God god = new God();
 7         You you = new You();
 8 
 9         Thread thread = new Thread(god);
10         thread.setDaemon(true);//默认是false表示用户线程(正常线程),切换为false表示守护线程
11         thread.start();//上帝守护线程启动
12         new Thread(you).start();//你的用户线程启动
13     }
14 }
15 
16 class God implements Runnable{
17     @Override
18     public void run() {
19         while(true){
20             System.out.println("上帝保佑你");
21         }
22     }
23 }
24 
25 class You implements Runnable{
26     @Override
27     public void run() {
28         for (int i = 0; i < 10; i++) {
29             System.out.println("你一生都开心的活着");
30         }
31     }
32 }
33 /*
34 Output:
35 上帝保佑你
36 上帝保佑你
37 你一生都开心的活着
38 你一生都开心的活着
39 你一生都开心的活着
40 你一生都开心的活着
41 你一生都开心的活着
42 你一生都开心的活着
43 你一生都开心的活着
44 你一生都开心的活着
45 你一生都开心的活着
46 你一生都开心的活着
47 上帝保佑你
48 上帝保佑你
49 上帝保佑你
50 上帝保佑你
51 上帝保佑你
52 上帝保佑你
53 上帝保佑你
54 上帝保佑你
55 上帝保佑你
56 上帝保佑你
57 上帝保佑你
58 上帝保佑你
59 上帝保佑你
60 上帝保佑你
61  */

关闭守护进程需要一定时间,多执行一部分

线程同步

多个线程操作同一个资源

由于同一进程的多个线程共享同一块存储空间 ,在带来方便的同时,也带来了访问冲突的问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:

  • 一个线程持有锁会导致其他所有需要此锁的线程挂起

  • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题

  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题

 

三个不安全案例

不安全买票:

 1 //不安全的买票,线程不安全
 2 public class UnsafeBuyTicket {
 3     public static void main(String[] args) {
 4         BuyTicket station = new BuyTicket();
 5  6         new Thread(station,"张三").start();
 7         new Thread(station,"李四").start();
 8         new Thread(station,"王五").start();
 9     }
10 }
11 12 class BuyTicket implements Runnable{
13     //
14     private int ticketNums = 10;
15     //外部停止方式
16     boolean flag = true;
17 18     @Override
19     public void run() {
20         //买票
21         while(flag){
22             try {
23                 buy();
24             } catch (InterruptedException e) {
25                 e.printStackTrace();
26             }
27         }
28     }
29 30     private void buy() throws InterruptedException {
31         //判断是否有票
32         if(ticketNums<=0){
33             flag = false;
34             return;
35         }
36         //模拟延时
37         Thread.sleep(100);
38         //买票
39         System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
40     }
41 }
42 /*
43 Output:
44 张三拿到10
45 李四拿到8
46 王五拿到9
47 张三拿到6
48 李四拿到7
49 王五拿到5
50 张三拿到4
51 李四拿到4
52 王五拿到3
53 王五拿到2
54 张三拿到0
55 李四拿到1
56  */

不安全取钱

 1 //不安全的取钱
 2 //两个人去取钱,需要两个账户
 3 public class UnsafeBank {
 4     public static void main(String[] args) {
 5         //账户
 6         Account account = new Account(100, "结婚基金");
 7         Drawing you = new Drawing(account,50, "你");
 8         Drawing girlFriend = new Drawing(account,100, "女朋友");
 9         you.start();
10         girlFriend.start();
11     }
12 }
13 14 //账户
15 class Account{
16     int money;//余额
17     String name;//卡名
18 19     public Account(int money, String name) {
20         this.money = money;
21         this.name = name;
22     }
23 }
24 25 //银行:模拟取款
26 class Drawing extends Thread{
27     Account account;//账户
28     //取了多少钱
29     int drawingMoney;
30     //现在手里有多少钱
31     int nowMoney;
32 33     public Drawing(Account account,int drawingMoney,String name){
34         super(name);
35         this.account = account;
36         this.drawingMoney = drawingMoney;
37     }
38 39     //取钱
40 41     @Override
42     public void run() {
43         //判断有没有钱
44         if (account.money-drawingMoney<0){
45             System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
46             return;
47         }
48 49         //卡内余额 = 余额 - 取的钱
50         account.money = account.money - drawingMoney;
51         //手上现有的钱
52         nowMoney = nowMoney + drawingMoney;
53 54         System.out.println(account.name + "余额为:" + account.money);
55 56         //Thread.currentThread().getName() = this.getName()
57         System.out.println(this.getName() + "手里的钱:" + nowMoney);
58         
59         //sleep可以放大问题的发生性
60         try {
61             Thread.sleep(1000);
62         } catch (InterruptedException e) {
63             e.printStackTrace();
64         }
65     }
66 }
67 /*
68 Output:
69 结婚基金余额为:-50
70 结婚基金余额为:-50
71 女朋友手里的钱:100
72 你手里的钱:50
73  */

不安全集合

 1 import java.util.ArrayList;
 2 import java.util.List;
 3 
 4 //线程不安全的集合
 5 public class UnsafeList {
 6     public static void main(String[] args) {
 7         List<String> list = new ArrayList<String>();
 8         for (int i = 0; i < 10000; i++) {
 9             new Thread(()->{
10                 list.add(Thread.currentThread().getName());
11             }).start();
12         }
13         try {
14             Thread.sleep(3000);
15         } catch (InterruptedException e) {
16             e.printStackTrace();
17         }
18         System.out.println(list.size());//Output:9999 两个数组覆盖同一个位置,不安全
19     }
20 }

队列和锁

同步机制

synchronized方法和synchronized块

同步方法:public synchronized void method (int args){}

synchronized方法控制对对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会堵塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

缺陷:若将一个大的方法声明为synchronized将会影响效率

方法里面需要修改的内容才需要锁

锁的太多,浪费资源

修改版买票:

 1 //安全的买票,线程不安全
 2 public class SafeBuyTicket {
 3     public static void main(String[] args) {
 4         BuyTicket station = new BuyTicket();
 5  6         new Thread(station,"张三").start();
 7         new Thread(station,"李四").start();
 8         new Thread(station,"王五").start();
 9     }
10 }
11 12 class BuyTicket implements Runnable{
13     //
14     private int ticketNums = 10;
15     //外部停止方式
16     boolean flag = true;
17 18     @Override
19     public void run() {
20         //买票
21         while(flag){
22             try {
23                 buy();
24                 //模拟延时
25                 Thread.sleep(100);
26             } catch (InterruptedException e) {
27                 e.printStackTrace();
28             }
29         }
30     }
31 32     //synchronized 同步方法,锁的是this
33     private synchronized void buy(){
34         //判断是否有票
35         if(ticketNums<=0){
36             flag = false;
37             return;
38         }
39 40         //买票
41         System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
42     }
43 }
44 /*
45 Output:
46 张三拿到10
47 王五拿到9
48 李四拿到8
49 张三拿到7
50 王五拿到6
51 李四拿到5
52 李四拿到4
53 王五拿到3
54 张三拿到2
55 张三拿到1
56  */

同步块

synchronized(obj){}

obj称之为同步监视器

obj可以是任何对象,但是推荐使用共享资源作为同步监视器

同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class[反射中讲解]

同步监视器的执行过程

  1. 第一个线程访问,锁定同步监视器,执行其中代码

  2. 第二个线程访问,发现同步监视器被锁定,无法访问

  3. 第一个线程访问完毕,解锁同步监视器

  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

参考资料:https://www.cnblogs.com/weibanggang/p/9470718.html

CopyOnWriteArrayList

代替ArrayList,更安全

 1 import java.util.concurrent.CopyOnWriteArrayList;
 2  3 //测试JUC安全类型的集合
 4 public class TestJUC {
 5     public static void main(String[] args) {
 6         CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
 7         for (int i = 0; i < 10000; i++) {
 8             new Thread(()->{
 9                 list.add(Thread.currentThread().getName());
10             }).start();
11         }
12         try {
13             Thread.sleep(3000);
14         } catch (InterruptedException e) {
15             e.printStackTrace();
16         }
17         System.out.println(list.size());//Output:10000
18     }
19 }

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

只要想办法破坏其中的任意一个或多个条件就可以避免死锁的发生

 1 //死锁:多个线程互相抱着对方需要的资源,然后形成僵持
 2 public class DeadLock {
 3     public static void main(String[] args) {
 4         Makeup g1 = new Makeup(0,"灰姑娘");
 5         Makeup g2 = new Makeup(1,"白雪公主");
 6         g1.start();
 7         g2.start();
 8     }
 9 }
10 11 //口红
12 class Lipstick{
13 14 }
15 16 //镜子
17 class Mirror{
18 19 }
20 21 class Makeup extends Thread{
22     //需要的资源只有一份,用static来保证只有一份
23     static Lipstick lipstick = new Lipstick();
24     static Mirror mirror = new Mirror();
25 26     int choice;//选择
27     String girlName;//使用化妆品的人
28 29     Makeup(int choice, String girlName) {
30         this.choice = choice;
31         this.girlName = girlName;
32     }
33 34     @Override
35     public void run() {
36         //化妆
37         try {
38             makeup();
39         } catch (InterruptedException e) {
40             e.printStackTrace();
41         }
42     }
43 44     //化妆,互相持有对方的锁,就是需要拿到对方的资源
45     private void makeup() throws InterruptedException {
46         if(choice==0){
47             synchronized (lipstick){//获得口红的锁
48                 System.out.println(this.girlName+"获得口红的锁");
49                 Thread.sleep(1000);
50 51                 synchronized (mirror){//一秒钟后想获得镜子
52                     System.out.println(this.girlName+"获得镜子的锁");
53                 }
54             }
55         }else {
56             synchronized (mirror){//获得镜子的锁
57                 System.out.println(this.girlName+"获得口红的锁");
58                 Thread.sleep(2000);
59 60                 synchronized (lipstick){//两秒钟后想获得口红
61                     System.out.println(this.girlName+"获得镜子的锁");
62                 }
63             }
64         }
65     }
66 }
67 /*
68 Output:
69 白雪公主获得口红的锁
70 灰姑娘获得口红的锁
71 (此时程序没有停止,而是卡死)72  */
解决方法:嵌套的第二个synchronized挪到第一个synchronized代码块以外,形成独立的两把锁 

Lock(锁)

从JDK 5.0开始,Java提供 了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

Java.util.concurrent.locks

Lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentranLock类实现了Lock,拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中比较常用,可以实现显式加锁、释放锁。

synchronized与Lock的对比

  • Lock是显性锁(手动开启和关闭锁,必须关闭锁)synchronized是隐性锁,除了作用域自动释放

  • Lock只有代码块锁,synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

  • 优先使用顺序:Lock>同步代码块(已经进入方法体,分配了相应资源)>同步方法(在方法体之外)

 1 import java.util.concurrent.locks.ReentrantLock;
 2  3 public class TestLock {
 4     public static void main(String[] args) {
 5         TestLocking testLocking = new TestLocking();
 6         new Thread(testLocking).start();
 7         new Thread(testLocking).start();
 8         new Thread(testLocking).start();
 9     }
10 }
11 12 class TestLocking implements Runnable{
13     int ticketNums = 100;
14 15     //定义Lock锁
16     private final ReentrantLock lock = new ReentrantLock();
17 18     @Override
19     public void run() {
20         while (true) {
21             try {
22                 lock.lock();//加锁
23                 if (ticketNums > 0) {
24                     try {
25                         Thread.sleep(100);
26                     } catch (InterruptedException e) {
27                         e.printStackTrace();
28                     }
29                     System.out.println(ticketNums--);
30                 } else {
31                     break;
32                 }
33             }finally {
34                 lock.unlock();//解锁
35             }
36         }
37     }
38 }

线程协作

生产者消费者模式

应用场景:生产者和消费者问题

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。

  • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。

  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。

分析:

  • 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件 。

  • 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费。

  • 仅有synchronized不够

    • synchronized可阻止并发更新同一个共享资源,实现了同步

    • synchronized不能用来实现不同线程之间的消息传递(通信)

Java提供了几个方法解决线程之间的通信问题

方法名作用
wait() 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个而处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度

均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

解决方式一

并发协作模型“生产者/消费者模式“-->管程法

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程);

  • 消费者:负责处理数据的模块(可能是方法、对象、线程、进程);

  • 缓冲区:消费者不能直接使用生产者的数据,生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

  1 //测试:生产者消费者模型-->利用缓冲区解决:管程法
  2 //进货100只鸡,商店一次只能卖20只,模拟卖光过程
  3 public class TestPC {
  4     public static void main(String[] args) {
  5         SynContainer container = new SynContainer();
  6   7         new Productor(container).start();
  8         new Consumer(container).start();
  9     }
 10 }
 11  12 //生产者
 13 class Productor extends Thread{
 14     SynContainer container;
 15  16     public Productor(SynContainer container) {
 17         this.container = container;
 18     }
 19  20     //生产100只鸡
 21     @Override
 22     public void run() {
 23         for (int i = 0; i < 100; i++) {
 24             container.push(new Chicken(i),i);
 25         }
 26     }
 27 }
 28  29 class Consumer extends Thread{
 30     SynContainer container;
 31  32     public Consumer(SynContainer container) {
 33         this.container = container;
 34     }
 35  36     //消费100只鸡
 37     @Override
 38     public void run() {
 39         for (int i = 0; i < 100; i++) {
 40             container.pop(i);
 41         }
 42     }
 43 }
 44  45 class Chicken{
 46     int id;//产品编号
 47  48     public Chicken(int id) {
 49         this.id = id;
 50     }
 51 }
 52  53 class SynContainer{
 54     //需要一个容器大小,商店可以卖20只鸡
 55     Chicken[] chickens = new Chicken[20];
 56     //容器计数器
 57     int count = 0;
 58  59     //生产者放入产品
 60     public synchronized void push(Chicken chicken, int id){
 61         //如果容器满了,就需要等待消费者消费
 62         if (count==chickens.length){
 63             System.out.print("\n");
 64             //通知消费者消费,生产等待
 65             try {
 66                 this.wait();
 67             } catch (InterruptedException e) {
 68                 e.printStackTrace();
 69             }
 70         }
 71         //如果没有满,我们就需要丢入产品
 72         chickens[count++]=chicken;
 73         System.out.print("+"+ (id+1) +"  ");//+数字 表示生产的鸡的编号
 74         //可以通知消费者消费了
 75         this.notifyAll();
 76     }
 77  78     //消费者消费产品
 79     public synchronized Chicken pop(int id){
 80         //判断能否消费
 81         if (count==0){
 82             System.out.print("\n");
 83             //等待生产者生产,消费者等待
 84             try {
 85                 this.wait();
 86             } catch (InterruptedException e) {
 87                 e.printStackTrace();
 88             }
 89         }
 90         //如果可以消费
 91         Chicken chicken = chickens[--count];
 92         System.out.print("-"+ (id+1) +"  ");//-数字 表示卖掉的鸡的编号
 93         //吃完了,通知生产者生产
 94         this.notifyAll();
 95         return chicken;
 96     }
 97 }
 98 /*
 99 Output:
100 +1  +2  +3  +4  +5  +6  +7  +8  +9  +10  +11  +12  +13  +14  +15  +16  +17  +18  +19  +20  
101 -1  -2  -3  -4  -5  -6  -7  -8  -9  -10  -11  -12  -13  -14  -15  +21  +22  +23  +24  +25  +26  +27  +28  +29  +30  +31  +32  +33  +34  +35  
102 -16  -17  -18  -19  -20  -21  -22  -23  +36  +37  +38  +39  +40  +41  +42  +43  
103 -24  -25  -26  -27  -28  -29  -30  -31  -32  -33  -34  -35  -36  -37  -38  -39  -40  -41  -42  -43  
104 +44  +45  +46  +47  +48  +49  +50  +51  +52  +53  +54  +55  +56  +57  +58  +59  +60  +61  +62  +63  
105 -44  -45  +64  +65  
106 -46  -47  -48  -49  -50  -51  -52  -53  -54  -55  -56  -57  -58  -59  -60  -61  -62  -63  -64  -65  
107 +66  +67  +68  +69  +70  +71  +72  +73  +74  +75  +76  +77  +78  +79  +80  +81  +82  +83  +84  +85  
108 -66  -67  -68  -69  -70  -71  -72  -73  -74  -75  -76  -77  -78  -79  -80  -81  -82  -83  -84  -85  
109 +86  +87  +88  +89  +90  +91  +92  +93  +94  +95  +96  +97  +98  +99  +100  -86  -87  -88  -89  -90  -91  -92  -93  -94  -95  -96  -97  -98  -99  -100  
110  */

 

一共有7笔交易

生产20只,卖掉15只

生产15只,卖掉8只

生产8只,卖掉20只

生产20只,卖掉2只

生产2只,卖掉20只

生产20只,卖掉20只

生产15只,卖掉15只

解决方式二

并发协作模型“生产者/消费者模式”-->信号灯法,缓冲区为1的管程法

  1 //解决生产者消费者问题2:信号灯法,标志位解决
  2 public class TestPC2 {
  3     public static void main(String[] args) {
  4         TV tv = new TV();
  5   6         new Player(tv).start();
  7         new Watcher(tv).start();
  8     }
  9 }
 10  11 //生产者-->演员
 12 class Player extends Thread{
 13     TV tv;
 14  15     public Player(TV tv) {
 16         this.tv = tv;
 17     }
 18  19     @Override
 20     public void run() {
 21         for (int i = 0; i < 10; i++) {
 22             if (i%2==0){
 23                 tv.play("快乐大本营");
 24             }else {
 25                 tv.play("天天向上");
 26             }
 27         }
 28     }
 29 }
 30  31 //消费者-->观众
 32 class Watcher extends Thread{
 33     TV tv;
 34  35     public Watcher(TV tv) {
 36         this.tv = tv;
 37     }
 38  39     @Override
 40     public void run() {
 41         for (int i = 0; i < 10; i++) {
 42             tv.watch();
 43         }
 44     }
 45 }
 46  47 //产品-->节目
 48 class TV{
 49     //演员表演,观众等待  T
 50     //观众观看,演员等待  T
 51     String voice;//表演的节目
 52     boolean flag = true;
 53  54     public synchronized void play(String voice){
 55         if (!flag){
 56             try {
 57                 this.wait();
 58             } catch (InterruptedException e) {
 59                 e.printStackTrace();
 60             }
 61         }
 62         System.out.println("演员表演了:"+voice);
 63         //通知观众观看
 64         this.notifyAll();//通知唤醒
 65         this.voice = voice;
 66         this.flag = !this.flag;
 67     }
 68  69     //观看
 70     public synchronized void watch(){
 71         if (flag){
 72             try {
 73                 this.wait();
 74             } catch (InterruptedException e) {
 75                 e.printStackTrace();
 76             }
 77         }
 78         System.out.println("观众观看了:"+voice);
 79         //通知演员表演
 80         this.notifyAll();
 81         this.flag = !this.flag;
 82     }
 83 }
 84 /*
 85 Output:
 86 演员表演了:快乐大本营
 87 观众观看了:快乐大本营
 88 演员表演了:天天向上
 89 观众观看了:天天向上
 90 演员表演了:快乐大本营
 91 观众观看了:快乐大本营
 92 演员表演了:天天向上
 93 观众观看了:天天向上
 94 演员表演了:快乐大本营
 95 观众观看了:快乐大本营
 96 演员表演了:天天向上
 97 观众观看了:天天向上
 98 演员表演了:快乐大本营
 99 观众观看了:快乐大本营
100 演员表演了:天天向上
101 观众观看了:天天向上
102 演员表演了:快乐大本营
103 观众观看了:快乐大本营
104 演员表演了:天天向上
105 观众观看了:天天向上
106  */

使用线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的交通工具。

好处:

  • 提高响应速度(减少了创建新线程的时间)

  • 降低资源消耗(重复利用线程池线程,不需要每次都创建)

  • 便于线程管理(……)

    • corePoolSize:核心池的大小

    • maximumPoolSize:最大线程数

    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

JDK 5.0 提供线程池相关API:ExecutorService和Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable

<T>Future<T> submit(Callable<T> task):执行任务,有返回值,一般用来执行Callable

void shutdown():关闭连接池

Executor:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

 1 import java.util.concurrent.ExecutorService;
 2 import java.util.concurrent.Executors;
 3  4 public class TestPool {
 5     public static void main(String[] args) {
 6         //1.创建服务,创建线程池
 7         //newFixedThreadPool参数为nThreads表示线程池大小
 8         ExecutorService service = Executors.newFixedThreadPool(10);
 9 10         //执行
11         service.execute(new MyThread());
12         service.execute(new MyThread());
13         service.execute(new MyThread());
14         service.execute(new MyThread());
15 16         //2.关闭链接
17         service.shutdown();
18     }
19 }
20 21 class MyThread implements Runnable{
22     @Override
23     public void run() {
24             System.out.println(Thread.currentThread().getName());
25     }
26 }
27 /*
28 Output:
29 pool-1-thread-2
30 pool-1-thread-4
31 pool-1-thread-1
32 pool-1-thread-3
33  */

总结

 1 import java.util.concurrent.Callable;
 2 import java.util.concurrent.ExecutionException;
 3 import java.util.concurrent.Future;
 4 import java.util.concurrent.FutureTask;
 5  6 //回顾总结线程的创建
 7 public class Revise {
 8     public static void main(String[] args) {
 9         new MyThread1().start();
10         new Thread(new MyThread2()).start();//代理
11         //扩展
12         FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
13         new Thread(futureTask).start();
14         try {
15             Integer integer = futureTask.get();
16             System.out.println(integer);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         } catch (ExecutionException e) {
20             e.printStackTrace();
21         }
22     }
23 }
24 25 //1.继承Thread类
26 class MyThread1 extends Thread{
27     @Override
28     public void run() {
29         System.out.println("MyThread1");
30     }
31 }
32 33 //2.实现Runnable接口
34 class MyThread2 implements Runnable{
35     @Override
36     public void run() {
37         System.out.println("MyThread2");
38     }
39 }
40 41 //3.实现Callable接口
42 class MyThread3 implements Callable{
43     @Override
44     public Integer call() throws Exception {
45         System.out.println("MyThread3");
46         return 100;
47     }
48 }
49 /*
50 Output:
51 MyThread1
52 MyThread2
53 MyThread3
54 100
55  */
posted on 2021-05-11 00:11  zrm0612  阅读(38)  评论(0)    收藏  举报