Java多线程-笔记

1、目录(要学习的知识)

2、多线程简介

2.1、 多任务

好比一个人边吃饭边玩手机,看起来是吃饭和玩手机是一起做的,但并不是这样其实本质上来讲是一会吃饭一会看手机,同一时间做一个任务

2.2、多线程

一个线程只能给一个程序使用,如果需要第二个程序则需要创建第二个线程给程序2提供线入。

(就好比一条小道上一次只能通过一辆车,车多了就会发送拥堵效率低为了提高效率,扩建道路将道路分成一条条小道,一次性可以通过多辆车大大提高了效率)

不使用和使用多线程的过程图:

2.3、进程

1)、一个进程有多个线程,如看视频可以听到使用、发弹幕、播放视频等;

2)、进程Process与线程Thread

程序执行为---进程里面有---多个线程

  • 说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  • 而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程, 不然没有存在的意义。线程是CPU调度和执行的的单位。

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

3、线程创建

3.1、创建线程有三中方式:

1)、Thread类:

使用该类创建线程,需要让自定义的类继承该类,并重写该类的run()方法来编写线程执行体,在创建该线程对象调用start()方法启动线程 线程不一定执行,CPU调度安排

 1 //创建线程方式一:继承Thread类,重写run()方法 ,调用start开启线程
 2 //总结:注意, 线程开后不一定立即执行,由CPU调度执行
 3 public class TestThread1 extends Thread {
 4     @Override
 5     public void run() {
 6         for(int i=0;i<200;i++) {
 7             System.out.println("我在看代码=="+i);
 8         }
 9     }
10     
11     public static void main(String[] args) {
12         //main线程:主线程
13         //创建一个线程对象
14         TestThread1 test=new TestThread1();
15         test.start();
16         
17         for(int i=0;i<1000;i++) {
18             System.out.println("我在学习多线程=="+i);
19         }
20     }
21 }

 

2)、Runnable接口:

使用该接口创建线程,需要让自定义的类实现该接口,并重写run()抽象方法编写线程执行体,然后创建该实现类的对象将该实现类对象传给Thread对象调用start()方法启动线程

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

3)、Callable接口:

实现该接口需要返回值(泛型),并重写call方法需要抛出异常,创建该实现类的对象(目标对象)

1、创建执行服务: ExecutorService ser =Executors.newFixedThreadPool(1);

2、提交执行: Future<Boolean> result1 = ser.submit(t1);

3、获取结果: boolean r1 = result1 .get()

4、关闭服务: ser.shutdownNow();

 1 public class CallableTest implements Callable<Boolean> {
 2     private String url;//网络图片路径
 3     private String name;//保存文件的名称
 4     
 5     public CallableTest() {}
 6     public CallableTest(String url,String name) {
 7         this.url=url;
 8         this.name=name;
 9     }
10     
11     //下载图片的线程执行体
12     public Boolean call() {
13         Xz1 xz=new Xz1();
14         xz.xaizai(url, name);
15         System.out.println("下载完成"+name);
16         return true;
17     }
18     
19     public static void main(String[] args) throws InterruptedException, ExecutionException {
20         CallableTest t1=new CallableTest("https://i0.hdslb.com/bfs/sycp/creative_img/202002/fcf948f538729637988488be9505f0ec.jpg@1100w_484h_1c_100q.jpg","1.jpg");
21         CallableTest t2=new CallableTest("https://i0.hdslb.com/bfs/archive/058056424b94c3ff8c1facc940f48ce3bfe423a5.jpg@1100w_484h_1c_100q.jpg","2.jpg");
22         CallableTest t3=new CallableTest("https://i0.hdslb.com/bfs/archive/c5b8e10627f5f880f5a644cfd36260e3662a638c.jpg@1375w_605h_1c_100q.webp","3.jpg");
23         
24         //3个线程谁先下完 谁先结束线程
25         ExecutorService ser=Executors.newFixedThreadPool(3);//创建执行服务器,指定有几个服务
26         Future<Boolean> r1=ser.submit(t1);//提交执行
27         Future<Boolean> r2=ser.submit(t2);
28         Future<Boolean> r3=ser.submit(t3);
29         System.out.println("返回结果"+r1.get());//获取返回结果
30         System.out.println("返回结果"+r2.get());
31         System.out.println("返回结果"+r3.get());
32         ser.shutdownNow();//关闭服务
33     }
34 }
35 36 //下载器
37 class Xz1{
38     //下载方法
39     public void xaizai(String url,String name) {
40         try {
41             //用FileUtil类中的
42             FileUtils.copyURLToFile(new URL(url), new File(name));
43         } catch (IOException e) {
44             e.printStackTrace();
45             System.out.println("下载方法报出异常");
46         }
47     }
48 }

也可以用FutureTask<T>类来执行callable接口,因为该类实现了Runnable接口所以可以直接new一个Thread将该callable实现类放进去在.start()启动线程,用该类的get方法就可以获取到call方法的返回值

 1 public static void main(String[] args) {
 2     FutureTask<Integer> futureTask=new FutureTask<>(new MyDome());
 3     new Thread(futureTask).start();
 4     try {
 5         Integer i=futureTask.get();
 6         System.out.println(i);
 7     }catch(Exception e1) {
 8         e1.printStackTrace();
 9     }
10 }
11 12 class MyDome implements Callable<Integer>{
13     @Override
14     public Integer call() throws Exception {
15         System.out.println("通过FutureTask执行Callable接口");
16         return 100;
17     }
18 }

 

3.2、小结:

在使用多线程操作同一个资源时,线程不安全,数据紊乱

4、静态代理

代理对象与真实对象都实现同一个接口,代理对象要代理真实角色

好处:代理对象可以做到真实对象做不了的事情,真实对象专注做自己的事情

(好比结婚,你是真实角色,婚庆公司代理你帮你处理结婚的事,线程对象Thread代理了Runnble接口)

5、线程状态

5.1、线程方法

5.2、停止线程

不推荐使用线程中的方法来停止线程,stop()、destroy()这些方法都已经过时了@Deprecated。

推荐线程自己停止,利用次数,不建议死循环。

使用一个标志位终止变量flag=false,则线程终止。

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

5.3、线程休眠sleep

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

2、sleep存在异常InterruptedException;

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

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

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

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

 1 Thread t=new Thread(new Runnable() {            
 2     @Override
 3     public void run() {
 4         try {
 5             Thread.sleep(3000);//sleep()设置该线程休眠多少毫秒
 6             System.out.println("过去了三秒");
 7         }catch(Exception e) {
 8             System.out.println("线程中断");
 9         }
10     }
11 });
12 t.start();
13 //用线程对象调用该方法,可以让线程捕捉到InteruptedExcepiton异常间接中断线程
14 t.interrupt();

5.4、线程礼让:Thread.yield()

礼让线程,让当前正在执行的线程暂停,但不阻塞,将线程从运行状态转为就绪状态,让CPU重新调度,礼让不一定成功,看CPU的心情

(a线程在执行,a礼让回到就绪状态,这是b线程或其他线程有可能会运行,但有可能还是a线程执行)

 1 //测试礼让线程
 2 //礼让不一定成功,看cpu心情
 3 public class TestYield {
 4     public static void main(String[] args) {
 5         MyYield yield=new MyYield();
 6         
 7         new Thread(yield,"a").start();
 8         new Thread(yield,"b").start();
 9     }
10 }
11 class MyYield implements Runnable{
12     @Override
13     public void run() {
14         System.out.println(Thread.currentThread().getName()+"线程开始执行");
15         /* 礼让   
16          * 礼让成功:a开始后面就是b开始
17          * 礼让失败:a开始还是a结束
18          */
19         Thread.yield();
20         System.out.println(Thread.currentThread().getName()+"线程结束");
21     }
22 }

5.5、线程join:线程对象.join()

Join合并线程,待此线程执行完成后,再执行其他线程,使用join的线程没有执行完时,其他线程阻塞(可以想象为插队)

 1 public class JoinTest {
 2     public static void main(String[] args) {
 3         Thread t2=new Thread(new Runnable() {
 4             public void run() {
 5                 System.out.println("我是线程B");
 6             }
 7         });
 8         Thread t1=new Thread(new Runnable() {
 9             public void run() {
10                 try {
11                     System.out.println("我是线程A");
12                     /*
13                      * 当某个线程中其他线程对象调用了join()方法时,
14                      * 所处线程会等待调用方法的线程执行完,在继续执行原线程
15                      */
16                     t2.join();
17                 } catch (InterruptedException e) {
18                     e.printStackTrace();
19                 }
20                 System.out.println("我hai是线程A");
21             }
22         });
23         t1.start();
24         t2.start();
25     }
26 }
 1 public class JoinTest {
 2     public static void main(String[] args) {
 3         Thread t2=new Thread(new Runnable() {
 4             public void run() {
 5                 System.out.println("我是线程B");
 6             }
 7         });
 8         Thread t1=new Thread(new Runnable() {
 9             public void run() {
10                 try {
11                     System.out.println("我是线程A");
12                     /*
13                      * 当某个线程中其他线程对象调用了join()方法时,
14                      * 所处线程会等待调用方法的线程执行完,在继续执行原线程
15                      */
16                     t2.join();
17                 } catch (InterruptedException e) {
18                     e.printStackTrace();
19                 }
20                 System.out.println("我hai是线程A");
21             }
22         });
23         t1.start();
24         t2.start();
25     }
26 }
 1 public class JoinTest implements Runnable {
 2     public void run() {
 3         for(int i=0;i<500;i++) {
 4             System.out.println("vip"+i);
 5         }
 6     }
 7     public static void main(String[] args) {
 8         //启动线程
 9         JoinTest join=new JoinTest();
10         Thread t=new Thread(join);
11         t.start();
12         //主线程
13         for(int i=0;i<500;i++) {
14             if(i==200) {
15                 try {
16                     //很霸道的方法,主线程执行到200后,调用jion方法的线程就开始执行,直到该线程结束,主线程才继续执行
17                     t.join();//插队
18                 }catch (InterruptedException e) {
19                     e.printStackTrace();
20                 }
21             }
22             System.out.println("main"+i);
23         }
24     }
25 }
26

5.5、线程状态观测

线程对象.getStart():获取线程现在的状态。

 1 import java.lang.Thread.State;
 2  3 public class TestStart {
 4     public static void main(String[] args) throws InterruptedException {
 5         Thread t=new Thread(()->{
 6             for(int i=0;i<4;i++) {
 7                 try {
 8                     Thread.sleep(1000);
 9                 } catch (InterruptedException e) {
10                     e.printStackTrace();
11                 }
12             }
13             System.out.println("4秒后");
14         });  
15         
16         State state=t.getState();//线程当前的状态 NEW
17         System.out.println(state);
18         
19         t.start();
20         state=t.getState();//Run
21         System.out.println(state);
22         
23         while(state!=Thread.State.TERMINATED) {//只要线程不终止,就一直输出阻塞状态
24             Thread.sleep(100);
25             state=t.getState();
26             System.out.println(state);
27         }
28     }
29 }

6、线程的优先级

线程调度器按照优先级决定一个调用哪个线程来执行,线程优先级用数字表示(1~10 默认优先级为5),使用getPriority()和setPriority(int i)来获取线程的优先级和设置优先级(设置优先级要在start()方法前调用)

优先级低只是意味着获得调度的概率低,并不是优先级低的就不会被调用(性能倒置),这都是看CPU调度

 1 Thread t1 = new Thread(new Runnable() {
 2     public void run() {
 3         for (int i = 0; i < 50; i++) {
 4             System.out.println("t1" + " " + i);
 5         }
 6     }
 7 });
 8 Thread t2 = new Thread(new Runnable() {
 9     public void run() {
10         for (int i = 0; i < 50; i++) {
11             System.out.println("t2" + " " + i);
12         }
13     }
14 });
15 Thread t3 = new Thread(new Runnable() {
16     public void run() {
17         for (int i = 0; i < 50; i++) {
18             System.out.println("t3" + " " + i);
19         }
20     }
21 });
22 23 t3.setPriority(1); //设置线程优先级
24 t2.setPriority(2);
25 t1.setPriority(3);
26 27 t1.start();
28 t2.start();
29 t3.start();

7、守护线程

线程分为用户线程守护线程(线程对象.setDaemon(true);)

虚拟机必须保证用户线程(main)执行完毕

虚拟机不用等待守护线程(如gc())执行完毕。(用户线程执行完毕,守护线程会自动关闭)

 1 //测试守护线程 上帝守护你
 2 public class TestDaemon {
 3     public static void main(String[] args) {
 4         God g=new God();
 5         You y=new You();
 6         
 7         Thread t=new Thread(g);
 8         t.setDaemon(true);//将上帝的线程设置守护线程
 9         t.start();//开启守护线程
10         
11         new Thread(y).start();
12     }
13 }
14 //上帝
15 class God implements Runnable{
16     @Override
17     public void run() {
18         while(true) {
19             System.out.println("上帝守护你");
20         }
21     }
22 }
23 //
24 class You implements Runnable{
25 26     @Override
27     public void run() {
28         for(int i=0;i<36500;i++) {
29             if(i==36499) {
30                 System.out.println("======古德拜======");
31                 break;
32             }
33             System.out.println("开心的活着");
34         }
35     }
36     
37 }

8、线程同步(并发问题:多个线程使用同一资源 )

三大不安全线程案例

  • 抢票

 1 //不安全的抢票 不安全的线程
 2 public class rob_fare implements Runnable {
 3     private int piao=10;//票数
 4     private boolean flag=true;//标识
 5     public static void main(String[] args) {
 6         rob_fare r=new rob_fare();
 7         
 8         /**
 9          * 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
10          * 下面的3个线程买票,都看到了还有最后一张是,将票拿到直接的工作内存中,买票
11          * 在线程看来不会出问题,但程序中的数据就会出问题
12          */
13         new Thread(r,"1").start();
14         new Thread(r,"2").start();
15         new Thread(r,"3").start();
16     }
17 18     @Override
19     public void run() {
20         while(flag) {
21             try {
22                 buy();
23             } catch (InterruptedException e) {
24                 // TODO Auto-generated catch block
25                 e.printStackTrace();
26             }
27         }
28     }
29     
30     //synchronized方法 同步方法 锁的是this 加上synchronized就安全了
31     public void buy() throws InterruptedException {
32         if(piao<=0) {
33             flag=false;
34             return;
35         }
36         //模拟延时:放大问题的发生性
37         Thread.sleep(100);
38         System.out.println(Thread.currentThread().getName()+"抢到第"+piao--+"票");
39     }
40 }
  • 取钱

 1 //不安全的线程
 2 public class draw_money {
 3     public static void main(String[] args) {
 4         Account a=new Account(100, "过日子");
 5         
 6         new Drawing(a,50,"你").start();//你取了50万,还剩50
 7         new Drawing(a,100,"你老婆").start();//因为同时取钱所以,你老婆看到的还是100万,取了100万还剩-50
 8     }
 9 }
10 class Account{
11      int money;
12      String name;
13     public Account(int money, String name) {
14         super();
15         this.money = money;
16         this.name = name;
17     }
18 }
19 class Drawing extends Thread{
20     private Account account;
21     private int qu_money;
22     private int m=0;
23     
24     public Drawing(Account account, int qu_money,String name) {
25         super(name);
26         this.account = account;
27         this.qu_money = qu_money;
28     }
29     
30     @Override
31     public void run() {
32         //synchronized (account) {//锁的是银行卡,因为银行卡上的钱需要改变
33             //取钱操作
34             if((account.money-qu_money)<0) {
35                 System.out.println("余额不足");
36                 return;
37             }
38             //放大问题的发生性
39             try {
40                 this.sleep(2000);
41             } catch (InterruptedException e) {
42                 // TODO Auto-generated catch block
43                 e.printStackTrace();
44             }
45             //卡里剩余的钱
46             account.money=account.money-qu_money;
47             //取钱的人剩余的钱
48             System.out.println(this.getName()+"现在有"+(m+qu_money));
49             System.out.println("卡里剩余"+account.money);
50         //}
51     }
52 }
  • 集合用线程存数据

 1 public class shibai_List {
 2     public static void main(String[] args) {
 3         List<String> list = new ArrayList<>();
 4  5         for (int i = 0; i < 10000; i++) {
 6             new Thread(() -> {
 7 //              synchronized (list) {//同步块 锁的是list对象
 8                     list.add(Thread.currentThread().getName());
 9 //              }
10             }).start();
11         }
12         try {
13             Thread.sleep(3000);
14         } catch (InterruptedException e) {
15             // TODO Auto-generated catch block
16             e.printStackTrace();
17         }
18         // 因为多个线程为list添加数据,导致有事多个线程添加数据到同一个位置,所以数据数量有误
19         System.out.println(list.size());
20         
21 //      CopyOnWriteArrayList:JUC安全类型的集合
22 //      CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();
23 //      for (int i = 0; i < 10000; i++) {
24 //          new Thread(() -> {
25 //                  list.add("gjjgjh");
26 //          }).start();
27 //      }
28 //      try {
29 //          Thread.sleep(2000);
30 //      } catch (InterruptedException e) {
31 //          // TODO Auto-generated catch block
32 //          e.printStackTrace();
33 //      }
34 //      System.out.println(list.size());
35     }
36 }

8.1、同步的概述

1)、解决并发问题,需要用线程同步。线程同步其实就是一种等待机制,多个线程同时访问一个对象时,访问的线程进入线程等待池中形成队列(排队),等待前面的线程结束,下一个线程才使用

2)、锁机制synchronized(同步的关键字):在线程中每个对象都有一把锁,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用完后释放锁。

队列 + 锁 = 线程同步的安全;

但存在以下的问题:

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

2、在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;(要安全就要牺牲性能,相反也一样)

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

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

(好比买票,取钱。多个线程同时买票,因为都看到最后还剩一张票(将这一张票copy到自己的工作内存空间来),直接买,导致数据为负数。取钱差不多。

集合对象用线程添加数据,有会出现多个对象添加数据到同一位置。可以用同步,也可以用JUC里面的安全类型的集合CopyOnWriteArrayList解决并发问题)

8.2、同步方法

synchronized关键字:有两种用synchronized方法synchronized块

1)、同步方法的synchronized 默认锁也是this(调用同步方法的类)

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

synchronized方法控制对“对象”的访问,每个对象对应一把锁每个synchronized方法都必须获取该方法的对象的锁才能执行,不然线程会阻塞。

执行过程:一但该方法执行,就独占该对象的锁,直到方法返回才释放锁,后面被阻塞的线程才能获得该锁继续执行

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

2)、同步块:

  • 同步块 :synchronized (Obj ){ }

锁的是指定需要增删改的对象,代码块中是增删改的操作

1 synchronized(变化的对象){
2     变化对象的代码
3 }
  • Obj 称之 同步监视器

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

    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,,就是这个对象本身,或者是class (用class对象调用)

  • 同步监视器的执行过程

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

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

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

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

同步块执行的执行的过程与同步方法一样

线程休眠不要放在线程同步中,因为休眠不会释放锁,休眠完毕程序会马上再次进入同步方法,在次锁住,所以其他线程拿不到锁

8.3、死锁

多个线程互相抱有对方需要的资源,然后形成僵持

死锁案例:

 1 public class si_suo {
 2     public static void main(String[] args) {
 3         new Thread(new Makeup(0,"灰姑娘")).start();
 4         new Thread(new Makeup(1,"白雪公主")).start();
 5     }
 6 }
 7  8 //口红
 9 class Lipstick{}
10 //镜子
11 class Mirror{}
12 //女人
13 class Makeup implements Runnable{
14     //需要资源只有一份,用static保证只有一份
15     static Lipstick l=new Lipstick();
16     static Mirror m=new Mirror();
17     
18     int choice;
19     String name;
20     public Makeup(int choice, String name) {
21         this.choice = choice;
22         this.name = name;
23     }
24     @Override
25     public void run() {
26         try {
27             add();
28         } catch (InterruptedException e) {
29             // TODO Auto-generated catch block
30             e.printStackTrace();
31         }
32     }
33     /**
34      * 因为一个线程拿到了口红,另一个线程拿到了镜子,双方都需要对方的东西,
35      * 又因为双方无法释放锁住的对象,所以两线程僵住程序卡死
36      * 
37      * 解决该问题,可以将里面同步块拿出来,这样就可以拿到对方的东西了,
38      * 因为第一个线程拿到了口红使用完后释放该锁,第二个线程刚好也释放了一个对方所需要的锁,
39      * 去拿第一个线程刚释放的锁,第一个线程去拿第二个线程刚释放的锁这样就可以解决死锁
40      */
41     public void add() throws InterruptedException {
42         if(choice==0) {
43             synchronized (l) {//拿到口红,释放对应的锁
44                 System.out.println(this.name+"拿到口红");
45                 Thread.sleep(1000);
46 //              synchronized (m) {//一秒后在拿到镜子
47 //                  System.out.println(this.name+"在拿到镜子");
48 //              }
49             }
50             synchronized (m) {//一秒后在拿镜子,因为选择镜子的人用完镜子了释放了锁,这时就可以拿到镜子了
51                 System.out.println(this.name+"在拿到镜子");
52             }
53         }else {
54             synchronized (m) {//拿到镜子,释放对应的锁
55                 System.out.println(this.name+"拿到镜子");
56                 Thread.sleep(1000);
57 //              synchronized (l) {//一秒后在拿到口红
58 //                  System.out.println(this.name+"在拿到口红");
59 //              }
60             }
61             synchronized (l) {//一秒后在拿口红,因为选择镜子的人用完镜子了释放了锁,这时就可以拿到镜子了
62                 System.out.println(this.name+"在拿到口红");
63             }
64         }
65     }
66 }

8.4、Lock锁

1、从jdk5开始,java提供更强大的线程同步机制,通过显示的同步锁对象来实现同步(Lock)

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

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

使用lock锁的案例:

 1 //不安全的抢票 不安全的线程
 2 public class Lock_suo implements Runnable {
 3     private int piao=10;//票数
 4     private boolean flag=true;//标识
 5     
 6     private final ReentrantLock lock=new ReentrantLock();//定义lock锁对象
 7     
 8     public static void main(String[] args) {
 9         Lock_suo r=new Lock_suo();
10         
11         new Thread(r,"1").start();
12         new Thread(r,"2").start();
13         new Thread(r,"3").start();
14     }
15     @Override
16     public void run() {
17         while(flag) {
18             try {
19                 lock.lock();//加锁
20                 buy();
21             } catch (InterruptedException e) {
22                 // TODO Auto-generated catch block
23                 e.printStackTrace();
24             }finally {
25                 lock.unlock();//解锁
26             }
27         }
28     }
29     
30     //synchronized方法 同步方法 锁的是this 
31     public void buy() throws InterruptedException {
32         if(piao<=0) {
33             flag=false;
34             return;
35         }
36         //模拟延时:放大问题的发生性
37         Thread.sleep(100);
38         System.out.println(Thread.currentThread().getName()+"抢到第"+piao--+"票");
39     }
40 }

 

9、线程协作

9.1、生产者消费者模式(问题)

解决该问题可以用管程法和信号灯法。

管程法:生产者生产的数据放入缓冲区,消费者从缓冲区拿数据,案例如下:

 1 public class TestPC {
 2     public static void main(String[] args) {
 3         Warehouse w=new Warehouse();
 4         
 5         
 6         Productor p=new Productor(w);
 7         Consumer c=new Consumer(w);
 8         p.start();
 9         c.start();
10     }
11 }
12 13 //生产者
14 class Productor extends Thread{
15     Warehouse w;
16     public Productor(Warehouse w) {
17         this.w=w;
18     }
19     @Override
20     public void run() {
21         for(int i=1;i<=100;i++) {
22             System.out.println("生产了"+i+"只鸡");
23             w.push(new Chicken(i));
24         }
25     }
26 }
27 //消费者
28 class Consumer extends Thread{
29     Warehouse w;
30     public Consumer(Warehouse w) {
31         this.w=w;
32     }
33     public void run() {
34         for(int i=1;i<=100;i++) {
35             System.out.println("消费了"+w.pop().id+"只鸡");
36         }
37     }
38     
39 }
40 //产品
41 class Chicken{
42     int id;
43     public Chicken(int id) {
44         this.id=id;
45     }
46 }
47 //缓存区
48 class Warehouse {
49     Chicken[] ch=new Chicken[10];//缓冲区大小
50     int i=0;
51     //生产者放入产品
52     public synchronized void push(Chicken c) {
53         if(ch.length==i) {
54             //通知消费者来消费,生产者等待
55             try {
56                 this.wait();//因为生产者调用了该方法,所以生产者等待
57             } catch (InterruptedException e) {
58                 // TODO Auto-generated catch block
59                 e.printStackTrace();
60             }
61         }
62         //如果产品没有放满,则继续放入
63         ch[i]=c;
64         i++;
65         //通知消费者消费
66         this.notifyAll();//唤醒生产者因为生产者在等待
67     }
68     //消费者消费产品
69     public synchronized Chicken pop() {
70         if(i==0) {
71             //通知生产者来生产,消费者等待
72             try {
73                 this.wait();//消费者调用该方法,所以消费者等待
74             } catch (InterruptedException e) {
75                 // TODO Auto-generated catch block
76                 e.printStackTrace();
77             }
78         }
79         //如果有产品则将产品拿出来
80         i--;
81         Chicken c=ch[i];
82         
83         //通知生产者生产
84         this.notifyAll();//唤醒生产者,因为生产者等待了
85         return c;
86     }
87 }

 

信号灯法:给个标识符,为true等待,为false唤醒

可以用一下方法来完成线程通信,案例如下:

 1 public class TestPC2 {
 2     public static void main(String[] args) {
 3         TV tv = new TV();
 4  5         new Player(tv).start();
 6         new Watcher(tv).start();
 7     }
 8 }
 9 10 //生产者 演员
11 class Player extends Thread {
12     TV tv;
13     public Player(TV tv) {
14         this.tv = tv;
15     }
16 17     @Override
18     public void run() {
19         for (int i = 1; i <= 5; i++) {
20             tv.play("西游记" + i);
21         }
22     }
23 }
24 25 //消费者 观众
26 class Watcher extends Thread {
27     TV tv;
28     public Watcher(TV tv) {
29         this.tv = tv;
30     }
31 32     @Override
33     public void run() {
34         for (int i = 1; i <= 5; i++) {
35             tv.watch();
36         }
37     }
38 }
39 40 //产品 节目
41 class TV {
42     // 演员表演 观众等待 T
43     // 观众观看 演员等待 F
44     String voice;// 表演的节目
45     boolean flag = true;//标识
46 47     public synchronized void play(String voice) {
48         if (!flag) {
49             try {
50                 this.wait();
51             } catch (InterruptedException e) {
52                 // TODO Auto-generated catch block
53                 e.printStackTrace();
54             }
55         }
56         System.out.println("演员表演了" + voice);
57         this.voice = voice;
58         this.flag = false;//将标识该变,是观众不会在进入方法再次等待,而演员等待
59         this.notifyAll();//唤醒等待的观众
60     }
61 62     public synchronized void watch() {
63         if (flag) {
64             try {
65                 this.wait();
66             } catch (InterruptedException e) {
67                 // TODO Auto-generated catch block
68                 e.printStackTrace();
69             }
70         }
71         System.out.println("观众观看的" + voice);
72         this.flag = true;//将标识改变,使演员不会在进入方法再次等待,而观众等待
73         this.notifyAll();//唤醒等待的演员
74     }
75 }

等待唤醒方法:

10、线程池

为什么要使用线程池:因为经常创建和销毁,使用了特别大的资源。如并发的情况下,就很影响性能。

10.1、使用线程池

可以用ExecutorService接口和Executors工具类来使用线程池

1)、ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor。该接口有以下方法:

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

Executors . newFixedThreadPool(大小) ;:该方法是用来创建线程池并指定大小,是Executors类中创建线程池的方法

案例:

 1 public class xianchengchi {
 2     public static void main(String[] args) {
 3         //创建服务,创建线程池
 4         ExecutorService e=Executors.newFixedThreadPool(3);
 5         //执行
 6         e.execute(new Add());
 7         e.execute(new Add());
 8         e.execute(new Add());
 9 //      e.execute(new Add());线程超出线程池大小将会输出第一个线程
10         //关闭服务
11         e.shutdown();
12     }
13 }
14 15 class Add implements Runnable{
16     @Override
17     public void run() {
18         System.out.println(Thread.currentThread().getName());
19     }
20 }

C:\Users\Admin\AppData\Local\YNote\data\qq9E4A200A5CE5CE782D90480A88BC8063\7e4d3461b01e4f6ebe76423eadb9e06f\clipboard.png

posted @ 2020-04-13 21:42  Beat_All  阅读(228)  评论(1)    收藏  举报