tyro_wang

导航

多线程

多线程

1、线程简介

任务,进程,线程,多线程

  • Process与Thread

    • 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

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

    • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。

    • 注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,以为切换的很快,所以就有同时执行的错觉。

  • 本章核心概念

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

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

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

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

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

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

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

2、线程实现(重点)

Thread、Runnable、Callable

三种创建方式:

     1. Thread Class       继承Thread类(重点)
  2. Runnable接口     实现Runnable接口(重点)
  3. Callable接口       实现Callable接口(了解)

2.1、Thread

  • 自定义线程类继承Thread类

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

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

 //创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
 
 //总结:注意,线程开启不一定立即执行,由CPU调度执行
 
 public class TestThread1 extends Thread{
     @Override
     public void run() {
         //run方法线程体
         for (int i = 0; i < 20; i++) {
             System.out.println("我在看代码---"+i);
        }
    }
 
     public static void main(String[] args) {
         //main线程,主线程
         //创建一个线程对象
         TestThread1 testThread1 = new TestThread1();
         //调用start()方法开启线程
         testThread1.start();
 
         for (int i = 0; i < 200; i++) {
             System.out.println("我在学习多线程---"+i);
        }
    }
 }
 
 我在学习多线程---0
 我在学习多线程---1
 我在学习多线程---2
 我在学习多线程---3
 我在学习多线程---4
 我在学习多线程---5
 我在学习多线程---6
 我在学习多线程---7
 我在学习多线程---8
 我在学习多线程---9
 我在学习多线程---10
 我在看代码---0
 我在看代码---1
 我在看代码---2
 我在看代码---3
 我在学习多线程---11
 我在学习多线程---12
 我在学习多线程---13
 我在学习多线程---14
 我在学习多线程---15
 我在学习多线程---16
 我在学习多线程---17
 我在学习多线程---18
 我在学习多线程---19
 我在学习多线程---20
 我在学习多线程---21
 我在看代码---4
 我在看代码---5
 我在看代码---6
 我在看代码---7
 案例一:多线程实现图片下载
     
 import org.apache.commons.io.FileUtils;
 
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 
 //练习Thread,实现多线程同步下载图片
 public class TestThread2 extends Thread {
     private String url; //网络图片地址
     private String name;  //保存的文件名
 
     public TestThread2(String url, String name){
         this.url = url;
         this.name = name;
    }
 
     //下载图片线程的执行体
     @Override
     public void run() {
         WebDownloader webDownloader = new WebDownloader();
         webDownloader.downloader(url,name);
         System.out.println("下载了文件名为:"+name);
    }
 
     public static void main(String[] args) {
         TestThread2 t1 = new TestThread2("", "1.jpg");
         TestThread2 t2 = new TestThread2("", "2.jpg");
         TestThread2 t3 = new TestThread2("", "3.jpg");
 
         t1.start();
         t2.start();
         t3.start();
    }
 }
 
 //下载器
 class WebDownloader{
     //下载方法
     public void downloader(String url,String name){
         try {
             FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
             e.printStackTrace();
             System.out.println("IO异常,downloader方法出现问题");
        }
 
    }
 }
 
 下载了文件名为:2.jpg
 下载了文件名为:1.jpg
 下载了文件名为:3.jpg
 
 Process finished with exit code 0

2.2、实现Runnable

  • 定义MyRunnable类实现Runnable接口

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

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

 //创建线程方式:实现runnable接口,重写run方法,
 //执行线程需要丢入runnable接口实现类,调用start方法。
 public class TestThread3 implements Runnable{
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             System.out.println("我在看代码---"+i);
        }
    }
 
     public static void main(String[] args) {
         //创建runnbale接口的实现类对象
         TestThread3 testThread3 = new TestThread3();
         //创建线程对象,通过线程对象来开启我们的线程,代理
         //Thread thread = new Thread(testThread3);
         //thread.start();
         new Thread(testThread3).start();
 
         for (int i = 0; i < 30; i++) {
             System.out.println("我在学习多线程---"+i);
        }
    }
 }
 我在学习多线程---0
 我在学习多线程---1
 我在学习多线程---2
 我在学习多线程---3
 我在学习多线程---4
 我在学习多线程---5
 我在看代码---0
 我在看代码---1
 我在看代码---2
 我在看代码---3
 我在看代码---4
 我在看代码---5
 我在看代码---6
 我在看代码---7
 我在看代码---8
 我在看代码---9
 我在看代码---10
 我在看代码---11
 我在学习多线程---6
 我在学习多线程---7
 我在学习多线程---8
 import org.apache.commons.io.FileUtils;
 
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 
 //练习Thread,实现多线程同步下载图片
 public class TestThread2 implements Runnable {
     private String url; //网络图片地址
     private String name;  //保存的文件名
 
     public TestThread2(String url, String name){
         this.url = url;
         this.name = name;
    }
 
     //下载图片线程的执行体
     @Override
     public void run() {
         WebDownloader webDownloader = new WebDownloader();
         webDownloader.downloader(url,name);
         System.out.println("下载了文件名为:"+name);
    }
 
     public static void main(String[] args) {
         TestThread2 t1 = new TestThread2("https://img1.baidu.com/it/u=1677307154,4128866455&fm=26&fmt=auto", "1.jpg");
         TestThread2 t2 = new TestThread2("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fdpic.tiankong.com%2Ftc%2Feb%2FQJ9124407543.jpg&refer=http%3A%2F%2Fdpic.tiankong.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1639645067&t=17627f99d78bfecb5d92e48f468a9d6c", "2.jpg");
         TestThread2 t3 = new TestThread2("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.2008php.com%2F09_Website_appreciate%2F10-07-11%2F1278861720_g.jpg&refer=http%3A%2F%2Fwww.2008php.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1639645048&t=f2f52dfe685f3268af142c5324a34985", "3.jpg");
 
         new Thread(t1).start();
         new Thread(t2).start();
         new Thread(t3).start();
    }
 }
 
 //下载器
 class WebDownloader{
     //下载方法
     public void downloader(String url,String name){
         try {
             FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
             e.printStackTrace();
             System.out.println("IO异常,downloader方法出现问题");
        }
 
    }
 }
 下载了文件名为:1.jpg
 下载了文件名为:2.jpg
 下载了文件名为:3.jpg
 
 Process finished with exit code 0

小结

  • 继承Thread类

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

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

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

  • 实现Runnable接口

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

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

    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

     //一份资源
     StartThread4 station = new StartThread4();
     
     //多个代理
     new Thread(station,"小明").start();
     new Thread(station,"老师").start();
     new Thread(station,"小红").start();
     //案例二:
     //多个线程同时操作同一个对象
     //买火车票的例子
     
     //发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱。例如:老师和小明同时拿了第一张票
     public class TestThread4 implements Runnable {
         //票数
         private int ticketNums = 10;
     
     
         @Override
         public void run() {
             while (true){
                 if (ticketNums <= 0){
                     break;
                }
                 //模拟延时
                 try {
                     Thread.sleep(200);
                } catch (InterruptedException e) {
                     e.printStackTrace();
                }
                 System.out.println(Thread.currentThread().getName()+"-->拿到了第" + ticketNums-- + "票");
            }
        }
     
         public static void main(String[] args) {
             TestThread4 ticket = new TestThread4();
     
             new Thread(ticket,"小明").start();
             new Thread(ticket,"老师").start();
             new Thread(ticket,"小红").start();
        }
     }
     小明-->拿到了第10票
     老师-->拿到了第9票
     小红-->拿到了第8票
     老师-->拿到了第7票
     小明-->拿到了第6票
     小红-->拿到了第5票
     小明-->拿到了第4票
     老师-->拿到了第3票
     小红-->拿到了第2票
     老师-->拿到了第1票
     小明-->拿到了第1票
     小红-->拿到了第0票
     
     Process finished with exit code 0

    案例:龟兔赛跑-Race

    1. 首先来个赛道距离,然后要离终点越来越近

    2. 判断比赛是否结束

    3. 打印出胜利者

    4. 龟兔赛跑开始

    5. 故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉

    6. 终于,乌龟赢得比赛

     public class Race implements Runnable {
     
         //胜利者
         private static String winner;
     
         @Override
         public void run() {
             for (int i = 0; i <= 100; i++) {
     
                 //模拟兔子休息
                 if (Thread.currentThread().getName().equals("兔子")&& i%10==0){
                     try {
                         Thread.sleep(1);
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    }
                }
     
                 //判断比赛是否结束
                 boolean flag = gameOver(i);
                 //如果比赛结束了,就停止程序
                 if (flag){
                     break;
                }
                 System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
            }
        }
         //判断是否完成比赛
         private boolean gameOver(int steps){
             //判断是否有胜利者
             if (winner!=null){//已经存在胜利者
                 return true;
            }else {
                 if (steps>=100){
                     winner = Thread.currentThread().getName();
                     System.out.println("winner is " + winner);
                     return true;
                }
            }
             return false;
        }
     
         public static void main(String[] args) {
             Race race = new Race();//赛道只有一条
             new Thread(race,"兔子").start();
             new Thread(race,"乌龟").start();
        }
     
     }
     乌龟-->跑了94步
     乌龟-->跑了95步
     乌龟-->跑了96步
     乌龟-->跑了97步
     乌龟-->跑了98步
     乌龟-->跑了99步
     winner is 乌龟
     
     Process finished with exit code 0

2.3、Callable

 1. 实现Callable接口,需要返回值类型
 2. 重写call方法,需要抛出异常
 3. 创建目标对象
 4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
 5. 提交执行:Future<Boolean> result1 = ser.submit(t1);
 6. 获取结果:boolean r1 = result1.get()
 7. 关闭服务:ser.shutdownNow();
 //线程创建方式三:实现callable接口
 /*
 callable好处
 1.可以定义返回值。
 2.可以抛出异常。
 * */
 public class TestCallable implements Callable<Boolean> {
     private String url; //网络图片地址
     private String name;  //保存的文件名
 
     public TestCallable(String url, String name){
         this.url = url;
         this.name = name;
    }
 
     //下载图片线程的执行体
     @Override
     public Boolean call() {
         WebDownloader webDownloader = new WebDownloader();
         webDownloader.downloader(url,name);
         System.out.println("下载了文件名为:"+name);
         return true;
    }
 
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         TestCallable t1 = new TestCallable("https://img1.baidu.com/it/u=1677307154,4128866455&fm=26&fmt=auto", "1.jpg");
         TestCallable t2 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fdpic.tiankong.com%2Ftc%2Feb%2FQJ9124407543.jpg&refer=http%3A%2F%2Fdpic.tiankong.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1639645067&t=17627f99d78bfecb5d92e48f468a9d6c", "2.jpg");
         TestCallable t3 = new TestCallable("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fwww.2008php.com%2F09_Website_appreciate%2F10-07-11%2F1278861720_g.jpg&refer=http%3A%2F%2Fwww.2008php.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1639645048&t=f2f52dfe685f3268af142c5324a34985", "3.jpg");
 
         //创建执行服务:
         ExecutorService ser = Executors.newFixedThreadPool(3);
         //提交执行
         Future<Boolean> r1 = ser.submit(t1);
         Future<Boolean> r2 = ser.submit(t2);
         Future<Boolean> r3 = ser.submit(t3);
 
         //获取结果
         Boolean rs1 = r1.get();
         Boolean rs2 = r2.get();
         Boolean rs3 = r3.get();
 
         //关闭服务
         ser.shutdownNow();
    }
 }
 
 
 //下载器
 class WebDownloader{
     //下载方法
     public void downloader(String url,String name){
         try {
             FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
             e.printStackTrace();
             System.out.println("IO异常,downloader方法出现问题");
        }
 
    }
 }
 下载了文件名为:2.jpg
 下载了文件名为:1.jpg
 下载了文件名为:3.jpg
 
 Process finished with exit code 0

 

2.4、静态代理

 //静态代理模式总结:
 //真实对象和代理对象都要实现同一个接口
 //代理对象要代理真实角色
 
 //好处:
     //代理对象可以做很多真实对象做不了的事情
     //真实对象专注做自己的事情
 
 public class StacticProxy {
 
     public static void main(String[] args) {
         You you = new You();//你要结婚
         WeddingCompany weddingCompany = new WeddingCompany(you);
         weddingCompany.HappyMarry();
    }
 }
 
 interface Marry{
     //人间四大喜事
         //久旱逢甘露
         //他乡遇故知
         //洞房花烛夜
         //金榜题名时
     void HappyMarry();
 }
 
 //真实角色,你去结婚
 class You implements Marry{
     @Override
     public void HappyMarry() {
         System.out.println("结婚了,超开心");
    }
 }
 
 //代理角色,帮助你结婚
 class WeddingCompany implements Marry{
 
     //代理谁--》真实目标角色
     private Marry target;
 
     public WeddingCompany(Marry target) {
         this.target = target;
    }
 
     @Override
     public void HappyMarry() {
         before();
         this.target.HappyMarry();//这就是真实对象
         after();
    }
 
     private void after() {
         System.out.println("结婚之后,收尾款");
    }
 
     private void before() {
         System.out.println("结婚之前,布置现场");
    }
 }
 结婚之前,布置现场
 结婚了,超开心
 结婚之后,收尾款
 
 Process finished with exit code 0

2.5、Lambda表达式

  • 理解Functional Interface(函数式接口)是学习Java 8 lambda表达式的关键所在。

  • 函数式接口定义:

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

     public interface Runnable{
         public abstract void run();
     }
    • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

    • λ希腊字母表中排序第十一位的字母,英文名称为Lambda

    • 避免匿名内部类定义过多

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

(params)->expression[表达式]
(params)->statement[语句]
(params)->{statements}

a-> System.out.println("i like lambda-->"+a);
 /*
 推导lambda表达式
 * */
 public class TestLambda1 {
 
     //3.静态内部类
     static class Like2 implements ILike{
         @Override
         public void lambda() {
             System.out.println("I like lambda2");
        }
    }
 
     public static void main(String[] args) {
         ILike like = new Like();
         like.lambda();
 
         like = new Like2();
         like.lambda();
 
         //4.局部内部类
         class Like3 implements ILike{
             @Override
             public void lambda() {
                 System.out.println("I like lambda3");
            }
        }
         like = new Like3();
         like.lambda();
 
         //5.匿名内部类,没有类的名称,必须借助接口或者父类
         like = new ILike() {
             @Override
             public void lambda() {
                 System.out.println("i like lambda4");
            }
        };
         like.lambda();
 
         //6.用lambda简化
         like = () ->{
             System.out.println("I like lambda5");
        };
         like.lambda();
 
 
    }
 }
 
 //1.定义一个函数接口
 interface ILike{
     void lambda();
 }
 
 //2.实现类
 class Like implements ILike{
     @Override
     public void lambda() {
         System.out.println("I like lambda");
    }
 }
 
I like lambda
I like lambda2
I like lambda3
i like lambda4
I like lambda5

Process finished with exit code 0

小结

  • 为什么要使用lambda表达式

    • 避免匿名内部类定义过多

    • 可以让你的代码看起来很简洁

    • 去掉了一堆没有意义的代码,只留下核心的逻辑。

  • 也许你会说,我看了Lambda表达式,不但不觉得简洁,反而觉得更乱,看不懂了。那是因为我们还没有习惯,用的多了,看习惯了,就好了。

 public class TestLambda2 {
     //静态内部类
     public static void main(String[] args) {
         ILove love1 = null;
         //lambda表达式
         love1 = (int a)-> {
             System.out.println("i love you->"+a);
        };
 
         //简化一:去掉参数类型
         love1 = (a)->{
             System.out.println("i love you->"+a);
        };
 
         //简化二:去掉()
         love1 = a->{
             System.out.println("i love you->"+a);
        };
 
         //简化三:去掉{}
         love1 = a -> System.out.println("i love you->"+a);
 
         love1.love(520);
 
         //总结:
             //lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。
             //前提是接口为函数式接口
             //多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
    }
 }
 
 interface ILove {
     void love(int a);
 }
 
 i love you->520
 
 Process finished with exit code 0

总结

  1. lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。

  2. 前提是接口为函数式接口。

  3. 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号。

 

3、线程状态

五大状态

image-20211117110859058

 

 

image-20211117115528704

 

 

3.1、停止线程

- 不推荐使用JDK提供的stop()、destroy()方法。【已废弃】
- 推荐线程自己停止下来
- 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。
 //测试stop
 //1.建议现场正常停止--->利用次数,不建议死循环。
 //2.建议使用标志位--->设置一个标志位
 //3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
 
 public class TestStop implements Runnable{
     //1.设置一个标识位
     private boolean flag =true;
 
     @Override
     public void run() {
         int i = 0;
         while (flag){
             System.out.println("run....Thread"+i++);
        }
    }
 
     //2.设置一个公开的方法停止线程,转换标志位
     public void stop(){
         this.flag = false;
    }
 
     public static void main(String[] args) {
         TestStop testStop = new TestStop();
         new Thread(testStop).start();
 
         for (int i = 0; i < 1000; i++) {
             System.out.println("main"+i);
             if (i==900){
                 //调用stop方法切换标志位,让线程停止
                 testStop.stop();
                 System.out.println("线程该停止了");
            }
 
        }
 
    }
 }
run....Thread986
run....Thread987
run....Thread988
run....Thread989
main893
main894
main895
main896
main897
main898
main899
main900
run....Thread990
线程该停止了
main901
main902

 

3.2、线程休眠

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

  • sleep存在异常InterruptedException;

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

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

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

//模拟网络延时:放大问题的发生性。

public class TestSleep implements Runnable {
//票数
private int ticketNums = 10;

@Override
public void run() {
while (true){
if (ticketNums <= 0){
break;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"票");
}
}

public static void main(String[] args) {
TestSleep testSleep = new TestSleep();
new Thread(testSleep,"小明").start();
new Thread(testSleep,"老师").start();
new Thread(testSleep,"黄牛党").start();
}
}
//模拟倒计时,,,
public class TestSleep2 {

public static void main(String[] args) {
try {
tenDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
}
}
}
 //打印当前系统时间
 public class TestSleep2 {
 
     public static void main(String[] args) {
         //打印当前系统时间
         Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间
         
         while (true){
             try {
                 Thread.sleep(1000);
                 System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                 startTime = new Date(System.currentTimeMillis());//更新当前时间
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
    }
 
     public static void tenDown() throws InterruptedException {
         int num = 10;
         while (true){
             Thread.sleep(1000);
             System.out.println(num--);
             if (num<=0){
                 break;
            }
        }
    }
 }

 

3.3、线程礼让

 - 礼让线程,让当前正在执行的线程暂停,但不阻塞
 - 将线程从运行状态转为就绪状态
 - ***让cpu重新调度,礼让不一定成功!看cpu心情***
 //测试礼让线程
 //礼让不一定成功,看cpu心情
 public class TestYield {
 
     public static void main(String[] args) {
         MyYield myYield = new MyYield();
         new Thread(myYield,"a").start();
         new Thread(myYield,"b").start();
    }
 
 }
 
 class MyYield implements Runnable{
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName()+"线程开始执行");
         Thread.yield();//礼让
         System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
 }
 //礼让失败
 a线程开始执行
 a线程停止执行
 b线程开始执行
 b线程停止执行
     
     或者
 //礼让成功
 a线程开始执行
 b线程开始执行
 b线程停止执行
 a线程停止执行
 

 

3.4 线程Join

 - Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
 - 可以想象成插队
 //测试join方法  想象为插队
 public class TestJoin implements Runnable {
     @Override
     public void run() {
         for (int i = 0; i < 5; i++) {
             System.out.println("线程vip来了"+i);
        }
 
    }
 
     public static void main(String[] args) throws InterruptedException {
 
         //启动我们的线程
         TestJoin testJoin = new TestJoin();
         Thread thread = new Thread(testJoin);
         thread.start();
         //主线程
         for (int i = 0; i < 100; i++) {
             if (i==20){
                 thread.join();//插队
            }
             System.out.println("main"+i);
        }
    }
 }
 
 main0
 main1
 线程vip来了0
 线程vip来了1
 线程vip来了2
 main2
 main3
 main4
 main5
 main6
 main7
 main8
 main9
 main10
 main11
 main12
 main13
 main14
 main15
 main16
 main17
 main18
 main19
 线程vip来了3
 线程vip来了4
 main20
 main21
 main22
 main23

 

3.5线程状态

  • Thread.State

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

    • NEW

      尚未启动的线程处于此状态。

    • RUNNABLE

      在Java虚拟机中执行的线程处于此状态。

    • BLOCKED

      被阻塞等待监视器锁定的线程处于此状态。

    • WAITING

      正在等待另一个线程执行特定动作的线程处于此状态。

    • TIMED_WAITING

      正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。

    • TERMINATED

      已退出的线程处于此状态。

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

    public class TestState {

    public static void main(String[] args) throws InterruptedException {

    Thread thread = new Thread(()->{
    for (int i = 0; i < 5; i++) {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    System.out.println("/////////");
    });

    //观察状态
    Thread.State state = thread.getState();
    System.out.println(state);//NEW

    //观察启动后状态
    thread.start();
    state = thread.getState();
    System.out.println(state);

    //只要线程不终止,就一直输出状态
    while (state != Thread.State.TERMINATED){
    Thread.sleep(100);
    state = thread.getState();//更新线程状态
    System.out.println(state);//输出状态
    }
    }
    }
     NEW
     RUNNABLE
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     TIMED_WAITING
     /////////
     TERMINATED
     
     Process finished with exit code 0

     

3.6线程优先级

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

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

    • Thread.MIN_PRIORITY = 1;

    • Thread.MAX_PRIORITY = 10;

    • Thread.NORM_PRIORITY = 5;

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

    • getPriority().setPriority(int xxx)

 //测试线程的优先级
 public class TestPriority {
 
     public static void main(String[] args) {
         System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
 
         MyPriority myPriority = new MyPriority();
 
         Thread t1 = new Thread(myPriority);
         Thread t2 = new Thread(myPriority);
         Thread t3 = new Thread(myPriority);
         Thread t4 = new Thread(myPriority);
         Thread t5 = new Thread(myPriority);
         Thread t6 = new Thread(myPriority);
 
         //先设置优先级,再启动
         t1.start();
 
         t2.setPriority(1);
         t2.start();
 
         t3.setPriority(4);
         t3.start();
 
         t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY = 10
         t4.start();
 
         //t5.setPriority(-1);
         //t5.start();
 
         //t6.setPriority(11);
         //t6.start();
    }
 }
 
 class MyPriority implements Runnable{
 
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
 }
 
 main-->5
 Thread-0-->5
 Thread-3-->10
 Thread-2-->4
 Thread-1-->1
 
 Process finished with exit code 0

 

3.7守护(daemon)线程

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

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

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

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

 //测试守护线程
 //上帝守护你
 public class TestDaemon {
 
     public static void main(String[] args) {
         God god = new God();
         You you = new You();
 
         Thread thread = new Thread(god);
         thread.setDaemon(true);//默认是false表示是用户线程,正常的线程都是用户线程...
         thread.start();//上帝守护线程启动
 
         new Thread(you).start();//你   用户线程启动...
    }
 }
 
 //上帝
 class God implements Runnable{
 
     @Override
     public void run() {
         while (true){
             System.out.println("上帝保佑着你");
        }
    }
 }
 
 //你
 class You implements Runnable{
 
     @Override
     public void run() {
         for (int i = 0; i < 36500; i++) {
             System.out.println("你一生都开心的活着");
        }
         System.out.println("-=======goodbye!world!=======");//Hello,World!
    }
 }
 上帝保佑着你
 上帝保佑着你
 上帝保佑着你
 上帝保佑着你
 Process finished with exit code 0

 

4、线程同步(重点)

多个线程操作同一个资源

4.1、并发

并发:同一个对象对多个线程同时操作

 

4.2、线程同步

  • 现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题,比如,食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队.一个个来.

  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池 形成队列,等待前面线程使用完毕,下一个线程在使用。

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

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

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

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

4.3、三个不安全案例

 //不安全的买票
 //现场不安全,有0
 public class UnsafeBuyTicket {
     public static void main(String[] args) {
         BuyTicket station = new BuyTicket();
 
         new Thread(station,"张三").start();
         new Thread(station,"李四").start();
         new Thread(station,"王五").start();
    }
 
 }
 
 //
 class BuyTicket implements Runnable{
     //票
     private int ticketNums = 10;
     boolean flag = true;//外部停止方式
 
 
     @Override
     public void run() {
         //买票
         while (flag){
             try {
                 buy();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
    }
 
     private void buy() throws InterruptedException {
         //判断是否有票
         if (ticketNums <= 0){
             flag = false;
             return;
        }
         //模拟延时
         Thread.sleep(100);
         //买票
         System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
    }
 }
 张三拿到10
 李四拿到8
 王五拿到9
 李四拿到7
 张三拿到5
 王五拿到6
 张三拿到4
 王五拿到3
 李四拿到4
 张三拿到2
 李四拿到1
 王五拿到0
 
 Process finished with exit code 0
 //不安全的取钱
 //连个人去银行取钱,账户
 public class UnsafeBank {
 
     public static void main(String[] args) {
         //账户
         Account account = new Account(100, "结婚基金");
 
         Drawing you = new Drawing(account,50,"你");
         Drawing girlFriend = new Drawing(account,100,"girlFriend");
 
         you.start();
         girlFriend.start();
 
    }
 }
 
 //账户
 class Account{
     int money;//余额
     String name;//卡号
 
     public Account(int money, String name) {
         this.money = money;
         this.name = name;
    }
 }
 
 //银行:模拟取款
 class Drawing extends Thread{
     Account account;//账户
     //取了多少钱
     int drawingMoney;
     //现在手里有多少钱
     int nowMoney;
     public Drawing(Account account,int drawingMoney,String name){
         super(name);
         this.account = account;
         this.drawingMoney = drawingMoney;
    }
 
     //取钱
     @Override
     public void run() {
         //判断有没有钱
         if (account.money-drawingMoney < 0){
             System.out.println(Thread.currentThread().getName()+"余额不足,取不了");
             return;
        }
 
         //sleep可以放大问题的发生性
         try {
             Thread.sleep(1000);
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
 
         //卡内余额 = 余额 - 你取的钱
         account.money = account.money - drawingMoney;
         //你手里的钱
         nowMoney = nowMoney + drawingMoney;
 
         System.out.println(account.name+"余额为:"+ account.money);
         //this.getName() = Thread.currentThread().getName()
         System.out.println(this.getName()+"手里的钱:"+nowMoney);
 
    }
 }
 
 结婚基金余额为:-50
 结婚基金余额为:-50
 girlFriend手里的钱:100
 你手里的钱:50
 
 Process finished with exit code 0
 import java.util.ArrayList;
 import java.util.List;
 
 //线程不安全的集合
 public class UnsafeList {
 
     public static void main(String[] args) {
 
         List<String> list = new ArrayList<String>();
 
         for (int i = 0; i < 10000; i++) {
             new Thread(()->{
                 list.add(Thread.currentThread().getName());
            }).start();
        }
 
         try {
             Thread.sleep(3000);
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
 
         System.out.println(list.size());
    }
 }
 
 9999
 
 Process finished with exit code 0

 

4.4、同步方法

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized方法 和 synchronized块。

     同步方法:public synchronized void method(int args){}
  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占改锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

     缺陷:若将一个大的方法申明为synchronized将会影响效率
  • 同步方法弊端

    • 方法里面需要修改的内容才需要锁,锁的太多,浪费资源

  •  

4.5、同步快

  • 同步快:synchronized(Obj){}

  • Obj称之为 同步监视器

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

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

  • 同步监视器的执行过程

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

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

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

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

     //不安全的买票
     //加上锁
     public class UnsafeBuyTicket {
        public static void main(String[] args) {
            BuyTicket station = new BuyTicket();
     
            new Thread(station,"张三").start();
            new Thread(station,"李四").start();
            new Thread(station,"王五").start();
        }
     
     }
     
     //
     class BuyTicket implements Runnable{
        //票
        private int ticketNums = 10;
        boolean flag = true;//外部停止方式
     
     
        @Override
        public void run() {
            //买票
            while (flag){
                try {
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
     
        //synchronized 同步方法,锁的是this
        private synchronized void buy() throws InterruptedException {
            //判断是否有票
            if (ticketNums <= 0){
                flag = false;
                return;
            }
            //模拟延时
            Thread.sleep(100);
            //买票
            System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
        }
     }
     张三拿到10
     张三拿到9
     张三拿到8
     张三拿到7
     张三拿到6
     张三拿到5
     张三拿到4
     张三拿到3
     张三拿到2
     张三拿到1
     
     Process finished with exit code 0
     //不安全的取钱
     //连个人去银行取钱,账户
     public class UnsafeBank {
     
         public static void main(String[] args) {
             //账户
             Account account = new Account(100, "结婚基金");
     
             Drawing you = new Drawing(account,50,"你");
             Drawing girlFriend = new Drawing(account,100,"girlFriend");
     
             you.start();
             girlFriend.start();
     
        }
     }
     
     //账户
     class Account{
         int money;//余额
         String name;//卡号
     
         public Account(int money, String name) {
             this.money = money;
             this.name = name;
        }
     }
     
     //银行:模拟取款
     class Drawing extends Thread{
         Account account;//账户
         //取了多少钱
         int drawingMoney;
         //现在手里有多少钱
         int nowMoney;
         public Drawing(Account account,int drawingMoney,String name){
             super(name);
             this.account = account;
             this.drawingMoney = drawingMoney;
        }
     
         //取钱
         //synchronized 默认锁的是this.
         @Override
         public void run() {
      //锁的对象就是变化的量,需要增删改的对象
             synchronized (account){
                 //判断有没有钱
                 if (account.money-drawingMoney < 0){
                     System.out.println(Thread.currentThread().getName()+"余额不足,取不了");
                     return;
                }
     
                 //sleep可以放大问题的发生性
                 try {
                     Thread.sleep(1000);
                } catch (InterruptedException e) {
                     e.printStackTrace();
                }
     
                 //卡内余额 = 余额 - 你取的钱
                 account.money = account.money - drawingMoney;
                 //你手里的钱
                 nowMoney = nowMoney + drawingMoney;
     
                 System.out.println(account.name+"余额为:"+ account.money);
                 //this.getName() = Thread.currentThread().getName()
                 System.out.println(this.getName()+"手里的钱:"+nowMoney);
            }
        }
     }
     
     结婚基金余额为:50
     你手里的钱:50
     girlFriend余额不足,取不了
     
     Process finished with exit code 0
     import java.util.ArrayList;
     import java.util.List;
     
     //线程不安全的集合
     public class UnsafeList {
     
         public static void main(String[] args) {
     
             List<String> list = new ArrayList<String>();
     
             for (int i = 0; i < 10000; i++) {
                 new Thread(()->{
                     synchronized (list){
                         list.add(Thread.currentThread().getName());
                    }
                }).start();
            }
     
             try {
                 Thread.sleep(3000);
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
     
             System.out.println(list.size());
        }
     }
     10000
     
     Process finished with exit code 0

     

4.6、扩展JUC

 import java.util.concurrent.CopyOnWriteArrayList;
 
 //测试JUC安全类型的集合
 public class TestJUC {
     public static void main(String[] args) {
         CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
         for (int i = 0; i < 10000; i++) {
             new Thread(()->{
                 list.add(Thread.currentThread().getName());
            }).start();
        }
 
         try {
             Thread.sleep(1000);
        } catch (InterruptedException e) {
             e.printStackTrace();
        }
         System.out.println(list.size());
    }
 }
 10000
 
 Process finished with exit code 0

 

4.7、死锁

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

     //导致死锁
     //死锁:多个线程相互抱着对方需要的资源,然后形成僵持.
     public class DeadLock {
         public static void main(String[] args) {
             Makeup g1 = new Makeup(0, "灰姑娘");
             Makeup g2 = new Makeup(1, "白雪公主");
     
             g1.start();
             g2.start();
        }
     
     }
     
     //口红
     class Lipstick{
     
     }
     
     //镜子
     class Mirror{
     
     }
     
     //化妆
     class Makeup extends Thread{
     
         //需要的资源只有一份,用static来保证只有一份
         static Lipstick lipstick = new Lipstick();
         static Mirror mirror = new Mirror();
     
         int choice;//选择
         String girlName;//使用化妆品的人
     
         Makeup(int choice, String girlName){
             this.choice = choice;
             this.girlName = girlName;
        }
     
         @Override
         public void run() {
             try {
                 makeup();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
     
        }
     
         //化妆,互相持有对方的锁,就是需要拿到对方的资源
         private void makeup() throws InterruptedException {
             if (choice==0){
                 synchronized (lipstick){//获得口红的锁
                     System.out.println(this.girlName+"获得口红的锁");
                     Thread.sleep(1000);
                     synchronized (mirror){
                         System.out.println(this.girlName+"获得镜子的锁");
                    }
                }
            }else {
                 synchronized (mirror){//获得镜子的锁
                     System.out.println(this.getName()+"获得镜子的锁");
                     Thread.sleep(2000);
                     synchronized (lipstick){
                         System.out.println(this.getName()+"获得口红的锁");
                    }
                }
            }
        }
     }
     
     //死锁,程序无法结束
     
     灰姑娘获得口红的锁
     Thread-1获得镜子的锁
     //解决死锁
     //死锁:多个线程相互抱着对方需要的资源,然后形成僵持.
     public class DeadLock {
         public static void main(String[] args) {
             Makeup g1 = new Makeup(0, "灰姑娘");
             Makeup g2 = new Makeup(1, "白雪公主");
     
             g1.start();
             g2.start();
        }
     
     }
     
     //口红
     class Lipstick{
     
     }
     
     //镜子
     class Mirror{
     
     }
     
     //化妆
     class Makeup extends Thread{
     
         //需要的资源只有一份,用static来保证只有一份
         static Lipstick lipstick = new Lipstick();
         static Mirror mirror = new Mirror();
     
         int choice;//选择
         String girlName;//使用化妆品的人
     
         Makeup(int choice, String girlName){
             this.choice = choice;
             this.girlName = girlName;
        }
     
         @Override
         public void run() {
             try {
                 makeup();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
     
        }
     
         //化妆,互相持有对方的锁,就是需要拿到对方的资源
         private void makeup() throws InterruptedException {
             if (choice==0){
                 synchronized (lipstick){//获得口红的锁
                     System.out.println(this.girlName+"获得口红的锁");
                     Thread.sleep(1000);
                }
                 synchronized (mirror){
                     System.out.println(this.girlName+"获得镜子的锁");
                }
            }else {
                 synchronized (mirror){//获得镜子的锁
                     System.out.println(this.getName()+"获得镜子的锁");
                     Thread.sleep(2000);
                }
                 synchronized (lipstick){
                     System.out.println(this.getName()+"获得口红的锁");
                }
            }
        }
     }
     
     灰姑娘获得口红的锁
     Thread-1获得镜子的锁
     灰姑娘获得镜子的锁
     Thread-1获得口红的锁
     
     Process finished with exit code 0

    死锁避免方法

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

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

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

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

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

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

4.8、Lock锁

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

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

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

 import java.util.concurrent.locks.ReentrantLock;
 
 //测试Lock锁
 public class TestLock {
 
     public static void main(String[] args) {
         TestLock2 testLock2 = new TestLock2();
 
         new Thread(testLock2).start();
         new Thread(testLock2).start();
         new Thread(testLock2).start();
    }
 }
 
 class TestLock2 implements Runnable{
 
     int tickerNums = 10;
 
     //定义lock锁
     private final ReentrantLock lock = new ReentrantLock();
 
     @Override
     public void run() {
         while (true){
             try {
                 lock.lock();//加锁
                 if (tickerNums>0){
                     try {
                         Thread.sleep(1000);
                    } catch (InterruptedException e) {
                         e.printStackTrace();
                    }
                     System.out.println(tickerNums--);
                }else {
                     break;
                }
            }finally {
                 lock.unlock();//解锁
            }
        }
    }
 }
 10
 9
 8
 7
 6
 5
 4
 3
 2
 1
 
 Process finished with exit code 0

 

4.9、synchronized 与 Lock 的对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放

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

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

  • 优先使用顺序:

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

 

5、线程通信问题

5.1、线程协作

生产者消费者模式

5.2、线程通信

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

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

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

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

    image-20211118144600276

     

5.3、线程通信-分析

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

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

  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。

  • 在生产者消费者问题中,仅有synchronized是不够的

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

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

image-20211118145542566

 

 

5.4、管程法

image-20211118150208808

 

 //测试:生产者消费者模型-->利用缓冲区解决:管程法
 //生产者,消费者,产品,缓冲区
 public class TestPC {
 
     public static void main(String[] args) {
         SynContainer container = new SynContainer();
 
         new Productor(container).start();
         new Consumer(container).start();
    }
 }
 
 //生产者
 class Productor extends Thread{
 
     SynContainer container;
 
     public Productor(SynContainer container){
         this.container = container;
    }
 
 
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             container.push(new Chicken(i));
             System.out.println("生产了"+i+"只鸡");
        }
    }
 }
 
 //消费者
 class Consumer extends Thread{
 
     SynContainer container;
 
     public Consumer(SynContainer container){
         this.container = container;
    }
 
     @Override
     public void run() {
         for (int i = 0; i < 100; i++) {
             System.out.println("消费了-->"+container.pop().id+"只鸡");
        }
    }
 }
 
 //产品
 class Chicken{
     int id;//产品编号
 
     public Chicken(int id) {
         this.id = id;
    }
 }
 
 //缓冲区
 class SynContainer{
     //需要一个容器大小
     Chicken[] chickens = new Chicken[10];
     //容器计数器
     int count = 0;
 
     //生产者放入产品
     public synchronized void push(Chicken chicken){
         //如果容器满了,就需要等待消费者消费
         if (count==chickens.length){
             //通知消费者消费,生产等待
             try {
                 this.wait();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
         //如果没有满,我们就需要丢入产品
         chickens[count]=chicken;
         count++;
 
         //可以通知消费者消费了.
         this.notifyAll();
    }
 
     //消费者消费产品
     public synchronized Chicken pop(){
         //判断能否消费
         if (count==0){
             //等待生产者生产,消费者等待
             try {
                 this.wait();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
 
         //如果可以消费
         count--;
         Chicken chicken = chickens[count];
 
         //吃完了,通知生产者生产
         this.notifyAll();
         return chicken;
    }
 }
 
 生产了0只鸡
 生产了1只鸡
 生产了2只鸡
 生产了3只鸡
 生产了4只鸡
 生产了5只鸡
 生产了6只鸡
 生产了7只鸡
 生产了8只鸡
 生产了9只鸡
 消费了-->9只鸡
 生产了10只鸡
 生产了11只鸡
 消费了-->10只鸡
 消费了-->11只鸡
 消费了-->8只鸡
 消费了-->12只鸡
 消费了-->7只鸡

 

5.5、信号灯法

 //测试生产者消费者问题2:信号灯法,标志位解决
 public class TestPc2 {
 
     public static void main(String[] args) {
         TV tv = new TV();
         new Player(tv).start();
         new Watcher(tv).start();
    }
 }
 
 //生产者-->演员
 class Player extends Thread{
 
     TV tv;
     public Player(TV tv){
         this.tv = tv;
    }
 
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             if (i%2==0){
                 this.tv.play("快乐大本营播放中");
            }else {
                 this.tv.play("抖音:记录美好生活");
            }
        }
    }
 }
 
 //消费者-->观众
 class Watcher extends Thread{
     TV tv;
     public Watcher(TV tv){
         this.tv = tv;
    }
 
     @Override
     public void run() {
         for (int i = 0; i < 20; i++) {
             tv.watch();
        }
    }
 }
 
 //产品-->节目
 class TV{
     //演员表演,观众等待
     //观众观看,演员等待
     String voice;//表演的节目
     boolean flag = true;
 
     //表演
     public synchronized void play(String voice){
         if (!flag){
             try {
                 this.wait();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
         System.out.println("演员表演了:"+voice);
         //通知观众观看
         this.notifyAll();//通知唤醒
         this.voice = voice;
         this.flag = !this.flag;
    }
 
     //观看
     public synchronized void watch(){
         if (flag){
             try {
                 this.wait();
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
        }
 
         System.out.println("观看了:"+voice);
         //通知演员表演
         this.notifyAll();
         this.flag = !this.flag;
    }
 
 }
 
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 演员表演了:快乐大本营播放中
 观看了:快乐大本营播放中
 演员表演了:抖音:记录美好生活
 观看了:抖音:记录美好生活
 
 Process finished with exit code 0

 

5.6、线程池

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

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

  • 好处:

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

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

    • 便于程序管理(....)

      • corePoolSize:核心池的大小

      • maximumPoolSize:最大线程数

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

5.6.1、使用线程池

  • JDK5.0起提供了线程池相关API:ExecutorService和Executors

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

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

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

    • void shutdown():关闭连接池

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

 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 //测试线程池
 public class TestPool {
     public static void main(String[] args) {
         //1.创建服务,创建线程池
         //newFixedThreadPool 参数为:线程池数量
         ExecutorService service = Executors.newFixedThreadPool(10);
 
         //执行
         service.execute(new MyThread());
         service.execute(new MyThread());
         service.execute(new MyThread());
         service.execute(new MyThread());
 
         //2.关闭链接
         service.shutdown();
    }
 }
 
 class MyThread implements Runnable{
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName());
    }
 }
 pool-1-thread-1
 pool-1-thread-2
 pool-1-thread-4
 pool-1-thread-3
 
 Process finished with exit code 0

 

6、总结

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2021-11-18 17:03  tyro_wang  阅读(22)  评论(0)    收藏  举报