四、线程的优先级
什么是线程的优先级
每一个线程都是有优先级的,我们可以为每个线程定义线程的优先级,但是这并不能保证高优先级的线程会在低优先级的线程前执行。线程的优先级用数字表示,范围从1到10,一个线程的默认优先级是5。
Java 的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关,如非特别需要,一般无需设置线程优先级。
注意
线程的优先级,不是说哪个线程优先执行,如果设置某个线程的优先级高。那就是有可能被执行的概率高。并不是绝对优先执行。
4.1 线程优先级的使用
使用下列方法获得或设置线程对象的优先级。
- int getPriority(); //获取当前进程的优先级
- void setPriority(int newPriority); //设置进程的优先级
注意:线程优先级是在线程启动之前就分配的,优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。
package cn.it.bz.Thread; class Priority implements Runnable{ private int num = 0;//记录当前线程执行次数 private boolean flag = true; //生死牌 @Override public void run() { while (flag){ System.out.println("当前线程名称:"+Thread.currentThread().getName()+",执行次数是:"+num++); } } public void stop(){ flag = false; } } public class PriorityThread { public static void main(String[] args) throws InterruptedException { Priority priority1 = new Priority(); Thread thread1 = new Thread(priority1,"线程1"); Priority priority2 = new Priority(); Thread thread2 = new Thread(priority2,"线程2"); System.out.println("线程1的优先级:"+thread1.getPriority());//5 System.out.println("线程2的优先级:"+thread2.getPriority());//5 thread1.setPriority(Thread.MAX_PRIORITY);// thread1.setPriority(10); thread2.setPriority(Thread.MIN_PRIORITY); //启动线程 thread1.start(); thread2.start(); //主线程休眠 Thread.sleep(2000); //结束线程 priority1.stop(); priority2.stop(); } }
五、守护线程
守护线程(即Daemon Thread),是一个服务线程,准确地来说就是服务其他的线程,这是它的作用,而其他的线程只有一种,那就是用户线程。
在Java中有两类线程:
- User Thread(用户线程):就是应用程序里的自定义线程。
- Daemon Thread(守护线程):比如垃圾回收线程,就是最典型的守护线程。
守护线程特点:守护线程会随着用户线程死亡而死亡。
守护线程与用户线程的区别:
用户线程,不随着主线程的死亡而死亡。用户线程只有两种情况会死掉,1、在run中异常终
止。2、正常把run执行完毕,线程死亡。
守护线程,随着用户线程的死亡而死亡,当用户线程死亡守护线程也会随之死亡。
5.1 守护线程的使用
package cn.it.bz.Thread; //守护线程 class Daemon implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("守护线程:"+Thread.currentThread().getName()+","+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } //主线程 public class DaemonThread { public static void main(String[] args) throws InterruptedException { //实例化守护线程 Thread thread = new Thread(new Daemon(),"啊哈哈"); thread.setDaemon(true); //将普通线程变为守护线程 thread.start(); //守护线程启动 Thread.sleep(3000); System.out.println("主线程结束");//主线程结束,守护线程结束。守护线程不一定非得守护主线程。 } }
六、线程同步
6.1 什么是线程冲突?
如图,该进程中的线程一在对进程空间中的对象进行修改时,突然时间片用完。此时线程一只是修改了对象的name,age并未做出修改。线程二读取到的对象的值就是错误的。
6.2 同步问题的提出
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比如:教室里,只有一台电脑,多个人都想使用。天然的解决办法就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。
6.3 实现线程同步
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。这套机制就是synchronized关键字。
synchronized语法结构:
synchronized(对象锁){
同步代码
}
synchronized关键字使用时需要考虑的问题:
- 需要对那部分的代码在执行时具有线程互斥(线程同步)的能力(线程互斥:并行变串行)。
- 需要对哪些线程中的代码具有互斥能力(通过synchronized锁对象来决定)。
- 拥有相同对象锁的线程才会做线程互斥。
- synchronized两种用法:
synchronized 方法
通过在方法声明中加入 synchronized关键字来声明,语法如下:
public synchronized void accessVal(int newVal);
synchronized 在方法声明时使用:放在访问控制符(public)之前或之后。这时同一个对象下synchronized方法在多线程中执行时,该方法是同步的,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入。
synchronized块
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
6.4 线程冲突案例演示
我们以银行取款经典案例来演示线程冲突现象。
银行取钱的基本流程基本上可以分为如下几个步骤。
(1)用户输入账户、密码,系统判断用户的账户、密码是否匹配。
(2)用户输入取款金额
(3)系统判断账户余额是否大于或等于取款金额
(4)如果余额大于或等于取款金额,则取钱成功;如果余额小于取款金额,则取钱失败。
6.4.1 没有实现线程冲突
package cn.it.bz.Thread; //账户类 class Account{ private String password; //账户密码 private double balance; //账户余额 public Account() { } public Account(String password, double balance) { this.password = password; this.balance = balance; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } } //取款线程 class DrawMoneyThread implements Runnable{ //账户对象 private Account account; //取款金额 private double drawMoney; public DrawMoneyThread() { } public DrawMoneyThread(Account account, double drawMoney) { this.account = account; this.drawMoney = drawMoney; } //取款线程体 @Override public void run() { //判断当前账户余额>=取款金额 if (this.account.getBalance()>this.drawMoney){ System.out.println(Thread.currentThread().getName()+"取款成功!"+"余额:"+(this.account.getBalance()-this.drawMoney)); //线程休眠,账户余额修改 try { Thread.sleep(1000); this.account.setBalance(this.account.getBalance()-this.drawMoney); } catch (InterruptedException e) { e.printStackTrace(); } }else { System.out.println(Thread.currentThread().getName()+"取款失败!!余额不足"); } } } public class TestDrawMoneyThread { public static void main(String[] args) { Account account = new Account("12345",2000); Thread manThread = new Thread(new DrawMoneyThread(account, 1300),"男人取款线程");//男人取款线程 Thread womanThread = new Thread(new DrawMoneyThread(account, 1000),"女人取款线程");//女人取款线程 manThread.start(); womanThread.start(); } }
6.4.2 实现线程同步
package cn.it.bz.Thread; //账户类 class Account{ private String password; //账户密码 private double balance; //账户余额 public Account() { } public Account(String password, double balance) { this.password = password; this.balance = balance; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } } //取款线程 class DrawMoneyThread implements Runnable{ //账户对象 private Account account; //取款金额 private double drawMoney; public DrawMoneyThread() { } public DrawMoneyThread(Account account, double drawMoney) { this.account = account; this.drawMoney = drawMoney; } //取款线程体 @Override public void run() { //判断当前账户余额>=取款金额 //同步范围,synchronized不能加在run方法上,因为不起作用。 synchronized (this.account){ //锁住账户对象,this指的是当前DrawMoneyThread线程对象 if (this.account.getBalance()>this.drawMoney){ System.out.println(Thread.currentThread().getName()+"取款成功!"+"余额:"+(this.account.getBalance()-this.drawMoney)); //线程休眠,账户余额修改 try { Thread.sleep(1000); this.account.setBalance(this.account.getBalance()-this.drawMoney); } catch (InterruptedException e) { e.printStackTrace(); } }else { System.out.println(Thread.currentThread().getName()+"取款失败!!余额不足"); } } } } public class TestDrawMoneyThread { public static void main(String[] args) { Account account = new Account("12345",2000); Thread manThread = new Thread(new DrawMoneyThread(account, 1300),"男人取款线程");//男人取款线程 Thread womanThread = new Thread(new DrawMoneyThread(account, 1000),"女人取款线程");//女人取款线程 manThread.start(); womanThread.start(); } }
6.5 使用this作为线程对象锁
对象锁的作用是决定了哪些线程具有互斥的效果,其类型必须是对象类型不能是基本数据类型。
语法结构:
synchronized(this){
//同步代码
}
或
public synchronized void accessVal(int newVal){
//同步代码
}
package cn.it.bz.Thread; //程序员类 class Programmer{ private String name; public Programmer(String name) { this.name = name; } //打开电脑 public void openPC() throws InterruptedException { synchronized (this){ //this代表的是张三 System.out.println(this.name+"接通电源"); Thread.sleep(1000);//哪个线程执行该方法,哪个线程就会休眠 System.out.println(this.name+"按开机按键"); Thread.sleep(1000); System.out.println("系统启动中……"); Thread.sleep(1000); System.out.println("系统启动成功!"); } } //编码 public void coding() throws InterruptedException { synchronized (this){ //this代表的是张三 System.out.println(this.name+"双击IDEA程序"); Thread.sleep(1000); System.out.println("IDEA程序启动成功"); Thread.sleep(1000); System.out.println("开开心心写代码"); } } } //打开电脑的工作线程 class OpenPC implements Runnable{ private Programmer programmer; public OpenPC(Programmer programmer) { this.programmer = programmer; } @Override public void run() { try { this.programmer.openPC(); } catch (InterruptedException e) { e.printStackTrace(); } } } //编写代码的工作线程 class Coding implements Runnable{ private Programmer programmer; public Coding(Programmer programmer) { this.programmer = programmer; } @Override public void run() { try { this.programmer.coding(); } catch (InterruptedException e) { e.printStackTrace(); } } } //主线程 public class TestSyncThread { public static void main(String[] args) { Programmer zs = new Programmer("张三"); OpenPC openPC = new OpenPC(zs) ; Thread threadOpenPC = new Thread(openPC); threadOpenPC.start(); Coding coding = new Coding(zs) ; Thread threadCoding= new Thread(coding); threadCoding.start(); } }
6.6 使用字符串作为线程对象锁
因为字符串是引用类型的数据而且字符串是个常量不会改变,因此所有线程都会同步。
语法结构:
synchronized(“字符串”){
//同步代码
}
package cn.it.bz.Thread; //程序员类 class Programmer{ private String name; public Programmer(String name) { this.name = name; } //去卫生间 public void wc() throws InterruptedException { synchronized ("随便写"){ //this代表的是张三 System.out.println(this.name+"打开卫生间门"); Thread.sleep(1000); System.out.println(this.name+"上厕所"); Thread.sleep(1000); System.out.println(this.name+"嘘嘘~"); Thread.sleep(1000); System.out.println(this.name+"离开厕所"); } } } //去卫生间的线程 class WC implements Runnable{ private Programmer programmer; public WC(Programmer programmer) { this.programmer = programmer; } @Override public void run() { try { this.programmer.wc(); } catch (InterruptedException e) { e.printStackTrace(); } } } //主线程 public class TestSyncThread { public static void main(String[] args) { Programmer zs = new Programmer("张三"); Programmer ls = new Programmer("李四"); WC wc1 = new WC(zs) ; Thread threadZSWC= new Thread(wc1); threadZSWC.start(); WC wc2 = new WC(ls) ; Thread threadLSWC= new Thread(wc2); threadLSWC.start(); } }
6.7 使用Class作为线程对象锁
语法结构:
synchronized(XX.class){
//同步代码
}
或
synchronized public static void accessVal()
package cn.it.bz.Thread; //销售类 class Sale{ private String name; public Sale(String name) { this.name = name; } //领奖金 public void money() throws InterruptedException { synchronized (Sale.class){ System.out.println(this.name+"被领导表扬"); Thread.sleep(1000); System.out.println(this.name+"拿钱"); Thread.sleep(1000); System.out.println(this.name+"表示感谢"); Thread.sleep(1000); System.out.println(this.name+"开开心心拿钱走人,O(∩_∩)O"); } } } //程序员类 class Programmer{ private String name; public Programmer(String name) { this.name = name; } //领取奖金 public void money() throws InterruptedException { synchronized (Programmer.class){ System.out.println(this.name+"被领导表扬"); Thread.sleep(1000); System.out.println(this.name+"拿钱"); Thread.sleep(1000); System.out.println(this.name+"表示感谢"); Thread.sleep(1000); System.out.println(this.name+"开开心心拿钱走人,O(∩_∩)O"); } } } //程序员领取奖金的线程 class GetMoney implements Runnable{ private Programmer programmer; public GetMoney(Programmer programmer) { this.programmer = programmer; } @Override public void run() { try { this.programmer.money(); } catch (InterruptedException e) { e.printStackTrace(); } } } //销售领取奖金的线程 class SaleGetMoney implements Runnable{ private Sale sale; public SaleGetMoney(Sale sale) { this.sale = sale; } @Override public void run() { try { this.sale.money(); } catch (InterruptedException e) { e.printStackTrace(); } } } //主线程 public class TestSyncThread { public static void main(String[] args) { Programmer zs = new Programmer("张三"); Programmer ls = new Programmer("李四"); new Thread(new GetMoney(zs)).start(); //张三领取奖金 new Thread(new GetMoney(ls)).start(); Sale sale1 = new Sale("小红"); Sale sale2 = new Sale("小兰"); new Thread(new SaleGetMoney(sale1)).start(); new Thread(new SaleGetMoney(sale2)).start(); } }
相同部门的员工在拿钱时是串行的,不同部门之间是并行的。
6.8 使用自定义对象作为线程对象锁
语法结构:
synchronized(自定义对象){
//同步代码
}
package cn.it.bz.Thread; //经理类 class Manager{ private String name; public Manager(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } //敬酒的方法 public void cheers(String Mname,String Ename) throws InterruptedException { synchronized (this){ //manager充当对象锁 System.out.println(Mname+"经理走到"+Ename+"面前"); Thread.sleep(1000); System.out.println(Ename+"拿起酒杯"); Thread.sleep(1000); System.out.println(Mname+"经理和"+Ename+"干杯"); } } } //敬酒的线程 class Cheers implements Runnable{ private Manager manager; //经理对象 private String Ename; public Cheers(Manager manager, String ename) { this.manager = manager; Ename = ename; } @Override public void run() { try { /* synchronized (this.manager){ //或者在线程类中使用经理对象锁 this.manager.cheers(manager.getName(),Ename); }*/ this.manager.cheers(manager.getName(),Ename); } catch (InterruptedException e) { e.printStackTrace(); } } } //主线程 public class TestSyncThread { public static void main(String[] args) { Manager manager = new Manager("张三"); Cheers cheers1 = new Cheers(manager,"李四"); Thread thread1 = new Thread(cheers1); thread1.start(); Cheers cheers2 = new Cheers(manager,"王五"); Thread thread2 = new Thread(cheers2); thread2.start(); } }
6.9 线程死锁
6.9.1 死锁的概念
“死锁”指的是:
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块需要同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。比如,“化妆线程”需要同时拥有“镜子对象”、“口红对象”才能运行同步块。那么,实际运行时,“小丫的化妆线程”拥有了“镜子对象”,“大丫的化妆线程”拥有了“口红对象”,都在互相等待对方释放资源,才能化妆。这样,两个线程就形成了互相等待,无法继续运行的“死锁状态”。
package cn.it.bz.Thread; //口红类 class Lipstick{ } //镜子类 class Mirror{ } //化妆线程 class MakeUp implements Runnable{ private int flag; //flag = 0表示拿到是口红,否则是镜子 private String name; //化妆人的名字 static Lipstick lipstick = new Lipstick(); //唯一的口红 static Mirror mirror = new Mirror(); //唯一的镜子 public MakeUp(int flag,String name){ this.flag = flag; this.name = name; } //开始化妆 public void doMakeUp() throws InterruptedException { if (this.flag == 0){ //拿到的是口红 synchronized (lipstick){ //口红只有一份,因此会产生线程互斥 System.out.println(name+"拿着口红"); Thread.sleep(1000); synchronized (mirror){ System.out.println(name+"拿着镜子"); } } }else { synchronized (mirror){ System.out.println(name+"拿着镜子"); Thread.sleep(1000); synchronized (lipstick){ System.out.println(name+"拿着口红"); } } } } @Override public void run() { try { doMakeUp(); } catch (InterruptedException e) { e.printStackTrace(); } } } public class DeadLockThread { public static void main(String[] args) { MakeUp makeUp1 = new MakeUp(0,"大丫"); Thread thread_makeUp1 = new Thread(makeUp1); thread_makeUp1.start(); MakeUp makeUp2 = new MakeUp(1,"小丫"); Thread thread_makeUp2 = new Thread(makeUp2); thread_makeUp2.start(); } }
6.9.2 解决线程死锁
死锁是由于 “同步块需要同时持有多个对象锁造成”的,要解决这个问题,思路很简单,就是:同一个代码块,不要同时持有两个对象锁,也就是synchronized中不能嵌套synchronized。
package cn.it.bz.Thread; //口红类 class Lipstick{ } //镜子类 class Mirror{ } //化妆线程 class MakeUp implements Runnable{ private int flag; //flag = 0表示拿到是口红,否则是镜子 private String name; //化妆人的名字 static Lipstick lipstick = new Lipstick(); //唯一的口红 static Mirror mirror = new Mirror(); //唯一的镜子 public MakeUp(int flag,String name){ this.flag = flag; this.name = name; } //开始化妆 public void doMakeUp() throws InterruptedException { if (this.flag == 0){ //拿到的是口红 synchronized (lipstick){ //口红只有一份,因此会产生线程互斥 System.out.println(name+"拿着口红"); Thread.sleep(1000); } synchronized (mirror){ System.out.println(name+"拿着镜子"); } }else { synchronized (mirror){ System.out.println(name+"拿着镜子"); Thread.sleep(1000); } synchronized (lipstick){ System.out.println(name+"拿着口红"); } } } @Override public void run() { try { doMakeUp(); } catch (InterruptedException e) { e.printStackTrace(); } } } public class DeadLockThread { public static void main(String[] args) { MakeUp makeUp1 = new MakeUp(0,"大丫"); Thread thread_makeUp1 = new Thread(makeUp1); thread_makeUp1.start(); MakeUp makeUp2 = new MakeUp(1,"小丫"); Thread thread_makeUp2 = new Thread(makeUp2); thread_makeUp2.start(); } }
七、线程并发协作
7.1 生产者消费者模式介绍
多线程环境下,我们经常需要多个线程的并发和协作。这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”。
角色介绍
- 什么是生产者?
生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。 - 什么是消费者?
消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。 - 什么是缓冲区?
消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。
缓冲区是实现并发的核心,缓冲区的设置有两个好处:
- 实现线程的并发协作
有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离,解除了生产者与消费者之间的耦合。
- 解决忙闲不均,提高效率
生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据 。
7.2 实现消费者与生产者模式
package cn.it.bz.Thread; //大饼类 class DaBing{ private int id; public int getId() { return id; } public void setId(int id) { this.id = id; } } //数据缓冲区,放大饼的,不需要实现线程接口,缓冲区不是线程。 class SyncStack{ //定义存放大饼的盒子 private DaBing[] daBings = new DaBing[10]; //定义操作盒子的索引 private int index; //放大饼 synchronized public void setDaBing(DaBing daBing) throws InterruptedException { //先判断盒子是否满了 while (daBings.length == index){ //满了不生产了让线程等待 this.wait(); //wait是Object类的方法,该方法必须在synchronized块中调用,wait执行后线程会将持有的对象锁释放并进入阻塞状态。- } //让消费者来取大饼 this.notify(); this.daBings[index] = daBing; index++; //注意 } //取大饼 synchronized public DaBing getDaBing() throws InterruptedException { //盒子有饼才能取 while (index == 0){ //等待 this.wait(); } this.notify();//该方法必须在synchronized块中调用,唤醒一个线程 this.index--; return daBings[index]; } } //生产者线程 class ShengChan implements Runnable{ //生产者生产的大饼需要放到缓冲区 private SyncStack syncStack; public ShengChan(SyncStack syncStack) { this.syncStack = syncStack; } @Override public void run() { //生产大饼 for (int i = 0; i < 9; i++) { DaBing daBing = new DaBing(); daBing.setId(i); System.out.println("生产大饼"+daBing.getId()); try { syncStack.setDaBing(daBing); //放到缓冲区 } catch (InterruptedException e) { e.printStackTrace(); } } } } //消费者线程 class XiaoFei implements Runnable{ //消费者到缓冲区拿 private SyncStack syncStack; public XiaoFei(SyncStack syncStack) { this.syncStack = syncStack; } @Override public void run() { //取大饼 for (int i = 0; i < 9; i++) { DaBing daBing = new DaBing(); try { DaBing daBing1 = syncStack.getDaBing();//拿大饼 System.out.println("取大饼"+daBing1.getId()); } catch (InterruptedException e) { e.printStackTrace(); } } } } //主线程 public class TestProduceThread { public static void main(String[] args) { //创建缓冲区对象 SyncStack syncStack = new SyncStack(); //生产者线程 Thread thread_shengChan = new Thread(new ShengChan(syncStack)); //消费者线程 Thread thread_xiaofei = new Thread(new XiaoFei(syncStack)); thread_shengChan.start(); thread_xiaofei.start(); } }
wait方法就好比张三和李四合租房子。张三去厨房做饭了,然后将厨房的门锁上了,好巧不巧张三此时心脏病犯了,张三马上晕倒了,于是张三把厨房的门打开了,让李四进去了。如果进行对象锁的释放的话就相当于是厨房的门永远锁着,李四永远进不了厨房。