一 (线程同步)
1 线程冲突和同步
(有线程冲突——>通过线程同步解决——>并行化转换成线性化)
(线程同步——>一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用)
1.1 线程冲突
可能动作一刚改完名字,时间片到了,时间片给到动作二,就会产生“张冠李戴”的问题,就是线程冲突
1.2 同步问题的提出
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比如:教室里,只有一台电脑,多个人都想使用。天然的解决办法就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。
1.3 线程同步的概念
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。 这时候,我们就需要用到“线程同步”。 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
2 线程冲突案例演示
(两个取款线程同时对同一个账户取钱)
我们以银行取款经典案例来演示线程冲突现象。
银行取钱的基本流程基本上可以分为如下几个步骤。
(1)用户输入账户、密码,系统判断用户的账户、密码是否匹配。
(2)用户输入取款金额
(3)系统判断账户余额是否大于或等于取款金额
(4)如果余额大于或等于取款金额,则取钱成功;如果余额小于取款金额,则取钱失败。
/**
* 账户类--------------------------------------------------------------------------------
*/
class Account{
//账号
private String accountNo;
//账户的余额
private double balance;
public Account() {
}
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
/**
* 取款线程--------------------------------------------------------------------------------
*/
class DrawThread implements Runnable{
//账户对象
private Account account;
//取款金额
private double drawMoney;
//构造方法
public DrawThread(){}
public DrawThread(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.drawMoney);
//休眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新账户余额
this.account.setBalance(this.account.getBalance()- this.drawMoney);
System.out.println("\t 余额为:"+this.account.getBalance());
}else{
System.out.println(Thread.currentThread().getName()+" 取钱失败,余额不足");
}
}
}
//主线程----------------------------------------------------------------------------------
public class TestDrawMoneyThread {
public static void main(String[] args) {
//初始化账户
Account account = new Account("1234",1000);
//创建两个取款线程——>对同一个账户操作
new Thread(new DrawThread(account,800),"老公").start();
new Thread(new DrawThread(account,800),"老婆").start();
}
}
出现线程冲突清空了
3 实现线程同步
(synchronized关键字——>把并行化转化成线性化)
(synchronized(锁对象){ 同步代码 }
——>锁对象里面只能是对象类型
——>拥有相同锁对象的线程才会做线程同步
——>同步代码里面放(原子操作),就是需要把并行转换成串行的)
(synchronized 方法和 synchronized 块——>块可以缩小同步范围,提高效率——>范围越大串行越多,效率慢)
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。这套机制就是synchronized关键字。
3.1 synchronized语法结构:
- synchronized(锁对象){ 同步代码 }
3.2 synchronized关键字使用时需要考虑的问题:
- 需要对那部分的代码在执行时具有线程互斥的能力(线程互斥:并行变串行)。
- 需要对哪些线程中的代码具有互斥能力(通过synchronized锁对象来决定)。
3.3 它包括两种用法:
synchronized 方法和 synchronized 块。
-
synchronized 方法
通过在方法声明中加入 synchronized关键字来声明,语法如下:
1 public synchronized void accessVal(int newVal);
synchronized 在方法声明时使用:放在访问控制符(public)之前或之后。这时同一个对象下synchronized方法在多线程中执行时,该方法是同步的,即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候,当前线程(就是在synchronized方法内部的线程)执行完该方法后,别的线程才能进入。
-
synchronized块
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
线程联合和线程同步的区别:——>线程同步是“如何安全地共享资源”,而线程联合是“如何正确地结束线程”
线程联合(Thread Joining)
目的:确保一个线程在继续执行之前等待另一个线程完成其任务。
机制:通过join()方法或其他同步辅助类(如CountDownLatch、Future等)来实现线程间的等待关系。
作用范围:通常作用于线程间,用于控制线程的执行顺序,确保某个线程在另一个线程结束后再继续执行。
结果:确保主线程或其他线程在子线程完成工作后再继续执行,避免资源使用上的冲突。
线程同步(Thread Synchronization)
目的:确保多个线程在访问共享资源时的一致性和完整性,防止数据竞争和不一致性问题。
机制:通过锁、信号量、临界区等机制来控制对共享资源的访问,确保同一时间只有一个线程可以执行特定的代码段。
作用范围:通常作用于代码块或方法级别,用于控制对共享资源的并发访问。
结果:防止多个线程同时修改同一数据,确保数据的一致性。
总结:
线程同步关注的是如何在多个线程之间协调对共享资源的访问,以保证数据的一致性和完整性。
线程联合关注的是如何在线程之间建立等待关系,确保一个线程在另一个线程完成后再继续执行。
简而言之,线程同步是关于“如何安全地共享资源”,而线程联合是关于“如何正确地结束线程”。两者在多线程编程中都是非常重要的,但它们解决的问题和使用的场景是不同的。
4 解决线程冲突案例演示
(synchronized加在run方法上面语法上面没有错,但是由于run方法是运行线程比较特殊,加上还是并行)
(确定同步范围——>确定锁对象(同一个账户(this.account))——>只有对同一个账户取钱才同步)
/**
* 账户类---------------------------------------------------------------------------------
*/
class Account{
//账号
private String accountNO;
//账户余额
private double balance;
public Account() {
}
public Account(String accountNO, double balance) {
this.accountNO = accountNO;
this.balance = balance;
}
public String getAccountNO() {
return accountNO;
}
public void setAccountNO(String accountNO) {
this.accountNO = accountNO;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
/**
* 取款线程-----------------------------------------------------------------------------
*/
class DrawThread implements Runnable{
//账户对象
private Account account;
//取款金额
private double drawMoney;
public DrawThread(){}
public DrawThread(Account account,double drawMoney){
this.account = account;
this.drawMoney = drawMoney;
}
/**
* 取款线程体-----------------------------------------------------------------------------
*/
@Override
public void run() {
//同步块-----------------------------------------------------------------------------
synchronized (this.account){
//判断当前账户余额是否大于或等于取款金额
if(this.account.getBalance() >= this.drawMoney){
System.out.println(Thread.currentThread().getName()+" 取钱成功!吐出钞票"+this.drawMoney);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新账户余额
this.account.setBalance(this.account.getBalance() - this.drawMoney);
System.out.println("\t 余额为:"+this.account.getBalance());
}else{
System.out.println(Thread.currentThread().getName()+" 取钱失败,余额不足");
}
}
}
}
public class TestDrawMoneyThread {
public static void main(String[] args) {
Account account = new Account("1234",1000);
new Thread(new DrawThread(account,800),"老公").start();
new Thread(new DrawThread(account,800),"老婆").start();
}
}
5 this作为线程对象锁
(在所有线程中——>!!!相同对象!!!——>才会互斥)
语法结构:
synchronized(this){
//同步代码
}
或
public synchronized void accessVal(int newVal){
//同步代码
}
//默让是this——>同一个对象——>才会互斥
示例:
/**
* 定义程序员类
*/
class Programmer{
private String name;
public Programmer(String name){
this.name = name;
}
//定义两个方法---------------------------------------------------------------------------
/**
* 打开电脑
*/
synchronized public void computer(){
try {
System.out.println(this.name + " 接通电源");
Thread.sleep(500);
System.out.println(this.name + " 按开机按键");
Thread.sleep(500);
System.out.println(this.name + " 系统启动中");
Thread.sleep(500);
System.out.println(this.name + " 系统启动成功");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 编码
*/
synchronized public void coding(){
try {
System.out.println(this.name + " 双击Idea");
Thread.sleep(500);
System.out.println(this.name + " Idea启动完毕");
Thread.sleep(500);
System.out.println(this.name + " 开开心心的写代码");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 打开电脑的工作线程
*/
class Working1 extends Thread{
private Programmer p;
public Working1(Programmer p){
this.p = p;
}
@Override
public void run() {
this.p.computer();
}
}
/**
* 编写代码的工作线程
*/
class Working2 extends Thread{
private Programmer p;
public Working2(Programmer p){
this.p = p;
}
@Override
public void run() {
this.p.coding();
}
}
//主线程
public class TestSyncThread {
public static void main(String[] args) {
Programmer p = new Programmer("张三");
new Working1(p).start();
new Working2(p).start();
}
}
6 字符串作为线程对象锁
(synchronized(“字符串”)——>字符串随便是什么都行,只是个标识(因为字符串是常量,无论如何只要用这个方法,锁肯定一样))
(不同对象——>也会互斥——>所有线程在执行synchronized时都会同步/互斥)
语法结构:
synchronized(“字符串”){
//同步代码
}
/**
* 定义程序员类
*/
class Programmer{
private String name;
public Programmer(String name){
this.name = name;
}
/**
* 去卫生间
*/
public void wc(){
synchronized ("suibian") {
try {
System.out.println(this.name + " 打开卫生间门");
Thread.sleep(500);
System.out.println(this.name + " 开始排泄");
Thread.sleep(500);
System.out.println(this.name + " 冲水");
Thread.sleep(500);
System.out.println(this.name + " 离开卫生间");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 去卫生间的线程
*/
class WC extends Thread{
private Programmer p;
public WC(Programmer p){
this.p = p;
}
@Override
public void run() {
this.p.wc();
}
}
//主线程
public class TestSyncThread {
public static void main(String[] args) {
Programmer p = new Programmer("张三");
Programmer p1 = new Programmer("李四");
Programmer p2 = new Programmer("王五");
new WC(p).start();
new WC(p1).start();
new WC(p2).start();
}
}
7 Class作为线程对象锁
(synchronized(XX.class)——>xx就是Class——>是class类在内存中的对象型式——>(Java万物皆对象))
(!!!相同类里面的,不同对象!!!——>中的synchronized会互斥)
(或者synchronized加在静态方法前面)
语法结构:
synchronized(XX.class){
//同步代码
}
或
synchronized public static void accessVal()//在静态方法上添加synchronized
示例:员工领取奖金,相同部门串行化一个一个进去领,不同部门并行化
问:能不能用this——>不行因为是不用的对象
能不能用字符串——>不行,因为是不同员工去不同部门,那样就所以员工去一个领导办公室了
/**
* 定义销售员工类
*/
class Sale{
private String name;
public Sale(String name){
this.name = name;
}
/**
* 领取奖金
*/
synchronized public static void money(){ //或者synchornized(Sale.class){}
try {
System.out.println(Thread.currentThread().getName() + " 被领导表扬");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " 拿钱");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " 对公司表示感谢");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " 开开心心的拿钱走人");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Programmer{
private String name;
public Programmer(String name){
this.name = name;
}
/**
* 领取奖金
*/
public void money(){
synchronized (Programmer.class) {
try {
System.out.println(this.name + " 被领导表扬");
Thread.sleep(500);
System.out.println(this.name + " 拿钱");
Thread.sleep(500);
System.out.println(this.name + " 对公司表示感谢");
Thread.sleep(500);
System.out.println(this.name + " 开开心心的拿钱走人");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 程序员领取奖金
*/
class ProgrammerMoney extends Thread{
private Programmer p;
public ProgrammerMoney(Programmer p){
this.p = p;
}
@Override
public void run() {
this.p.money();
}
}
/**
* 销售部门领取奖金
*/
class SaleMoney extends Thread{
private Sale p;
public SaleMoneyThread(Sale p){
this.p = p;
}
@Override
public void run() {
this.p.money();
}
}
public class TestSyncThread {
public static void main(String[] args) {
//程序员
Programmer p = new Programmer("张三");
Programmer p1 = new Programmer("李四");
new ProgrammerMoney(p).start();
new ProgrammerMoney(p1).start();
//销售部
Sale s = new Sale("张晓丽");
Sale s1 = new Sale("王晓红");
new SaleMoney(s).start();
new SaleMoney(s1).start();
}
}
//部门之间并行,部门里的员工串行
8 自定义对象作为线程对象锁
(synchronized,可以使用在线程类的run方法中控制——>或者在普通类调用方法时控制)
(synchronized(自定义对象)——>之前this是在普通类的方法里面用——>自定义对象是在线程类里面用——>效果相同)
(无论是在普通类的方法中使用this
作为锁,还是在线程类中使用自定义对象作为锁,只要确保锁对象是相同的,它们的效果是相同的)
语法结构:
synchronized(自定义对象){
//同步代码
}
示例:经理给每个人敬酒
class Manager{
private String name;
public Manager(String name){
this.name = name;
}
public String getName(){
return this.name;
}
/**
* 敬酒
*/
public void cheers(String mName,String eName){
try {
System.out.println(mName + " 来到 " + eName + " 面前");
Thread.sleep(500);
System.out.println(eName + " 拿起酒杯");
Thread.sleep(500);
System.out.println(mName + " 和 " + eName + " 干杯");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 敬酒线程类
*/
class CheersThread extends Thread{
private Manager manager;
private String name;
public CheersThread(String name,Manager manager){
this.name = name;
this.manager = manager;
}
@Override
public void run() {
synchronized (this.manager) { //线程类里面要用自定义对象,不能再用this了
this.manager.cheers(this.manager.getName(), name);
}
}
}
public class TestSyncThread {
public static void main(String[] args) {
Manager manager = new Manager("张三丰");
new CheersThread("张三",manager).start();
new CheersThread("李四",manager).start();
}
}
9 什么是线程死锁
(synchronized的使用不当——>多个线程都在等待对方释放对象锁——>引起的线程等待现象)
“死锁”指的是:
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
某一个同步块需要同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。比如,“化妆线程”需要同时拥有“镜子对象”、“口红对象”才能运行同步块。那么,实际运行时,“小丫的化妆线程”拥有了“镜子对象”,“大丫的化妆线程”拥有了“口红对象”,都在互相等待对方释放资源,才能化妆。这样,两个线程就形成了互相等待,无法继续运行的“死锁状态”。
10 线程死锁案例和解决
(static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
——>唯一的口红和镜子)
(Synchronized里面又有一个Synchronized,嵌套了,互相等待对方,导致都不动了)
(死锁——> “同步块需要同时持有多个对象锁造成——>要解决这个问题——>同一个代码块,不要同时持有两个对象锁——>不让synchronized嵌套,把它拿出来)
示例:这个程序会一直处在阻塞状态
/**
* 口红类
*/
class Lipstick{
}
/**
* 镜子类
*/
class Mirror{
}
/**
* 化妆线程类
*/
class Makeup extends Thread{
private int flag; //flag=0:拿着口红。flag!=0:拿着镜子
private String girlName;
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
public Makeup(int flag,String girlName){
this.flag = flag;
this.girlName = girlName;
}
@Override
public void run() {
this.doMakeup();
}
/**
* 开始化妆
*/
public void doMakeup(){
if(flag == 0){
synchronized (lipstick){ //相当于字符串,因为是static修饰的
System.out.println(this.girlName+" 拿着口红");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在上一个块里面
synchronized (mirror){
System.out.println(this.girlName+" 拿着镜子");
}
}
}else{
synchronized (mirror){
System.out.println(this.girlName+" 拿着镜子");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick){
System.out.println(this.girlName+" 拿着口红");
}
}
}
}
}
public class DeadLockThread {
public static void main(String[] args) {
new Makeup(0,"大丫").start();
new Makeup(1,"小丫").start();
}
}
解决:不让synchronized嵌套,把它拿出来
二 (线程并发协作)
1 生产者/消费者模式
(生产者——>负责生产数据的模块,消费者——>负责处理数据的模块)
(生产者和消费者之间放一个缓冲区——>实现线程的并发协作,解决忙闲不均,提高效率)
(为什么java能做到跨平台性——>虚拟机——>Java代码和操作系统解除耦合度——>的一个中间层)
多线程环境下,我们经常需要多个线程的并发和协作。这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”。
角色介绍
-
什么是生产者?
生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。
-
什么是消费者?
消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。
-
什么是缓冲区?
消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。
缓冲区是实现并发的核心,缓冲区的设置有两个好处:
-
实现线程的并发协作
有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离,解除了生产者与消费者之间的耦合。
-
解决忙闲不均,提高效率
生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据 。
2 创建缓冲区
(wait()方法 , notify()方法,notidyAll()方法)
(缓冲区不是线程类,就是一个能存放数据的区域
——>1 定义存放数据的空间
——>2 定义操作空间的索引
——>3 再提供放数据和取数据的方法
——>4 因为放和取操作的是缓冲区同一个空间,要给他们this锁
——>5 放数据之前判断是否已满用while
——>6 如果内存满了调用Object类里面的wait()方法,this.wait(),只能在synchronized里面使用——>执行wait方法时——>线程会将持有的对象锁释放,并进入阻塞状态,其他需要该对象锁的线程就可以继续运行
——>7 取数据时——>判断没有数据就wait等待
——>8 放数据和取数据,都会进入wait状态,用Object类里面的notify()方法,唤醒处于等待状态队列中的一个线程,也只能在synchronized里面使用(notifyAll唤醒所有的)
——>互相唤醒,放在wait方法之外)
/**
* 定义馒头类------------------------------------------------------------------------------
*/
class ManTou{
private int id;
public ManTou(int id){
this.id = id;
}
public int getId(){
return this.id;
}
}
/**
* 定义缓冲区类----------------------------------------------------------------------------
*/
class SyncStack{
//1 定义存放馒头的盒子
private ManTou[] mt = new ManTou[10];
//2 定义操作盒子的索引
private int index;
/**
* 3 放馒头-----------------------------------------------------------------------------
*/
public synchronized void push(ManTou manTou){
//5 判断盒子是否已满
while(this.index == this.mt.length){
try {
/**6
* 语法:wait(),该方法必须要在synchronized块中调用。
* wait执行后,线程会将持有的对象锁释放,并进入阻塞状态,
* 其他需要该对象锁的线程就可以继续运行了。
*/
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//8 唤醒取馒头的线程
/**
* 语法:该方法必须要在synchronized块中调用。
* 该方法会唤醒处于等待状态队列中的一个线程。
*/
this.notify();
this.mt[this.index] = manTou;
this.index++;
}
/**
* 4 取馒头-----------------------------------------------------------------------------
*/
public synchronized ManTou pop(){
//7
while(this.index == 0){
try {
/**
* 语法:wait(),该方法必须要在synchronized块中调用。
* wait执行后,线程会将持有的对象锁释放,并进入阻塞状态,
* 其他需要该对象锁的线程就可以继续运行了。
*/
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//8 唤醒
this.notify();
this.index--;
return this.mt[this.index];
}
}
public class TestProduceThread {
public static void main(String[] args) {
}
}
3 创建生产者消费者线程
(通过构造方法将缓冲区传递进来)
(生产者和消费者公用同一个缓冲区)
/**
* 定义生产者线程类
*/
class ShengChan extends Thread{
private SyncStack ss;
public ShengChan(SyncStack ss){
this.ss = ss;
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("生产馒头:"+i);
ManTou manTou = new ManTou(i);
this.ss.push(manTou);
}
}
}
/**
* 定义消费者线程类
*/
class XiaoFei extends Thread{
private SyncStack ss;
public XiaoFei(SyncStack ss){
this.ss = ss;
}
@Override
public void run() {
for(int i=0;i<10;i++){
ManTou manTou = this.ss.pop();
System.out.println("消费馒头:"+i);
}
}
}
public class ProduceThread {
public static void main(String[] args) {
//创建缓冲区
SyncStack ss = new SyncStack();
//生产者线程
new ShengChan(ss).start();
//消费者线程
new XiaoFei(ss).start();
}
}
4 线程并发协作总结
(synchronized可阻止并发更新同一个共享资源,实现了同步,但是synchronized不能用来实现不同线程之间的消息传递——>并发协作(也叫线程通信))
(在实际开发中,尤其是“架构设计”中,会大量使用这个模式。 对于初学者了解即可,如果晋升到中高级开发人员,这就是必须掌握的内容)
线程并发协作(也叫线程通信)
生产者消费者模式:
-
生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
-
对于生产者,没有生产产品之前,消费者要进入等待状态。而生产了产品之后,又需要马上通知消费者消费。
-
对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费。
-
在生产者消费者问题中,仅有synchronized是不够的。synchronized可阻止并发更新同一个共享资源,实现了同步但是synchronized不能用来实现不同线程之间的消息传递(通信)。
-
那线程是通过哪些方法来进行消息传递(通信)的呢?见如下总结:
方法名 作 用 final void wait() 表示线程一直等待,直到得到其它线程通知 void wait(long timeout) 线程等待指定毫秒参数的时间 final void wait(long timeout,int nanos) 线程等待指定毫秒、微秒的时间 final void notify() 唤醒一个处于等待状态的线程 final void notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行 -
以上方法均是java.lang.Object类的方法
都只能在同步方法或者同步代码块中使用,否则会抛出异常。
标签:Java,Thread,synchronized,线程,new,多线程,public,name From: https://blog.csdn.net/2301_80867182/article/details/144473296