Java多线程


Java多线程

1. 多线程存在的意义

  多线程最大的作用就是能够创建“灵活响应”的桌面程序,而编写多线程最大的困难就是不同线程之间共享资源的问题,要使这些资源不会同时被多个线程访问。由于CPU的调度具有一定的随机性,因此使用多线程千万别较真。

2. 进程与线程的区别

  -进程:运行时(runtime)的应用程序,进程之间的内存是独立的,进程之间的通信需要使用socket

  -线程:进程中并发执行的代码段,线程之间的内存时共享的,每一个运行着的线程对应一个stack,一个应用程序(进程)至少有一个线程(主线程)

3. 线程的创建方式

3.1 方式一:继承Thread类

    1、覆盖重写run()方法,将需要同步执行的代码编写到run()方法中。

    2、创建子类对象的同时线程被创建

    3、调用Thread.start()方法开启线程

3.1.1 Thread.yield()方法

  yield()是静态方法,与对象无关。让当前线程放弃cpu的抢占权,具有谦让之意,但动作是瞬时的,放弃之后又会立即去抢占cpu。

案例1:多个线程交替打印当前系统时间,观察输出情况

 1 package com.yss.gyg;
 2 
 3 import java.util.Date;
 4 
 5 /*
 6  * 案例1:多个线程交替打印当前系统时间,观察输出情况
 7  * */
 8 public class PrintSysDateTime extends Thread {
 9 
10     @Override
11     public void run() {
12         String name = Thread.currentThread().getName();
13         for (int i = 0; i < 10; i++) {
14             System.out.println(name + ":"+new Date());
15             //输出完成一次之后放弃当前cpu的抢占权
16             Thread.yield();
17         }
18     }
19 }
PrintSysDateTime
 1 package com.yss.gyg.test;
 2 
 3 import com.yss.gyg.PrintSysDateTime;
 4 
 5 import java.util.Date;
 6 
 7 public class PrintSysDateTimeTest {
 8     public static void main(String[] args) {
 9         PrintSysDateTime t1 = new PrintSysDateTime();
10         PrintSysDateTime t2 = new PrintSysDateTime();
11         PrintSysDateTime t3 = new PrintSysDateTime();
12         t1.start();
13         t2.start();
14         t3.start();
15     }
16 }
PrintSysDateTimeTest

3.1.2 Thread.join()方法

  join()方法:当前线程需等待指定的线程结束后才能继续运行。

案例2:打麻将需要4个人,需4个人都到了之后才能开局

 1 package com.yss.gyg;
 2 
 3 public class Player extends Thread{
 4     private String name;
 5     private int time;
 6 
 7     public Player(String name, int time) {
 8         this.name = name;
 9         this.time = time;
10     }
11 
12     @Override
13     public void run() {
14         System.out.println(name + ":出发了");
15         try {
16             Thread.sleep(time);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20         System.out.println(name + ":到了");
21     }
22 }
Player
 1 package com.yss.gyg.test;
 2 
 3 import com.yss.gyg.Player;
 4 
 5 public class PlayerTest {
 6     public static void main(String[] args) {
 7         Player player1 = new Player("player1", 1000);
 8         Player player2 = new Player("player2", 1500);
 9         Player player3 = new Player("player3", 1500);
10         Player player4 = new Player("player4", 1700);
11         player1.start();
12         player2.start();
13         player3.start();
14         player4.start();
15         try {
16             player1.join();
17             player2.join();
18             player3.join();
19             player4.join();
20         } catch (InterruptedException e) {
21             e.printStackTrace();
22         }
23         System.out.println("开搞!!!");
24     }
25 }
26 /*
27 player1:出发了
28 player4:出发了
29 player3:出发了
30 player2:出发了
31 player1:到了
32 player2:到了
33 player3:到了
34 player4:到了
35 开搞!!!
36 * */
PlayerTest

 3.1.3 t1.start()和直接调用run()方法的区别

   直接调用run()方法:直接调用run方法就是普通方法的调用,run()方法的执行依然是压栈到mian()的栈中,每一个运行着的线程对应一个stack

  t1.start():我们只是告诉cpu可以调用run()方法了,具体又cpu开启一个新的线程去调用run()方法,也就是另外开启一个stack

3.1.4  Thread.setDaemon():设置守护线程

   守护线程是为其它的线程服务的,当一个进程中除了守护线程外的其它线程都执行完时,则该进程结束。

 案例3:Waiter为酒吧里的每一个包厢服务,每一个包厢为一个线程,当所有的包厢都结束之后Waiter下班,整个进程结束。

 1 package com.yss.gyg;
 2 
 3 import java.util.Date;
 4 
 5 public class Waiter extends Thread {
 6     @Override
 7     public void run() {
 8         //每隔2s钟报一次时间
 9         for (; ; ) { //死循环
10             System.out.println("当前时间:" + new Date());
11             try {
12                 Thread.sleep(2000);
13             } catch (InterruptedException e) {
14                 e.printStackTrace();
15             }
16         }
17     }
18 }
Waiter
 1 package com.yss.gyg;
 2 
 3 public class Box extends Thread{
 4     private String name;
 5     private int time;
 6 
 7     public Box(String name, int time) {
 8         this.name = name;
 9         this.time = time;
10     }
11 
12     @Override
13     public void run() {
14         System.out.println(name+":开始消费");
15         try {
16             Thread.sleep(time);
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         }
20         System.out.println(name+":消费结束");
21 
22     }
23 }
Box
 1 package com.yss.gyg.test;
 2 
 3 import com.yss.gyg.Box;
 4 import com.yss.gyg.Waiter;
 5 
 6 public class WaiterTest {
 7     public static void main(String[] args) {
 8         Waiter waiter = new Waiter();
 9         Box box1 = new Box("box1", 5000);
10         Box box2 = new Box("box2", 8000);
11         Box box3 = new Box("box3", 10000);
12         Box box4 = new Box("box4", 7800);
13         waiter.setDaemon(true);
14         waiter.start();
15         box1.start();
16         box2.start();
17         box3.start();
18         box4.start();
19     }
20 }
21 /*
22 box1:开始消费
23 box4:开始消费
24 box3:开始消费
25 box2:开始消费
26 当前时间:Fri Dec 18 16:41:15 CST 2020
27 当前时间:Fri Dec 18 16:41:17 CST 2020
28 当前时间:Fri Dec 18 16:41:19 CST 2020
29 box1:消费结束
30 当前时间:Fri Dec 18 16:41:21 CST 2020
31 box4:消费结束
32 box2:消费结束
33 当前时间:Fri Dec 18 16:41:23 CST 2020
34 box3:消费结束
35 * */
WaiterTest

 3.1.5 线程间资源共享(synchronize)

多线程之间的内存是共享的,但多个线程同时访问同一个资源的时候会出现线程安全问题,同步可以解决线程安全问题。

同步的前提:同步需要两个或者两个以上的线程,多个线程使用的是同一个锁。

同步的方式:

  同步代码块:同一时刻只能有一个线程执行同步代码块中的代码,同步代码块以指定的那个对象为“锁旗标”。同步代码块执行期间,线程始终持有对象的监控权,其它的线程处于阻塞状态。

1 synchronize(对象){
3     需要同步的代码块
5   }

  同步非静态方法:synchronize(this)=== 同步方法,以当前对象为“锁旗标”

  同步静态方法:使用类的描述符(.class)作为“锁旗标”。

同步的弊端:当线程比较多的时候,由于每个线程都要去判断锁的状态,需要消耗比较多的资源,因此会降低程序的运行效率。

 案例4:多线程卖票:多个售票员同时卖一个票池中的票,直到票被全部卖完后结束进程。

 1 package com.yss.gyg;
 2 
 3 public class TicketPoll {
 4     private int initNum;//初始化票池
 5 
 6     public TicketPoll(int initNum) {
 7         this.initNum = initNum;
 8     }
 9 
10     //同步方法:以当前对象为锁旗标
11     public synchronized int getTicket() {//取票的方法同时只能有一个线程执行
12         //有票返回票号
13         if (initNum > 0) {
14             //initNum--; //原子性操作
15             return initNum--;
16         }
17         //没票返回-1
18         return -1;
19     }
20 }
TicketPoll
 1 package com.yss.gyg;
 2 /*售票员类*/
 3 public class Seller extends Thread {
 4     private String name;
 5     private TicketPoll poll;
 6 
 7     public Seller(String name, TicketPoll poll) {
 8         this.name = name;
 9         this.poll = poll;
10     }
11 
12     @Override
13     public void run() {
14         int ticketNum = -1;
15         while ((ticketNum = poll.getTicket()) > 0) {
16             System.out.println(name + "卖出了 " + ticketNum + " 号票");
17             try {
18                 //模拟售票时间
19                 Thread.sleep(50);
20             } catch (InterruptedException e) {
21                 e.printStackTrace();
22             }
23         }
24 
25     }
26 }
Seller
 1 package com.yss.gyg.test;
 2 
 3 import com.yss.gyg.Seller;
 4 import com.yss.gyg.TicketPoll;
 5 
 6 public class SellerTest {
 7     public static void main(String[] args) {
 8         TicketPoll poll = new TicketPoll(100);
 9         Seller seller1 = new Seller("seller1", poll);
10         Seller seller2 = new Seller("seller2", poll);
11         Seller seller3 = new Seller("seller3", poll);
12         System.out.println("售票开始...");
13         seller1.start();
14         seller2.start();
15         seller3.start();
16 
17     }
18 }
SellerTest

3.1.6 线程间通信(wait()、notify()、notifyall())

 wait()方法:让当前线程进入等待队列,释放cpu的抢占权,并且还释放“锁旗标”的监控权,进入等待队列后等待Object.notify()来通知可以“抢”cpu了。

notify()方法:通知等待队列中的一个线程唤醒,若等待队列中有多个等待线程,将随机选择一个。

notifyall()方法:通知等待队列中的所有线程唤醒,可以解决“死锁”的象限。

 案例5:多线程生产者、消费者问题:

 1 package com.yss.gyg;
 2 
 3 public class Producer extends Thread{
 4     private String name;
 5     private P_C_Poll poll;
 6 
 7     public Producer(String name, P_C_Poll poll) {
 8         this.name = name;
 9         this.poll = poll;
10     }
11 
12     @Override
13     public void run() {
14         int i = 1;
15         while (true) {
16             poll.add(i);
17             i++;
18             try {
19                 sleep(500);//模拟生产时间
20             } catch (InterruptedException e) {
21                 e.printStackTrace();
22             }
23         }
24     }
25 }
生产者
 1 package com.yss.gyg;
 2 
 3 public class Consumer  extends Thread{
 4     private String name;
 5     private P_C_Poll poll;
 6 
 7     public Consumer(String name, P_C_Poll poll) {
 8         this.name = name;
 9         this.poll = poll;
10     }
11 
12     @Override
13     public void run() {
14         while (true) {
15             poll.remove();
16             try {
17                 sleep(200);//模拟消费时间
18             } catch (InterruptedException e) {
19                 e.printStackTrace();
20             }
21         }
22     }
23 }
消费者
 1 package com.yss.gyg.test;
 2 
 3 import com.yss.gyg.Consumer;
 4 import com.yss.gyg.P_C_Poll;
 5 import com.yss.gyg.Producer;
 6 
 7 public class P_C_Test {
 8     public static void main(String[] args) {
 9         P_C_Poll poll = new P_C_Poll();
10         //10个生产者
11         for (int i = 0; i < 10; i++) {
12             new Producer(i + "", poll).start();
13         }
14 
15         //5个消费者
16         for (int i = 0; i < 5; i++) {
17             new Consumer(i + "", poll).start();
18         }
19 
20     }
21 }
测试类
 1 package com.yss.gyg;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 public class P_C_Poll {
 7     private int max = 100;
 8     private List list = new ArrayList<>();
 9 
10     public synchronized void add(int num) {
11         int i = 0;
12         //容器已满
13         while ((i = list.size()) >= max) {
14             notify();//通知消费
15             try {
16                 wait();//进入等待
17             } catch (InterruptedException e) {
18                 e.printStackTrace();
19             }
20         }
21         //容器没满
22         list.add(num);
23         System.out.println("生产了:" + num);
24         System.out.println("容器中数量:" + list.size());
25         notify();//通知消费
26     }
27 
28     public synchronized int remove() {
29         int i = 0;
30         //容器为空
31         while (list.isEmpty() && (i = list.size()) == 0) {
32             notify();//通知生产
33             try {
34                 wait();//进入等待队列
35             } catch (InterruptedException e) {
36                 e.printStackTrace();
37             }
38         }
39         //容器不为空
40         int num = list.remove(0);
41         System.out.println("消费了:" + num);
42         System.out.println("容器中数量:" + list.size());
43         notify();//通知生产
44         return num;
45     }
46 }
容器类

wait()、notify()、notifyall()方法为什么定义在Object类中?

  wait()、notify()、notifyall()方法都是在同步中使用,而同步必须要有一个“锁”,“锁”可以是任意对象,因此定义在Object中。

sleep()和wait()方法的区别:

  sleep()方法释放cpu的抢占权,不释放“锁”的监控权

  wait()方法释放cpu的抢占权,同时释放“锁”的监控权

3.2 方式二:

-------------------------------end--------------------------------------------------------