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--------------------------------------------------------