Java多线程详解

目录

1、进程与线程

2、创建线程的方法

1、继承Thread类

2、实现runnable接口,重写run方法~

3、实现Callable接口(了解即可)

3、线程状态

1、停止线程

2、线程休眠

3、线程礼让

4、线程合并

5、观测线程状态

6、线程优先级

7、守护线程

8、线程同步

4、三大不安全案例

1、买票案例

2、取钱案例

3、ArrayList案例

5、线程同步方法

1、synchronized方法

2、synchronized同步块

6、JUC-CopyOnWriteArrayList

7、死锁和锁

1、死锁

2、锁

8、线程通信

1、解决方法一:管程法

2、解决方法二:信号灯法

9、线程池


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

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

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

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

核心概念:

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

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

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

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

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

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

2、创建线程的方法

1、继承Thread类
 package com.study.demo01;
 ​
 //创建线程方式一:继承thread类,重写run方法,调用start开启线程
 public class TestThread 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线程  主线程
 ​
         //创建一个线程对象
         TestThread testThread = new TestThread();
 ​
         //调用start()方法开启线程
         testThread.start();
         
         for (int i = 0; i < 20; i++) {
             System.out.println("我在学习多线程-"+i);
         }
     }
 }
 ​

运行结果:

 我在学习多线程-0
 我在学习多线程-1
 我在看代码-0
 我在学习多线程-2
 我在看代码-1
 我在看代码-2
 我在学习多线程-3
 我在学习多线程-4
 我在学习多线程-5
 我在学习多线程-6
 我在学习多线程-7
 我在学习多线程-8
 我在学习多线程-9
 我在学习多线程-10
 我在学习多线程-11
 我在看代码-3
 我在学习多线程-12
 我在看代码-4
 我在学习多线程-13
 我在看代码-5
 我在学习多线程-14
 我在看代码-6
 我在学习多线程-15
 我在看代码-7
 我在看代码-8
 我在看代码-9
 我在看代码-10
 我在看代码-11
 我在看代码-12
 我在看代码-13
 我在看代码-14
 我在学习多线程-16
 我在学习多线程-17
 我在学习多线程-18
 我在学习多线程-19
 我在看代码-15
 我在看代码-16
 我在看代码-17
 我在看代码-18
 我在看代码-19

总结:线程开启不一定立即执行,由CPU调度执行~

2、实现runnable接口,重写run方法~
 package com.study.demo01;
 ​
 //创建线程方法2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类。调用start方法
 public class TestThread03 implements Runnable{
     @Override
     public void run() {
         //run方法线程体
         for (int i = 0; i < 20; i++) {
             System.out.println("我在看代码~"+i);
         }
     }
 ​
     public static void main(String[] args) {
         //创建runnable接口实现类的对象
         TestThread03 testThread03 = new TestThread03();
 ​
         //创建线程对象,通过线程对象来开启我们的线程  代理
         Thread thread = new Thread(testThread03);
         thread.start();
         //new Thread(testThread03).start();
 ​
         for (int i = 0; i < 20; i++) {
             System.out.println("我在学习多线程~"+i);
         }
     }
 }
 ​

运行结果:

 我在学习多线程~0
 我在学习多线程~1
 我在学习多线程~2
 我在学习多线程~3
 我在学习多线程~4
 我在学习多线程~5
 我在学习多线程~6
 我在学习多线程~7
 我在学习多线程~8
 我在学习多线程~9
 我在学习多线程~10
 我在学习多线程~11
 我在学习多线程~12
 我在看代码~0
 我在看代码~1
 我在看代码~2
 我在看代码~3
 我在看代码~4
 我在看代码~5
 我在看代码~6
 我在看代码~7
 我在看代码~8
 我在看代码~9
 我在看代码~10
 我在看代码~11
 我在学习多线程~13
 我在看代码~12
 我在看代码~13
 我在看代码~14
 我在看代码~15
 我在看代码~16
 我在看代码~17
 我在看代码~18
 我在看代码~19
 我在学习多线程~14
 我在学习多线程~15
 我在学习多线程~16
 我在学习多线程~17
 我在学习多线程~18
 我在学习多线程~19

并发问题

 package com.study.demo01;
 //多线程同时操作同一对象
 //买火车票的例子
 ​
 public class TestThread04 implements Runnable{
 ​
     //票数
     private int ticketNum = 10;
     @Override
     public void run() {
         while(true){
             if (ticketNum<=0){
                 break;
             }
             //模拟延时~
             try {
                 Thread.sleep(200);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNum--+"票~");
         }
     }
 ​
     public static void main(String[] args) {
         TestThread04 ticket = new TestThread04();
 ​
         new Thread(ticket,"张三").start();
         new Thread(ticket,"李四").start();
         new Thread(ticket,"王五").start();
     }
 }
 ​

运行结果:

 王五-->拿到了第10票~
 张三-->拿到了第9票~
 李四-->拿到了第10票~
 李四-->拿到了第8票~
 张三-->拿到了第7票~
 王五-->拿到了第6票~
 李四-->拿到了第4票~
 王五-->拿到了第3票~
 张三-->拿到了第5票~
 张三-->拿到了第2票~
 李四-->拿到了第0票~
 王五-->拿到了第1票~

发现问题:多线程操作同一资源的情况下,线程不安全,数据紊乱!

两种方法对比:

  • 继承Thread类

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

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

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

  • 实现Runnable接口:

    • 实现接口Runable就有多线程能力

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

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

龟兔赛跑实例:

 package com.study.demo01;
 ​
 public class TestRace implements Runnable{
 ​
     //胜利者
     private static String winner;
 ​
     @Override
     public void run() {
         for (int i = 0; i <= 100; i++) {
             if (Thread.currentThread().getName().equals("兔子")){
                 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){
         for (int i = 0; i < 100; i++) {
             //判断比赛是否有胜利者
             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) {
         TestRace race = new TestRace();
 ​
         new Thread(race,"兔子").start();
         new Thread(race,"乌龟").start();
     }
 }

3、实现Callable接口(了解即可)

Callable的好处:

  1. 可以定义返回值

  2. 可以抛出异常

 package com.study.demo02;
 ​
 import com.study.demo01.TestTheard02;
 ​
 import java.util.concurrent.*;
 ​
 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() {
         WebDownloader2 webDownloader = new WebDownloader2();
         webDownloader.downloader1(url,name);
         System.out.println("下载了文件名为:"+name);
         return true;
     }
 ​
     public static void main(String[] args) {
         TestCallable t1 = new TestCallable("https://i2.hdslb.com/bfs/archive/41575b1afec5305fa…a.jpg@320w_200h_1c_!web-space-index-myseries.avif","1.jpg");
         TestCallable t2 = new TestCallable("https://i0.hdslb.com/bfs/archive/756f7695bcdd43e45…8.jpg@320w_200h_1c_!web-space-index-myseries.avif","2.jpg");
         TestCallable t3 = new TestCallable("https://i2.hdslb.com/bfs/archive/0ae34a0c6347a0fd2…6.jpg@320w_200h_1c_!web-space-index-myseries.avif","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 WebDownloader2{
     //下载方法
     public void downloader1(String url,String name){
         //
     }
 }

静态代理

实例:你结婚,婚庆公司代理~

 package com.study.demo02;
 ​
 import java.awt.event.WindowAdapter;
 ​
 public class StaticProxy {
 ​
     public static void main(String[] args) {
         You you = new You();//你要结婚
 ​
         new Thread(()->System.out.println("我爱你~")).start();
 ​
         new WeddingCompany(new You()).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 BEfore(){
         System.out.println("婚前布置工作~");
     }
     private void After(){
         System.out.println("婚后收尾工作~");
     }
 }

运行结果:

 我爱你~
 婚前布置工作~
 你要结婚了~祝福
 婚后收尾工作~

静态代理模式总结:

  • 特点

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

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

  • 好处

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

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

lambda表达式:

 package com.study.demo02;
 ​
 public class Testlambda2 {
 ​
     public static void main(String[] args) {
 ​
         ILove love = (int a)->{
             System.out.println("I love you -->"+a);
         };
 ​
 ​
         love.love(520);
     }
 }
 interface ILove{
     void love(int a);
 }

lambda表达式与其他表达式对比:

 package com.study.demo02;
 ​
 public class TestLambda {
 ​
     //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 lambda1~");
     }
 }

运行结果:

 I like lambda1~
 I like lambda2~
 I like lambda3~
 I like lambda4~
 I like lambda5~

lambda表达式总结:

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

3、线程状态
1、停止线程

 package com.study.state;
 ​
 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("线程停止了");
             }
         }
     }
 }

当i执行到900以后时,标志位变为false,System.out.println("run......Thread"+i);将不再执行!

注意:

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

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

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

2、线程休眠

 //模拟倒计时
 package com.study.state;
 ​
 public class TestSleep {
 ​
     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;
             }
         }
     }
 }

动态打印系统时间

 package com.study.state;
 ​
 import java.text.SimpleDateFormat;
 import java.util.Date;
 ​
 public class TestSleep {
 ​
     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();
             }
         }
     }
 }

3、线程礼让

 package com.study.state;
 ​
 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()+"线程停止执行");
     }
 }

注意:礼让不一定成功,看cpu心情~

4、线程合并

 package com.study.state;
 ​
 public class TestJoin implements Runnable{
     @Override
     public void run() {
         for (int i = 0; i < 1000; 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 < 1000; i++) {
             if (i==100){
                 thread.join();//插队
             }
             System.out.println("main"+i);
         }
     }
 }

5、观测线程状态

运行时可能进行休眠进入阻塞状态或者进入死亡状态

 package com.study.state;
 ​
 //观察测试线程的状态
 public class TestState {
 ​
     public static void main(String[] args) throws InterruptedException {
         Thread thread = new Thread(()->{
             for (int i = 0; i < 2; 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);//Run
 ​
         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
 ///
 TERMINATED

6、线程优先级


 package com.study.state;
 ​
 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);//最大优先级10
         t4.start();
 ​
         t5.setPriority(8);
         t5.start();
 ​
         t6.setPriority(7);
         t6.start();
 ​
     }
 }
 class MyPriority implements Runnable{
 ​
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
 ​
     }
 }

7、守护线程


 package com.study.state;
 ​
 //测试守护线程
 //上帝守护你
 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=======");
     }
 }

总结:在用户线程(你)执行完成后,守护线程(上帝)也会随之结束,尽管上帝线程执行条件恒为真~

8、线程同步


利用队列和锁实现线程同步,详见同步方法及同步块

4、三大不安全案例

1、买票案例
 package com.study.Syn;
 ​
 public class UnsafetyBuyTicket {
 ​
     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--);
     }
 }

问题:出现不同的人买到同一张票!

2、取钱案例
 package com.study.Syn;
 ​
 public class UnsafetyBank {
     public static void main(String[] args) {
         //账户
         Account account = new Account(100, "结婚基金");
 ​
         Drawing zhangsan = new Drawing(account, 50, "张三");
         Drawing lisi = new Drawing(account, 100, "李四");
 ​
         zhangsan.start();
         lisi.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);
         //Thread.currentThread().getName()=this.getName()
         System.out.println(this.getName()+"手里的钱:"+nowMoney);
     }
 }

问题:余额出现负数

3、ArrayList案例
 package com.study.Syn;
 ​
 import java.util.ArrayList;
 ​
 //线程不安全的集合
 public class UnsafetyLisr {
     public static void main(String[] args) {
         ArrayList<String> list = new ArrayList<>();
         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());
     }
 }

问题:集合大小与预想不一致

5、线程同步方法
1、synchronized方法

买票案例改进

 package com.study.Syn;
 ​
 //不安全的买票
 //线程不安全,有负数~
 public class UnsafetyBuyTicket {
 ​
     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(1000);
 ​
         //买票
         System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
     }
 }
2、synchronized同步块

取钱案例改进

 package com.study.Syn;
 ​
 public class UnsafetyBank {
     public static void main(String[] args) {
         //账户
         Account account = new Account(100, "结婚基金");
 ​
         Drawing zhangsan = new Drawing(account, 50, "张三");
         Drawing lisi = new Drawing(account, 100, "李四");
 ​
         zhangsan.start();
         lisi.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() {
         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);
             //Thread.currentThread().getName()=this.getName()
             System.out.println(this.getName()+"手里的钱:"+nowMoney);
         }
     }
 }

ArrayList案例改进

 package com.study.Syn;
 ​
 import java.util.ArrayList;
 ​
 //线程不安全的集合
 public class UnsafetyLisr {
     public static void main(String[] args) {
         ArrayList<String> list = new ArrayList<>();
         for (int i = 0; i < 10000; i++) {
             new Thread(()->{
                 synchronized (list){
                     list.add(Thread.currentThread().getName());
                 }
             }).start();
         }
         try {
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println(list.size());
     }
 }

6、JUC-CopyOnWriteArrayList

测试集合(不使用synchronized同步代码块方法)

 package com.study.Syn;
 ​
 import java.util.concurrent.CopyOnWriteArrayList;
 //测试JUC安全类型的集合
 public class TestJUC {
     public static void main(String[] args) {
         CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
         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());
     }
 }

7、死锁和锁
1、死锁

 package com.study.lock;
 ​
 //死锁:多个线程互相抱着对方需要的资源,然后形成僵持
 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.girlName = girlName;
         this.choice = choice;
     }
 ​
     @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.girlName + "获得镜子的锁~");
                 Thread.sleep(2000);
                 synchronized (lipStick) {//获得口红的锁
                     System.out.println(this.girlName + "获得口红的锁~");
             }
 ​
             }
         }
     }
 }

运行结果为:

 灰姑凉获得口红的锁~
 白雪公主获得镜子的锁~
 ​

此时变为了死锁,程序无法正常运行!

将makeup()函数改成如下:

 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.girlName + "获得镜子的锁~");
                 Thread.sleep(2000);
             }
             synchronized (lipStick) {//获得口红的锁
                 System.out.println(this.girlName + "获得口红的锁~");
             }
         }
     }

运行结果为:

 灰姑凉获得口红的锁~
 白雪公主获得镜子的锁~
 白雪公主获得口红的锁~
 灰姑凉获得镜子的锁~
     

此时程序运行是没有问题的

2、锁

 package com.study.lock;
 ​
 import java.util.concurrent.locks.ReentrantLock;
 ​
 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 ticketNums = 10;
 ​
 ​
     @Override
     public void run() {
         while(true){
                 if (ticketNums>0){
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println(ticketNums--);
                 }
                 else{
                     break;
                 }
             }
         }
 }

此时会出现多个资源操作同一对象,是不安全的!

依据锁的使用方法,修改TestLock2类如下

 class TestLock2 implements Runnable{
 ​
     int ticketNums = 10;
 ​
     //定义lock锁
     private final ReentrantLock lock = new ReentrantLock();
 ​
     @Override
     public void run() {
         while(true){
             try{
                 lock.lock();//加锁
                 if (ticketNums>=0){
                     try {
                         Thread.sleep(1000);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println(ticketNums--);
                 }
                 else{
                     break;
                 }
             }finally {
                 //解锁
                 lock.unlock();
             }
         }
     }
 }

此时,代码是正常运行的~

总结对比

8、线程通信


对于线程通信,Java提供了几个解决方法:

1、解决方法一:管程法

生产者消费者实例:利用缓冲区解决--管程法

 package com.study.HighLevel;
 ​
 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++) {
             System.out.println("生产了-->"+i+"只鸡");
             container.push(new Chicken(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.notify();
     }
 ​
     //消费者消费产品
     public synchronized Chicken pop(){
         //判断能否消费
         if (count==0){
             //等待生产者生产
             try {
                 this.wait();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
         //如果可以消费
         count--;
         Chicken chicken = chickens[count];
 ​
         //吃完就通知生产者生产
         this.notify();
         return chicken;
     }
 }

2、解决方法二:信号灯法

生产者消费者实例:信号灯法,标志位解决

 package com.study.HighLevel;
 ​
 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.notify();
         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.notify();
         this.flag = !this.flag;
     }
 }

9、线程池

线程池使用背景:

线程池使用方法:

重点:ExecutorService、Executor

 package com.study.HighLevel;
 ​
 import java.util.concurrent.Executor;
 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());
 ​
         //关闭连接
         service.shutdown();
     }
 }
 class MyThread implements Runnable{
 ​
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName());
     }
 }

Commons-IO下载教程

  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值