Java 多线程 - 总结概述
阅读原文时间:2021年11月17日阅读:1

菜鸟教程:

  • Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
  • 这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
  • 多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

大白话:

  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程式CPU调度和执行的单位
  • 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
  • 进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
  • 注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉

知识点:

  • 线程就是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程main(),gc()垃圾回收线程;
  • main()称之为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排执行,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如cpu调度事件,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

下图显示了一个线程完整的生命周期。

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 终止状态:

    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

  • 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
  • 线程开启不一定立即执行,由cpu调度
  • Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
  • 默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
  • 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
  • 在下面的线程状态节点具体讲述

Java 提供了三种创建线程的方法:

  • 通过继承 Thread 类本身;(重点)
  • 通过实现 Runnable 接口;(重点)
  • 通过 Callable 和 Future 创建线程;(重点)

继承 Thread 类创建线程

  • 创建一个线程的第一种方法是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。

  • 继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。

  • 该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

    1 //创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
    2 public class TestThread01 extends Thread {
    3 @Override
    4 public void run() {
    5 //run方法线程体
    6 for (int i = 0; i < 3; i++) {
    7 System.out.println("好好学习");
    8 }
    9 }
    10
    11 public static void main(String[] args) {
    12 //main线程,主线程
    13
    14 //创建一个线程对象
    15 TestThread01 testThread01 = new TestThread01();
    16
    17 //testThread01.run();执行run方法,没有开启线程,依旧是自上而下执行代码
    18
    19 //调用start()方法,开启线程,run方法体线程和main()主线程根据调度交叉执行
    20 testThread01.start();
    21 for (int i = 0; i <3 ; i++) {
    22 System.out.println("天天向上");
    23 }
    24 }
    25 }

Thread线程练习-网图下载

前置条件:

  • 下载好之后放到项目的lib目录下,没有lib目录的,自己新增一个。

  • 右键lib目录,选择Add as Library,点击OK即可

1 //练习Thread,实现多线程下载图片
2 public class TestThread2 extends Thread{
3 private String url;
4 private String name;
5
6 public TestThread2(String url,String name){
7 this.url=url;
8 this.name=name;
9 }
10
11 //下载图片线程的执行体
12 @Override
13 public void run() {
14 WebDownloader webDownloader = new WebDownloader();
15 webDownloader.downloader(url,name);
16 System.out.println("下载了文件名为:"+name);
17 }
18
19 public static void main(String[] args) {
20 TestThread2 t1 = new TestThread2("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-1.jpg");
21 TestThread2 t2 = new TestThread2("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-2.jpg");
22 TestThread2 t3 = new TestThread2("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-3.jpg");
23
24 t1.start();
25 t2.start();
26 t3.start();
27 }
28 }
29
30 //下载器
31 class WebDownloader{
32 //下载方法
33 public void downloader(String url,String name){
34 try {
35 FileUtils.copyURLToFile(new URL(url),new File(name));
36 } catch (IOException e) {
37 e.printStackTrace();
38 System.out.println("IO异常,download方法出现问题");
39 }
40 }
41 }


实现 Runnable 接口创建线程

创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。

为了实现 Runnable,一个类只需要执行一个方法调用 run(),声明如下:

public void run()

可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。

在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。

Thread 定义了几个构造方法,下面的这个是我们经常使用的:

Thread(Runnable threadOb,String threadName);

这里,threadOb 是一个实现 Runnable 接口的类的实例,并且 threadName 指定新线程的名字。

新线程创建之后,你调用它的 start() 方法它才会运行。

void start();

1 //创建线程方式二:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法启动线程
2 public class TestThread03 implements Runnable{
3 @Override
4 public void run() {
5 for (int i = 0; i < 200; i++) {
6 System.out.println("好好学习"+i);
7 }
8 }
9
10 public static void main(String[] args) {
11 //创建runnable接口的实现类对象
12 TestThread03 testThread03 = new TestThread03();
13 //创建线程对象,通过线程对象来开启我们的线程,代理
14 new Thread(testThread03).start();
15
16 for (int i = 0; i < 200; i++) {
17 System.out.println("天天向上"+i);
18 }
19 }
20 }

Runnable 接口创建线程练习-下载网图

1 public class TestThread04 implements Runnable{
2
3 private String url;
4 private String name;
5
6 public TestThread04(String url,String name){
7 this.url=url;
8 this.name= name;
9 }
10
11 @Override
12 public void run() {
13 new WebDownLoad01(url,name);
14 System.out.println("下载了文件名:"+name);
15 }
16
17 public static void main(String[] args) {
18 TestThread04 t1 = new TestThread04("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-1.jpg");
19 TestThread04 t2 = new TestThread04("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-2.jpg");
20 TestThread04 t3 = new TestThread04("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-3.jpg");
21
22 new Thread(t1).start();
23 new Thread(t2).start();
24 new Thread(t3).start();
25 }
26 }
27 //下载器
28 class WebDownLoad01{
29 public WebDownLoad01(String url,String name){
30 try {
31 FileUtils.copyURLToFile(new URL(url),new File(name));
32 } catch (IOException e) {
33 e.printStackTrace();
34 System.out.println("IO异常,下载失败");
35 }
36 }
37 }

Runnable 接口创建线程练习-多个线程同时操作同一个对象

买火车票的例子

1 //多个线程同时操作同一个对象
2 //买火车票的例子
3 public class TestThread05 implements Runnable{
4
5 //站台总票数
6 private int ticketNums=10;
7
8 @Override
9 public void run() {
10 while (true){
11 //没有票时退出
12 if(ticketNums<=0){ 13 break; 14 } 15 16 //模拟延时 17 try { 18 Thread.sleep(200); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 23 System.out.println(Thread.currentThread().getName()+"-->拿到了第:"+ticketNums--+"票"); //Thread.currentThread().getName()获得当前执行线程的名字
24 }
25 }
26
27 public static void main(String[] args) {
28 TestThread05 testThread05 = new TestThread05();
29
30 new Thread(testThread05,"小明").start();//小明:线程的名字
31 new Thread(testThread05,"小张").start();
32 new Thread(testThread05,"黄牛").start();
33 }
34 }

输出结果,发现同一张票被两个人买走了,发现了线程的问题:多个线程操作同一个资源的时候,线程不安全,数据紊乱,后面线程几个重要概念中的线程安全会有具体讲述

Runnable 接口创建线程练习-龟兔赛跑

1 //模拟龟兔赛跑
2 public class Race implements Runnable{
3
4 // 胜利者
5 private static String winner;
6
7 //线程运行体
8 @Override
9 public void run() {
10 for (int i = 0; i <= 100; i++) { 11 //判断比赛是否结束 12 boolean b = gameOver(i); 13 //如果有胜利者了,就停止程序 14 if (b){ 15 break; 16 } 17 System.out.println(Thread.currentThread().getName()+"跑了"+i+"米"); 18 } 19 } 20 21 //判断是否完成比赛 22 private boolean gameOver(int steps){ 23 //判断是否有胜利者 24 if (winner!=null){ 25 return true; 26 }{ 27 if (steps>=100) {
28 winner=Thread.currentThread().getName();
29 System.out.println("winner is :"+winner);
30 return true;
31 }
32 }
33 return false;
34 }
35
36 public static void main(String[] args) {
37 Race race = new Race();
38
39 new Thread(race,"乌龟").start();
40 new Thread(race,"兔子").start();
41 }
42 }


实现Callable接口创建线程

了解即可

  1. 实现Callable接口,需要返回值类型;

  2. 重写call方法,需要抛出异常

  3. 创建目标对象

  4. 创建执行服务ExecutorService ser = Executors.newFixedThreadPool();

  5. 提交执行Future r1 = ser.submit(t1);

  6. 获得结果Boolean rs1 = r1.get();

  7. 关闭服务ser.shutdown();

    1 //线程创建方式三:实现Callable接口
    2 public class TestCallable implements Callable {
    3 private String url;
    4 private String name;
    5
    6 public TestCallable(String url,String name){
    7 this.url=url;
    8 this.name=name;
    9 }
    10
    11 @Override
    12 public Object call() throws Exception {
    13 WebDownload webDownload = new WebDownload(url,name);
    14 System.out.println(name+"文件已下载");
    15 return true;
    16 }
    17
    18 public static void main(String[] args) throws ExecutionException, InterruptedException {
    19 TestCallable t1 = new TestCallable("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-1.jpg");
    20 TestCallable t2 = new TestCallable("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-2.jpg");
    21 TestCallable t3 = new TestCallable("https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc","百度图片-3.jpg");
    22
    23 //创建执行服务
    24 ExecutorService ser = Executors.newFixedThreadPool(3);
    25
    26 //提交执行
    27 Future r1 = ser.submit(t1);
    28 Future r2 = ser.submit(t2);
    29 Future r3 = ser.submit(t3);
    30
    31 //获取结果
    32 Boolean rs1 = r1.get();
    33 Boolean rs2 = r2.get();
    34 Boolean rs3 = r3.get();
    35
    36 //关闭服务
    37 ser.shutdown();
    38 }
    39 }
    40
    41 //下载器
    42 class WebDownload{
    43 public WebDownload(String url,String name){
    44 try {
    45 FileUtils.copyURLToFile(new URL(url),new File(name));
    46 } catch (IOException e) {
    47 e.printStackTrace();
    48 System.out.println("IO异常,下载失败");
    49 }
    50 }
    51 }


创建线程的三种方式的对比

  • 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。

  • 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。


拓展

静态代理

  • 真实对象和代理对象都要实现同一个接口

  • 代理对象要代理真实角色

  • #好处

  • 代理对象可以做很多真实对象做不了的事情

  • 真实对象专注做自己的事情

    1 //以结婚找婚庆公司举例
    2 public class StaticProxy {
    3 public static void main(String[] args) {
    4 WeddingCompany weddingCompany = new WeddingCompany(new You());
    5 weddingCompany.HappyMarry();
    6 }
    7 }
    8
    9 //同一个接口———结婚这件事情
    10 interface Marry{
    11 void HappyMarry();
    12 }
    13
    14 //真实对象————结婚的主人公
    15 class You implements Marry{
    16 @Override
    17 public void HappyMarry() {
    18 System.out.println("张三要结婚了");
    19 }
    20 }
    21
    22 //代理对象————结婚场景布置找的婚庆公司,代理角色
    23 class WeddingCompany implements Marry{
    24
    25 //代理的主人公,结婚的主人公,给谁帮忙的
    26 private Marry target;
    27
    28 public WeddingCompany(Marry target){
    29 this.target= target;
    30 }
    31
    32 @Override
    33 public void HappyMarry() {
    34 before();
    35 this.target.HappyMarry();
    36 after();
    37 }
    38
    39 private void before() {
    40 System.out.println("结婚之前布置现场");
    41 }
    42
    43 private void after() {
    44 System.out.println("结婚之后收尾款");
    45 }
    46
    47
    48 }

  • 反观线程Thread和Runable,Thread类其实是实现Runable接口的,Thread类相当于就是一个静态代理,完成Runable许多完成不了的事情,比如使用Runable创建线程,最后运行线程使用的是代理Thread的start方法;Runnabe接口启动线程就是是用了静态代理的方式

  • Runnable接口和Thread代理都有run方法,最后调用的是Thread的start方法,但实际执行的还是Runnable中的run方法中的方法体

Lambda表达式

好处:

  • 避免匿名内部类定义过多
  • 其实质属于函数式编程的概念
  • 例子:new Thread(()-> System.out.println("多线程学习")).start();

函数式接口:

  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口,比如

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

推导lambda表达式

正常写法

1 /*
2 推导lambda表达式--正常写法
3 */
4 public class TestLambda01 {
5 public static void main(String[] args) {
6 ILike like = new Like();
7 like.lambda();
8 }
9 }
10
11 //1.定义一个函数式接口
12 interface ILike{
13 void lambda();
14 }
15
16 //2.实现类
17 class Like implements ILike{
18 @Override
19 public void lambda() {
20 System.out.println("i like lambda");
21 }
22 }

静态内部类

1 /*
2 推导lambda表达式--优化方法:静态内部类
3 */
4 public class TestLambda01 {
5
6 public static void main(String[] args) {
7 ILike like = new Like();
8 like.lambda();
9
10 like = new Like1();
11 like.lambda();
12 }
13
14 //3.静态内部类
15 static class Like1 implements ILike{
16 @Override
17 public void lambda() {
18 System.out.println("i like lambda1-静态内部类");
19 }
20 }
21 }
22
23 //1.定义一个函数式接口
24 interface ILike{
25 void lambda();
26 }
27
28 //2.实现类
29 class Like implements ILike{
30 @Override
31 public void lambda() {
32 System.out.println("i like lambda");
33 }
34 }

局部内部类

1 /*
2 推导lambda表达式--优化方法:局部内部类
3 */
4 public class TestLambda01 {
5
6 public static void main(String[] args) {
7 ILike like = new Like();
8 like.lambda();
9
10 //3.静态内部类
11 like = new Like1();
12 like.lambda();
13
14 //4.局部内部类
15 class Like2 implements ILike{
16 @Override
17 public void lambda() {
18 System.out.println("i like lambda2-局部内部类");
19 }
20 }
21
22 //4.局部内部类
23 like = new Like2();
24 like.lambda();
25 }
26
27 //3.静态内部类
28 static class Like1 implements ILike{
29 @Override
30 public void lambda() {
31 System.out.println("i like lambda1-静态内部类");
32 }
33 }
34 }
35
36 //1.定义一个函数式接口
37 interface ILike{
38 void lambda();
39 }
40
41 //2.实现类
42 class Like implements ILike{
43 @Override
44 public void lambda() {
45 System.out.println("i like lambda");
46 }
47 }

匿名内部类

1 /*
2 推导lambda表达式--优化方法:匿名内部类
3 */
4 public class TestLambda01 {
5
6 public static void main(String[] args) {
7 ILike like = new Like();
8 like.lambda();
9
10 //3.静态内部类
11 like = new Like1();
12 like.lambda();
13
14 //4.局部内部类
15 class Like2 implements ILike{
16 @Override
17 public void lambda() {
18 System.out.println("i like lambda2-局部内部类");
19 }
20 }
21
22 //4.局部内部类
23 like = new Like2();
24 like.lambda();
25
26 //5.匿名内部类,没有类的名称,必须借助接口或者父类
27 like = new ILike() {
28 @Override
29 public void lambda() {
30 System.out.println("i like lambda3-匿名内部类");
31 }
32 };
33 //5.匿名内部类
34 like.lambda();
35 }
36
37 //3.静态内部类
38 static class Like1 implements ILike{
39 @Override
40 public void lambda() {
41 System.out.println("i like lambda1-静态内部类");
42 }
43 }
44 }
45
46 //1.定义一个函数式接口
47 interface ILike{
48 void lambda();
49 }
50
51 //2.实现类
52 class Like implements ILike{
53 @Override
54 public void lambda() {
55 System.out.println("i like lambda");
56 }
57 }

思考:怎么还能将它再简化呢,于是乎,JDK1.8出了个lambda表达式

1 /*
2 推导lambda表达式--优化方法:lambda表达式
3 */
4 public class TestLambda01 {
5
6 public static void main(String[] args) {
7 ILike like = new Like();
8 like.lambda();
9
10 //3.静态内部类
11 like = new Like1();
12 like.lambda();
13
14 //4.局部内部类
15 class Like2 implements ILike{
16 @Override
17 public void lambda() {
18 System.out.println("i like lambda2-局部内部类");
19 }
20 }
21
22 //4.局部内部类
23 like = new Like2();
24 like.lambda();
25
26 //5.匿名内部类,没有类的名称,必须借助接口或者父类
27 like = new ILike() {
28 @Override
29 public void lambda() {
30 System.out.println("i like lambda3-匿名内部类");
31 }
32 };
33 //5.匿名内部类
34 like.lambda();
35
36 //6.lambda简化
37 like = ()->{
38 System.out.println("i like lambda4-lambda简化");
39 };
40 //6.lambda简化
41 like.lambda();
42 }
43
44 //3.静态内部类
45 static class Like1 implements ILike{
46 @Override
47 public void lambda() {
48 System.out.println("i like lambda1-静态内部类");
49 }
50 }
51 }
52
53 //1.定义一个函数式接口
54 interface ILike{
55 void lambda();
56 }
57
58 //2.实现类
59 class Like implements ILike{
60 @Override
61 public void lambda() {
62 System.out.println("i like lambda");
63 }
64 }

lambda例子

1 //例子-带参数的lambda
2 public class TestLambda02 {
3 public static void main(String[] args) {
4
5 LunchEat lunchEat = (String name)->{
6 System.out.println("中午吃"+name);
7 };
8
9 lunchEat.eat("牛肉");
10 }
11 }
12
13 interface LunchEat {
14 void eat(String name);
15 }

疑问:上述的lambda还能不能再简化?可以,请看下面代码

1 //例子-带参数的lambda
2 public class TestLambda02 {
3 public static void main(String[] args) {
4
5 LunchEat lunchEat=null;
6
7 //未简化
8 lunchEat = (String name)->{
9 System.out.println("中午吃"+name);
10 };
11
12 //简化1:去掉参数类型
13 lunchEat = (name)->{
14 System.out.println("早上吃"+name);
15 };
16
17 //简化2:去掉括号()
18 lunchEat=name->{
19 System.out.println("晚上吃"+name);
20 };
21
22 //简化3:去掉花括号()
23 lunchEat=name->System.out.println("夜宵吃"+name);
24
25 lunchEat.eat("牛肉");
26 }
27 }
28
29 interface LunchEat {
30 void eat(String name);
31 }

lambda简化总结

  • lambda表达式只有在代码只有一行的情况下,才能简化成一行,如果多行,那么就用代码块包裹
  • 前提是接口为函数式接口
  • 多个参数也可以去掉参数类型,要去掉就都去掉,但是必须得加上括号

线程停止

  • 建议线程正常停止—-->利用次数,不建议死循环

  • 建议使用标志位----->设置一个标志位

  • 不要使用stop或者destroy等过时或者JDK不建议使用的方法

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


线程休眠

  • Thread.sleep()

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

  • sleep存在异常InterruptedException

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

  • sleep可以模拟网络延时,将线程的问题暴露出来,同时还可以模拟倒计时等

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

    1 //模拟网络延时
    2 public class TestSleep implements Runnable{
    3 //站台总票数
    4 private int ticketNums=10;
    5
    6 @Override
    7 public void run() {
    8 while (true){
    9 //没有票时退出
    10 if(ticketNums<=0){ 11 break; 12 } 13 14 //模拟延时 15 try { 16 Thread.sleep(200); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 21 System.out.println(Thread.currentThread().getName()+"-->拿到了第:"+ticketNums--+"票"); //Thread.currentThread().getName()获得当前执行线程的名字
    22 }
    23 }
    24
    25 public static void main(String[] args) {
    26 TestSleep testSleep = new TestSleep();
    27
    28 new Thread(testSleep,"小明").start();//小明:线程的名字
    29 new Thread(testSleep,"小张").start();
    30 new Thread(testSleep,"黄牛").start();
    31 }
    32 }

    1 //倒计时
    2 public class TestSleep02 {
    3 public static void main(String[] args) {
    4 try {
    5 new TestSleep02().tenDown();
    6 } catch (InterruptedException e) {
    7 e.printStackTrace();
    8 }
    9
    10 }
    11
    12 public static void tenDown() throws InterruptedException{
    13 int num=10;
    14 while (true){
    15 Thread.sleep(1000);
    16 System.out.println(num--);
    17 if (num<=0){
    18 break;
    19 }
    20 }
    21 }
    22 }

    1 //打印系统当前时间
    2 public class TestSleep02 {
    3 public static void main(String[] args) throws InterruptedException{
    4 //打印系统当前时间
    5 Date startTime= new Date(System.currentTimeMillis());//获得系统当前时间
    6 while (true){
    7 Thread.sleep(1000);
    8 System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
    9 startTime=new Date(System.currentTimeMillis());//更新时间
    10 }
    11 }
    12 }


线程礼让

  • Thread.yield()

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

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

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

    1 //测试礼让线程
    2 //礼让不一定成功,看CPU调度
    3 public class TestYield {
    4 public static void main(String[] args) {
    5 MyYield myYield = new MyYield();
    6
    7 new Thread(myYield,"a").start();
    8 new Thread(myYield,"b").start();
    9 }
    10 }
    11
    12 class MyYield implements Runnable{
    13
    14 @Override
    15 public void run() {
    16 System.out.println(Thread.currentThread().getName()+"线程开始执行");
    17 Thread.yield(); //礼让
    18 System.out.println(Thread.currentThread().getName()+"线程停止执行");
    19 }
    20 }


线程强制执行

  • thread.join()

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

  • 可以想象成插队

    1 //线程强制执行,想象为插队
    2 public class TestJoin implements Runnable{
    3 public static void main(String[] args) throws InterruptedException {
    4 //我们的线程
    5 TestJoin testJoin = new TestJoin();
    6 Thread thread = new Thread(testJoin);
    7 thread.start();
    8
    9 //main线程
    10 for (int i = 0; i < 50; i++) {
    11 System.out.println("main线程");
    12 if (i==10){
    13 thread.join();//原本main和我们自己的线程是根据cpu调度并行执行的,但是当i=10时,main线程停下来不走,等vip线程执行完
    14 }
    15 }
    16
    17 }
    18
    19 @Override
    20 public void run() {
    21 //我们的线程
    22 for (int i = 0; i <30 ; i++) {
    23 System.out.println("线程VIP");
    24 }
    25 }
    26 }


线程状态观测

thread.getState()

1 //观察线程的状态
2 public class TestState {
3 public static void main(String[] args) throws InterruptedException {
4 //lambda表达式重写Runable的run方法,给thread实现类执行
5 Thread thread = new Thread(()->{
6 for (int i = 0; i < 5; i++) { 7 try { 8 Thread.sleep(1000); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 } 13 System.out.println("/////"); 14 }); 15 //上面等价于下面这个写法 16 /* 17 Runnable runnable = ()->{
18 System.out.println("");
19 };
20
21 Thread thread =new Thread(runnable);
22 */
23
24 //观察装填
25 Thread.State state = thread.getState();
26 System.out.println(state); //new
27
28 //观察启动后
29 thread.start();
30 Thread.State state1 = thread.getState();
31 System.out.println(state);
32
33 while (state != Thread.State.TERMINATED){ //只要线程不终止,就一致输出状态
34 Thread.sleep(100);
35 state=thread.getState();//更新状态
36 System.out.println(state);
37 }
38
39 //thread.start()报错,死亡后的线程,不能再启动
40
41 }
42 }

线程死亡后不能再启动


线程的优先级

thread.setPriority()

1 public class TestPriority {
2 public static void main(String[] args) {
3 //main线程的优先级
4 System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
5
6 //我们自己的线程
7 MyPriority myPriority = new MyPriority();
8
9
10 Thread t1 = new Thread(myPriority);
11 Thread t2 = new Thread(myPriority);
12 Thread t3 = new Thread(myPriority);
13 Thread t4 = new Thread(myPriority);
14 Thread t5 = new Thread(myPriority);
15
16 //设置优先级再启动
17 t1.setPriority(3);
18 t1.start();
19
20 /*
21 优先级范围为1-10,数值高,优先级高,下面两个线程执行会报错
22 t2.setPriority(11);
23 t2.start();
24
25 t3.setPriority(-1);
26 t3.start();
27 */
28
29 t4.setPriority(Thread.MAX_PRIORITY);//最高优先级,相当于t4.setPriority(10)
30 t4.start();
31
32 t5.setPriority(Thread.MIN_PRIORITY);//最低优先级,相当于t5.setPriority(1)
33 t5.start();
34 }
35 }
36
37 class MyPriority implements Runnable{
38
39 @Override
40 public void run() {
41 System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
42 }
43 }

总结

  • 优先级的设定建议在start()调度前
  • 优先级低只意味着获得调度的概率低,并不是优先级低的就不会被调用。都得看cpu的调度
  • 优先级低的先执行,我们成为“性能倒置”

守护线程

  • thread.setDaemon(true)

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

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

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

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

    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 //将上帝设置为守护线程
    10 Thread thread = new Thread(god);
    11 thread.setDaemon(true); //setDaemon()默认为false,false表示的是用户线程,正常线程都是用户线程;为true代表守护线程
    12
    13 thread.start(); //守护线程启动
    14
    15 new Thread(you).start();//用户线程启动
    16 }
    17 }
    18
    19 //上帝,守护线程
    20 class God implements Runnable{
    21 @Override
    22 public void run() {
    23 while (true){
    24 System.out.println("上帝永远守护着你");
    25 }
    26 }
    27 }
    28
    29 //人,用户线程
    30 class You implements Runnable{
    31 @Override
    32 public void run() {
    33 for (int i = 0; i < 100; i++) {
    34 System.out.println("每一年都要过的开心");
    35 }
    36 System.out.println("=======goodbye!world=======");
    37 }
    38 }

  • 多个线程操作同一个资源
  • 并发:同一个对象被多个线程同时操作
    • 100张票被10万人抢,100张票就是一个对象,10万人就是多个线程
    • 银行卡里有100万,两个人同时取,一个在银行柜台取,一个使用手机银行取
  • 现实生活中,我们会遇到“同一个资源,多个人都想要使用”的问题,比如食堂排队打饭,最天然的解决方法就是,排队,一个一个来
  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
  • 队列+锁才能保证线程同步的安全性
  • 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized。当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,但是存在以下问题:
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引发性能问题。

三大不安全案例

1 //不安全的买票
2 public class UnsafeBuyTicket {
3 public static void main(String[] args) {
4 BuyTicket buyTicket = new BuyTicket();
5 new Thread(buyTicket,"小明").start();
6 new Thread(buyTicket,"小王").start();
7 new Thread(buyTicket,"小张").start();
8 }
9 }
10
11 class BuyTicket implements Runnable{
12
13 //票
14 private int ticketnums = 10;
15
16 //线程停止的标志位
17 boolean flag=true;
18
19 //线程运行的代码块
20 @Override
21 public void run() {
22 //买票
23 while (flag){
24 try {
25 buy();
26 } catch (InterruptedException e) {
27 e.printStackTrace();
28 }
29 }
30 }
31
32 //买票
33 private void buy() throws InterruptedException {
34 //判断是否有票
35 if (ticketnums<=0){
36 flag=false;
37 return;
38 }
39
40 //模拟延时
41 Thread.sleep(100);
42 System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnums--);
43 }
44 }

1 //不安全的取钱
2 //两个人去银行取钱
3 public class UnsafeBank {
4 public static void main(String[] args) {
5 Account account = new Account(100,"工资");
6
7 Drawing xiaoming = new Drawing(account,50,"小明");
8 Drawing xiaohong = new Drawing(account,100,"小红");
9
10 xiaoming.start();
11 xiaohong.start();
12
13 }
14 }
15
16 //账户
17 class Account{
18 int money;//余额
19 String name;//卡名
20
21 public Account(int money,String name){
22 this.money=money;
23 this.name=name;
24 }
25 }
26
27 //银行:模拟取款
28 class Drawing extends Thread{
29 Account account;//账户
30 int drawingMoney;//取了多少钱
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 @Override
41 public void run() {
42 //判断有没有钱
43 if(account.money-drawingMoney<0){
44 System.out.println(Thread.currentThread().getName()+"余额不足");
45 return;
46 }
47
48 try {
49 Thread.sleep(1000);
50 } catch (InterruptedException e) {
51 e.printStackTrace();
52 }
53 //卡内余额=余额-要取得钱
54 account.money=account.money-drawingMoney;
55 //你手里得钱
56 nowMoney=nowMoney+drawingMoney;
57 System.out.println(account.name+"余额为:"+account.money);
58 System.out.println(this.getName()+"手里的钱:"+nowMoney);
59
60 }
61 }

1 //线程不安全的集合
2 public class UnsafeList {
3 public static void main(String[] args) {
4 List list = new ArrayList<>();
5 for (int i = 0; i < 10000; i++) { 6 new Thread(()->{
7 list.add(Thread.currentThread().getName());
8 }).start();
9 }
10 System.out.println(list.size());
11 }
12 }


同步方法及同步块

同步方法

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种方法:synchronized方法和synchronized块
  • 同步方法:public synchronized void method(int args){}
  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面堵塞的线程才能获得这个锁,继续执行
  • 缺陷:若将一个大的方法申名为synchronized将会影响效率
  • 方法里面需要修改的内容才需要锁,锁太多,会浪费资源

同步块

  • 同步块:synchronized(Obj){}

  • Obj称之为同步监视器

    • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象的本身,或者是class【后面反射笔记有】
  • 同步监视器的执行过程

    1 //synchronized 同步方法,安全的买票
    2 public class UnsafeBuyTicket {
    3 public static void main(String[] args) {
    4 BuyTicket buyTicket = new BuyTicket();
    5 new Thread(buyTicket,"小明").start();
    6 new Thread(buyTicket,"小王").start();
    7 new Thread(buyTicket,"小张").start();
    8 }
    9 }
    10
    11 class BuyTicket implements Runnable{
    12
    13 private int ticketnums = 10;
    14
    15 boolean flag=true;
    16
    17 @Override
    18 public void run() {
    19 while (flag){
    20 try {
    21 buy();
    22 Thread.sleep(100);
    23 } catch (InterruptedException e) {
    24 e.printStackTrace();
    25 }
    26 }
    27 }
    28
    29 //synchronized 同步方法,锁的是this,要锁共享的资源对象
    30 private synchronized void buy() throws InterruptedException {
    31 if (ticketnums<=0){
    32 flag=false;
    33 return;
    34 }
    35
    36 System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketnums--);
    37 }
    38 }

    1 //同步块锁定synchronized(要锁的对象){},安全的取钱
    2 //两个人去银行取钱
    3 public class UnsafeBank {
    4 public static void main(String[] args) {
    5 Account account = new Account(100,"工资");
    6
    7 Drawing xiaoming = new Drawing(account,50,"小明");
    8 Drawing xiaohong = new Drawing(account,100,"小红");
    9
    10 xiaoming.start();
    11 xiaohong.start();
    12
    13 }
    14 }
    15
    16 //账户
    17 class Account{
    18 int money;//余额
    19 String name;//卡名
    20
    21 public Account(int money,String name){
    22 this.money=money;
    23 this.name=name;
    24 }
    25 }
    26
    27 //银行:模拟取款
    28 class Drawing extends Thread{
    29 Account account;//账户
    30 int drawingMoney;//取了多少钱
    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 @Override
    41 public void run() {
    42 //synchronized 同步块锁定,代码放到同步块里面,要锁共享的资源对象account他们的银行卡,并且锁的要是变化的量
    43 // 可以试一下,如果锁放在银行的方法Drawing上 class synchronized Drawing extends Thread{},是不成功的,因为银行不是他们的共享资源,卡才是,卡的对象是account,锁account
    44 synchronized (account){
    45 if(account.money-drawingMoney<0){
    46 System.out.println(Thread.currentThread().getName()+"余额不足");
    47 return;
    48 }
    49 try {
    50 Thread.sleep(1000);
    51 } catch (InterruptedException e) {
    52 e.printStackTrace();
    53 }
    54 //卡内余额=余额-要取得钱
    55 account.money=account.money-drawingMoney;
    56 //你手里得钱
    57 nowMoney=nowMoney+drawingMoney;
    58 System.out.println(account.name+"余额为:"+account.money);
    59 System.out.println(this.getName()+"手里的钱:"+nowMoney);
    60 }
    61 }
    62 }

//同步块锁,线程安全的集合
public class UnsafeList {
public static void main(String[] args) throws InterruptedException {
List list = new ArrayList<>();
for (int i = 0; i < 100; i++) { new Thread(()->{
//同步块锁,避免写入列表的时候写入脏数据
synchronized (list){
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(1000);//不加时间,主线索跑完成,print已经答应出来,但是run线程还是没有跑完,这个休眠是给主线程休眠的
System.out.println(list.size());
Iterator iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}

线程安全的集合,不要加同步块锁 CopyOnWriteArrayList

//拓展:测试JUC并发,安全类型的集合
public class TextJUC {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList list = new CopyOnWriteArrayList(); //线程安全的集合
for (int i = 0; i < 10000; i++) { new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
Thread.sleep(1000);
System.out.println(list.size());
}
}


死锁

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

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

  • 上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁的发生

    1 //死锁:多个线程互相抱着对方需要的资源,形成僵持
    2 public class DeadLock {
    3 public static void main(String[] args) {
    4 MakeUp m1 = new MakeUp(0,"灰姑娘");
    5 MakeUp m2 = new MakeUp(1,"白雪公主");
    6
    7 m1.start();
    8 m2.start();
    9 }
    10 }
    11
    12 //口红
    13 class Lipstick{
    14 }
    15
    16 //镜子
    17 class Mirror{
    18 }
    19
    20 class MakeUp extends Thread{
    21
    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 synchronized (mirror){//一秒后获得镜子的锁
    51 System.out.println(this.girlName+"获得镜子的锁");
    52 }
    53 }
    54 }else {
    55 synchronized (mirror){//获得镜子的锁
    56 System.out.println(this.girlName+"获得了镜子的锁");
    57 Thread.sleep(2000);
    58 synchronized (lipstick){//二秒后获得口红的锁
    59 System.out.println(this.girlName+"获得口红的锁");
    60 }
    61 }
    62 }
    63 }
    64 }

    1 //死锁解决,不放在一个synchronized(){}代码块中,不让它报对方的锁
    2 public class DeadLock {
    3 public static void main(String[] args) {
    4 MakeUp m1 = new MakeUp(0,"灰姑娘");
    5 MakeUp m2 = new MakeUp(1,"白雪公主");
    6
    7 m1.start();
    8 m2.start();
    9 }
    10 }
    11
    12 //口红
    13 class Lipstick{
    14 }
    15
    16 //镜子
    17 class Mirror{
    18 }
    19
    20 class MakeUp extends Thread{
    21
    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 }else {
    55 synchronized (mirror){//获得镜子的锁
    56 System.out.println(this.girlName+"获得了镜子的锁");
    57 Thread.sleep(2000);
    58 }
    59 synchronized (lipstick){//二秒后获得口红的锁
    60 System.out.println(this.girlName+"获得口红的锁");
    61 }
    62 }
    63 }
    64 }


Lock锁

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

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

synchronized  和  Lock 对比

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

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
  • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止


应用场景:分析

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

  • 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
  • 在生产者消费者问题中,仅有synchronized是不够的
    • synchronized可阻止并发更新同一个共享资源,实现了同步
    • synchronized不能用来实现不同线程之间的消息传递(通信)
    • java提供了几个方法解决线程之间的通信问题

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

应用场景:解决方式(一) 管程法

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

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区。生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

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


应用场景:解决方式(二) 信号灯法

并发协作模型“生产者/消费者模式”--->信号灯法

1 //测试生产者消费者问题2:信号灯法,标志位解决
2 public class TestPc2 {
3 public static void main(String[] args) {
4 TV tv = new TV();
5 new Player(tv).start();
6 new Wather(tv).start();
7
8 }
9
10 }
11 //生产者--->演员
12 class Player extends Thread{
13 TV tv;
14 public Player(TV tv){
15 this.tv=tv;
16 }
17
18 @Override
19 public void run() {
20 for (int i = 0; i < 20; i++) { 21 if (i%2==0){ 22 this.tv.play("话剧"); 23 }else { 24 this.tv.play("相声"); 25 } 26 } 27 } 28 } 29 30 //消费者--->观众
31 class Wather extends Thread{
32 TV tv;
33 public Wather(TV tv){
34 this.tv=tv;
35 }
36
37 @Override
38 public void run() {
39 for (int i = 0; i < 20; i++) { 40 tv.watch(); 41 } 42 } 43 } 44 45 //产品--->节目
46 class TV{
47 //演员表演,观众等待 T
48 //观众鼓掌,演员等待 F
49 String voice;//表演的节目
50 boolean flag=true;
51
52 //表演
53 public synchronized void play(String voice){
54 if (!flag){
55 try {
56 this.wait();
57 } catch (InterruptedException e) {
58 e.printStackTrace();
59 }
60 }
61 System.out.println("演员表演了:"+voice);
62 //通知观众鼓掌
63 this.voice=voice;
64 this.notifyAll();//通知唤醒
65 this.flag = !this.flag;
66 }
67
68 //鼓掌
69 public synchronized void watch(){
70 if (flag){
71 try {
72 this.wait();
73 } catch (InterruptedException e) {
74 e.printStackTrace();
75 }
76 }
77 System.out.println(voice+"观众鼓过掌了");
78 //通知演员继续表演
79 this.notifyAll();
80 this.flag = !this.flag;
81 }
82 }

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具
  • 好处:
    • 提高响应速度(减少了创建新线程的时间)
    • 降低资源消耗(重复利用线程池中的线程,不需要每次创建)
    • 便于线程管理
      • corePoolSize:核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间会终止

Callable在上面创建线程里面已经讲述过了,下面是使用Runnable实现线程池操作

1 //测试线程池-Runnable
2 public class TestPool {
3 public static void main(String[] args) {
4 //1.创建服务,创建线程池
5 ExecutorService service = Executors.newFixedThreadPool(10);//参数为线程池大小
6
7 //执行
8 service.execute(new MyThread());
9 service.execute(new MyThread());
10 service.execute(new MyThread());
11 service.execute(new MyThread());
12
13 //2.关闭连接
14 service.shutdown();
15 }
16 }
17
18 class MyThread implements Runnable{
19 @Override
20 public void run() {
21 System.out.println(Thread.currentThread().getName());
22 }
23 }