15_多线程
文章目录
- OS中的基本概念
- 进程(process)与线程(thread)
- 串行(serial)、并行(parallel)与并发(concurrency)
- 同步(synchronization)与异步(asynchronization)
- java程序运行原理
- java命令+主类类名运行原理
- 多线程的实现方式一:继承Thread类
- 获取和设置线程的名称
- 线程的调度方式
- 调度方式的分类
- java中采用哪种调度方式
- 线程的优先级
- 线程控制API
- 线程休眠sleep
- 线程合并join
- 线程礼让yield
- 守护线程setDaemon
- 线程中断stop(已过时)
- 线程的生命周期
- 线程的几种状态
- 多线程实现方式二:实现Runnable接口
- 方式一 VS 方式二
- 多线程数据安全问题
- 出现的情况
- 产生原因
- 解决多线程数据安全问题
- synchronized
- synchronized的细节问题
- Lock锁
- ReentrantLock可重入锁
- 死锁
- 生产者消费者模型
- 线程间通信
- wait与notify机制
- wait()
- notify()
- notifyAll()
- 为什么wait,notify,notifyAll方法不定义在Thread类中?
Java的代码都是在某一条执行路径下,按照顺序依次执行的,一条路径就是一个线程。
OS中的基本概念
进程(process)与线程(thread)
进程:
- 进程是操作系统进行资源调度与分配的基本单位
- 正在运行的程序或者软件
线程:
- 进程中有多个子任务,每个子任务就是一个线程。
- 从执行路径的角度看, 一条执行路径就是一个线程
- 线程是CPU进行资源调度与分配的基本单位
进程与线程的关系
- 线程依赖于进程而存在
- 一个进程中可以有多个线程(最少1个)
- 线程共享进程资源
- 举例: 迅雷, Word拼写
串行(serial)、并行(parallel)与并发(concurrency)
串行
- 一个任务接一个任务按顺序执行
并行
- 在同一个时间点(时刻)上, 多个任务同时运行
并发
- 在同一时间段内,多个任务同时运行
同步(synchronization)与异步(asynchronization)
单道批处理:内存中只能运行一个进程
多道批处理:内存中可以运行多个进程, "同时"发生 (进程的上下文切换)
- 进程的上下文切换
-
- 保护现场
-
- 恢复现场
-
现代操作系统:引入了线程
java程序运行原理
java命令+主类类名运行原理
- java命令会启动jvm进程, jvm进程会创建一个线程(main线程)
- 执行main线程里面的main方法
- jvm是多线程的。除了main线程外,还有其他线程,起码还有一个垃圾回收线程
多线程的实现方式一:继承Thread类
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
步骤:
- 定义一个类继承Thread类
- 重写run方法
- 创建子类对象
- 通过start方法启动线程
eg:
public class Demo {public static void main(String[] args) {System.out.println("start");// 3. 创建子类对象MyThread myThread = new MyThread();// 4. 通过start方法启动线程myThread.start();System.out.println("end");}
}/*多线程的实现方式一:继承Thread*/// 1. 定义一个类继承Thread类
class MyThread extends Thread {// 2. 重写run方法@Overridepublic void run() {// 放的是在线程中要执行的代码System.out.println("nihao");}
}
注:两条路径执行会得到这个结果,如果想end
和nihao
颠倒过来,在输出end语句上面增加一个睡眠TimeUnit.SECONDS.sleep(1);
注意事项:
- 多线程的执行特点是随机的
- start方法跟run方法有什么区别?
- start方法才是开辟新的执行路径。
- run方法只是普通方法调用, 并没有开辟新的执行路径, 还是一条执行路径, 仍然是单线程的。
- 同一个线程能否启动多次?
- 不能启动多次, 会产生
java.lang.IllegalThreadStateException
的异常报错
- 不能启动多次, 会产生
- 谁才代表一个线程?
- Thread及其子类对象才代表线程, 就是t1,t2
获取和设置线程的名称
获取名称:
设置名称:
线程的调度方式
给线程分配CPU处理权的过程
调度方式的分类
- 协同式线程调度
- 线程的执行时间由线程本身决定, 当这个线程执行完后报告操作系统,切换到下一个线程
- 抢占式的线程调度方式
- 线程的执行时间由系统决定, 哪个线程抢到了CPU的执行,哪个线程执行
java中采用哪种调度方式
- Java中采用的是抢占式的调度方式
线程的优先级
注:
- MAX_PRIORITY 线程可以具有的最高优先级。10
- MIN_PRIORITY 线程可以具有的最低优先级。1
- NORM_PRIORITY 分配给线程的默认优先级。 5
结论:
- 优先级没有什么用
- 它仅仅只能被看做是一种"建议"(对操作系统的建议),
实际上,操作系统本身,有它自己的一套线程优先级 (静态优先级 + 动态优先级)
线程控制API
线程休眠sleep
暂停执行的作用
注:
TimeUnit.SECONDS.sleep(1);
和Thread.sleep(1000);
是等价的,只不过后者单位是毫秒,前者单位是秒
线程合并join
等待该线程终止。
执行结果上看等待的是子线程, 哪个线程调用了join, 等待的就是这个线程。
eg:
public class Demo {public static void main(String[] args) {// 创建线程对象MyThread1 thread1 = new MyThread1();// 启动startthread1.start();// 使用join方法try {thread1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}// main函数打印3个数for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName() + "----" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}class MyThread1 extends Thread {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName() + "----" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
线程礼让yield
注:虽然yield方法
使当前线程放弃了CPU的执行权 但是仍然可以参与下轮的CPU的竞争。
eg:
public class Demo {public static void main(String[] args) {YieldThread a = new YieldThread("A");YieldThread b = new YieldThread("B");a.start();b.start();}
}class YieldThread extends Thread{public YieldThread(String name) {super(name);}@Overridepublic void run() {for (int i = 0; i < 3; i++) {System.out.println(this.getName() + "----" + i);// 暂停当前正在执行的线程对象,并执行其他线程。Thread.yield();}}
}
守护线程setDaemon
线程分类:
- 用户线程(默认)
- 系统的工作线程
- 守护线程
- 为用户线程服务的线程(GC垃圾回收线程),系统的后台线程,可以把它当做用户线程的奴仆。
- on - 如果为 true,则将该线程标记为守护线程。
注意事项:
- 当正在运行的线程都是守护线程时,Java 虚拟机退出。
- 该方法必须在启动线程前调用。
- 如果在
start
之后写出,则会报错 —>java.lang.IllegalThreadStateException
- 如果在
线程中断stop(已过时)
该方法具有固有的不安全性
eg:
需求:
-
定义一个flag标记, true 是正常状态 false中断
-
主线程打印3个数 打印1个 休眠1秒 中断子线程
-
创建子线程 打印10个数 休眠1秒
-
打印之前判断一下是否中断 如果正常----> 打印数据
-
如果发生了中断-------> 不在打印, 并且把中断信息保存到log.txt文件中
-
格式:年月日 时分秒 哪个线程发生了中断
public class Demo {public static void main(String[] args) {// 创建线程对象ThreadStop threadStop = new ThreadStop();// 启动线程threadStop.start();for (int i = 0; i < 3; i++) {System.out.println("main" + "----" + i);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}threadStop.flag = false;}
}class ThreadStop extends Thread {// 定义一个标记flagboolean flag = true;@Overridepublic void run() {for (int i = 0; i < 10; i++) {// 判断线程的状态if (flag) {// 如果正常----> 打印数据System.out.println(this.getName() + "----" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}} else {// 创建字符输出流对象FileWriter fileWriter = null;try {// 如果发生了中断-------> 不在打印, 并且把中断信息保存到log.txt文件中fileWriter = new FileWriter("log.txt");// 创建SimpleDataFormat对象,指定日期格式SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// write(String s)fileWriter.write(sdf.format(new Date()) + getName() + "发生了中断");fileWriter.flush();} catch (IOException e) {e.printStackTrace();} finally {if (fileWriter != null) {try {// close 释放资源fileWriter.close();} catch (IOException e) {e.printStackTrace();}}}}}}
}
线程的生命周期
线程的几种状态
理论层面上:
新建
- 刚
new
出来的线程对象
就绪
- 线程执行了
start()
方法后
执行
- 拥有CPU的执行权
阻塞
- 线程会处于阻塞状态
死亡
run
方法执行完
代码层面上:
线程状态的转换:
多线程实现方式二:实现Runnable接口
步骤:
- 定义一个类实现Runnable接口
- 重写run方法
- 创建子类对象
- 创建线程对象, 把实现了Runnable接口的子类对象作为参数传递
- start方法启动线程
使用的构造方法:
eg:
public class Demo {public static void main(String[] args) {/*多线程的实现方式二:实现Runnable接口*/// 3. 创建子类对象MyRunnable myRunnable = new MyRunnable();// 4. 创建线程对象, 把实现了Runnable接口的子类对象作为参数传递Thread thread = new Thread(myRunnable);// 5. start方法启动线程thread.start();}
}// 1. 定义一个类实现Runnable接口
class MyRunnable implements Runnable{// 2. 重写run方法@Overridepublic void run() {System.out.println("son of thread is running!");}
}
eg:(也可以使用lambda表达式或者匿名内部类)
// 匿名内部类
new Thread(new Runnable() {@Overridepublic void run() {System.out.println("1111");}
}).start();// lambda
new Thread(()->{System.out.println("222222");
}).start();
Runnable中的run方法为什么会运行在子线程中?
见下面的伪代码:
class Thread{
// 成员变量private Runnable target;// 构造方法Thread(Runnable target){init(target);}void init(){// 左边是成员变量 右边是传过来的参数 给成员变量赋值this.target = target;}void run(){if(target != null){target.run()}}
}
方式一 VS 方式二
- 步骤上, 方式一4步, 方式二是5步
- 方式一通过继承的方式(单继承的局限性),方式二通过实现接口的方式
- 方式二把线程跟线程上要做的事情区分开来(执行路径,跟执行路径上的任务区分开来) 解耦
- 方式二便于数据共享
eg:
public class Demo {public static void main(String[] args) {MyShare runnable = new MyShare();Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable);Thread thread3 = new Thread(runnable);thread1.setName("window No.1");thread2.setName("window No.2");thread3.setName("window No.3");thread1.start();thread2.start();thread3.start();}
}class MyShare implements Runnable{int ticket = 100;@Overridepublic void run() {while(true){if(ticket >0){// 模拟网络时延try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+ "sell No." + (ticket --) + " ticket");}}}
}
多线程数据安全问题
出现的情况
- 重复情况
- 出现不存在的数据
产生原因
- 多线程的运行环境(需求)
- 多线程共享数据(需求)
- 存在非原子操作(可以进行操作)
- 原子操作: 一个不可分割的操作(一个操作要么一次执行完, 要么不执行)
解决多线程数据安全问题
synchronized
同步代码块:
- 同步代码块的锁对象(对象 , 用来充当锁的角色)
- 锁对象可以是任意的java对象, 但是要保证是同一个。
语法:
synchronized(锁对象){// 对共享数据的访问操作
}
修改后为:
public class Demo {public static void main(String[] args) {MyShare runnable = new MyShare();Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable);Thread thread3 = new Thread(runnable);thread1.setName("window No.1");thread2.setName("window No.2");thread3.setName("window No.3");thread1.start();thread2.start();thread3.start();}
}class MyShare implements Runnable {int ticket = 100;// 定义一把锁Object object = new Object();@Overridepublic void run() {while (true) {synchronized (object) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+ "sell No." + (ticket--) + " ticket");}}}}
}
同步方法:
同步方法的锁对象是:this
eg:
private synchronized void sell() {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+ "sell No." + (ticket--) + " ticket");}}
静态同步方法:
静态同步方法的锁对象是:字节码文件对象(Class对象)
字节码文件对象的获取方法: 对象.getClass()
或者类名.class
eg:
private static synchronized void sell() {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+ "sell No." + (ticket--) + " ticket");}}}// 上面的同步代码块是
synchronized (MyShare.getClass()); // MyShare是创建的对象
synchronized的细节问题
- 执行流程
- A、B 2个线程访问
synchronized代码块
中的内容 - 假设A线程抢到了CPU的执行权, 看一下锁对象是否可用,如果可用, A线程就持有了锁对象, A线程访问同步代码块的内容
- A还没有访问结束,发生了线程切换,B抢到了执行权,B也想访问同步代码块中的内容, 看一下锁是否可用, 不可用, 对于B线程来说, 只能在
synchronized
外面等待, B就处于同步阻塞状态 - A再次抢到执行权. A接着执行,访问结束, 退出
synchronized
代码块, A释放锁 - B线程就可以获取锁, 访问
synchronized
代码块中的内容.
- A、B 2个线程访问
eg:
public class Demo {// 定义一把锁public static final Object obj = new Object();public static void main(String[] args) {// 定义线程Anew Thread(() -> {synchronized (obj) {System.out.println("进入线程A");try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("退出线程A");}},"A").start();// main休眠1stry {System.out.println("main函数休眠1s");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 定义线程Bnew Thread(() -> {synchronized (obj) {System.out.println("进入线程B");try {TimeUnit.SECONDS.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("退出线程B");}},"B").start();}
}
出现异常就会释放锁:
eg:
public class Demo {// 定义一把锁public static final Object OBJECT = new Object();public static int count = 0;public static void main(String[] args) {// 定义线程Anew Thread(() -> {System.out.println("进入线程A");synchronized (OBJECT) {while (true) {count++;System.out.println("A进入到synchronized中");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}if(count == 5){System.out.println("count = " + count);// 人为制造一个异常System.out.println(10/0);}}}}, "A").start();// main休眠1stry {System.out.println("main函数休眠1s");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 定义线程Bnew Thread(() -> {System.out.println("进入线程B");synchronized (OBJECT) {System.out.println("B进入到了synchronized中");}System.out.println("退出线程B");}, "B").start();}
}
- 2条字节码指令(monitorenter/monitorexit)
Lock锁
它是一个接口
方法:
ReentrantLock可重入锁
一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
eg:
public class Demo {public static void main(String[] args) {MyShare1 runnable = new MyShare1();Thread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable);Thread thread3 = new Thread(runnable);thread1.setName("window No.1");thread2.setName("window No.2");thread3.setName("window No.3");thread1.start();thread2.start();thread3.start();}
}class MyShare1 implements Runnable {int ticket = 100;//定义一把Lock锁Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {// 获取锁// lock()lock.lock();try{if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+ "sell No." + (ticket--) + " ticket");}}finally {// 释放锁// unlock()lock.unlock();}}}
}
synchronized VS Lock
- synchronized是关键字, Lock是个接口
- synchronized是一把隐式的锁, 加锁和释放锁是由jvm自动完成的。Lock它是一把真正的(显式的)锁, 我们能看到加锁跟释放锁的过程(lock , unlock)
死锁
2个或以上线程因为争抢资源而造成的互相等待的现象
发生的场景:一般出现在同步代码块嵌套
语法:
synchronized(objA){synchronized(objB){
}
}
eg:
/*
模拟死锁产生的场景*/public class Demo {public static void main(String[] args) {new Thread(new DieLock(true)).start();new Thread(new DieLock(false)).start();}
}// 定义一个锁类
class MyLock {public static final Object OBJECTA = new Object();public static final Object OBJECTB = new Object();
}class DieLock implements Runnable {// 定义一个flagboolean flag = true;public DieLock(boolean flag) {this.flag = flag;}@Overridepublic void run() {if (flag) {synchronized (MyLock.OBJECTA) {System.out.println("if A");synchronized (MyLock.OBJECTB) {System.out.println("if B");}}} else {synchronized (MyLock.OBJECTB) {System.out.println("else A");synchronized (MyLock.OBJECTA) {System.out.println("else B");}}}}
}
解决死锁的办法:
- 使加锁的顺序保持一致
eg:
@Overridepublic void run() {if (flag) {synchronized (MyLock.OBJECTA) {System.out.println("if A");synchronized (MyLock.OBJECTB) {System.out.println("if B");}}} else {synchronized (MyLock.OBJECTA) {System.out.println("else A");synchronized (MyLock.OBJECTB) {System.out.println("else B");}}}}
- 再加一把锁,使其变成原子操作
eg:
@Overridepublic void run() {if (flag) {synchronized (MyLock.OBJECT) {synchronized (MyLock.OBJECTA) {System.out.println("if A");synchronized (MyLock.OBJECTB) {System.out.println("if B");}}}} else {synchronized (MyLock.OBJECT) {synchronized (MyLock.OBJECTB) {System.out.println("else A");synchronized (MyLock.OBJECTA) {System.out.println("else B");}}}}}
生产者消费者模型
同步代码块版本:
// 蒸笼类
/*
使用同步代码块*/// 蒸笼类
public class Box {// 定义成员Food food;// 生产包子的方法 只要生产者会执行public void makeFood(Food newFood){food = newFood;System.out.println(Thread.currentThread().getName()+ "生产了" + food);}// 吃包子的方法 只要消费者会执行public void eatFood(){System.out.println(Thread.currentThread().getName()+ "吃了" + food);food = null;}// 判断蒸笼状态的方法public boolean isEmpty(){return food == null;}}// 定义包子类
class Food{String name;int price;public Food(String name, int price) {this.name = name;this.price = price;}@Overridepublic String toString() {return "Food{" +"name='" + name + '\'' +", price=" + price +'}';}
}// 生产者任务
public class ProducerTask implements Runnable{// 定义一个成员Box box;Food[] foods = {new Food("pork",3),new Food("mutton",4),new Food("tomato",2)};Random random = new Random();public ProducerTask(Box box) {this.box = box;}@Overridepublic void run() {// 生产包子while (true){// 使用synchronizedsynchronized (box){// 按照上图逻辑if(box.isEmpty()){// 如果蒸笼为空,没有包子生产包子 放进去int index = random.nextInt(foods.length);box.makeFood(foods[index]);box.notify();}else{try {box.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}
}// 消费者任务
public class ConsumerTask implements Runnable{Box box;public ConsumerTask(Box box) {this.box = box;}@Overridepublic void run() {// 吃包子while (true){synchronized (box){if(box.isEmpty()){try {box.wait();} catch (InterruptedException e) {e.printStackTrace();}}else{box.eatFood();box.notify();}}}}
}// 运行
public class Demo {public static void main(String[] args) {// 创建一个Box对象Box box = new Box();// 创建生产者任务ProducerTask producerTask = new ProducerTask(box);// 创建消费者任务ConsumerTask consumerTask = new ConsumerTask(box);// 创建生产者线程Thread t1 = new Thread(producerTask,"生产者线程");// 创建消费者线程Thread t2 = new Thread(consumerTask,"消费者线程");// start启动线程t1.start();t2.start();}
}
同步方法版本:
/*
使用同步方法*/// 蒸笼类
public class Box {// 定义成员Food food;// 生产包子的方法 只要生产者会执行public synchronized void makeFood(Food newFood){if (food == null){food = newFood;System.out.println(Thread.currentThread().getName()+ "生产了" + food);this.notify();}else{try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}// 吃包子的方法 只要消费者会执行public synchronized void eatFood(){if(food == null){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}else{System.out.println(Thread.currentThread().getName()+ "吃了" + food);food = null;this.notify();}}
}// 定义包子类
class Food{String name;int price;public Food(String name, int price) {this.name = name;this.price = price;}@Overridepublic String toString() {return "Food{" +"name='" + name + '\'' +", price=" + price +'}';}
}// 生产者任务
public class ProducerTask implements Runnable {// 定义一个成员Box box;Food[] foods = {new Food("pork", 3),new Food("mutton", 4),new Food("tomato", 2)};Random random = new Random();public ProducerTask(Box box) {this.box = box;}@Overridepublic void run() {// 生产包子while (true) {// 按照上图逻辑// 如果蒸笼为空,没有包子生产包子 放进去int index = random.nextInt(foods.length);box.makeFood(foods[index]);}}
}// 消费者任务
public class ConsumerTask implements Runnable {Box box;public ConsumerTask(Box box) {this.box = box;}@Overridepublic void run() {// 吃包子while (true) {box.eatFood();}}
}public class Demo {public static void main(String[] args) {// 创建一个Box对象Box box = new Box();// 创建生产者任务ProducerTask producerTask = new ProducerTask(box);// 创建消费者任务ConsumerTask consumerTask = new ConsumerTask(box);// 创建生产者线程Thread t1 = new Thread(producerTask,"生产者线程");// 创建消费者线程Thread t2 = new Thread(consumerTask,"消费者线程");// start启动线程t1.start();t2.start();}
}
当有多个生产 多个消费者的时候, 出现"卡顿"的现象, 怎么解决?
答:使用notifyAll
线程间通信
wait与notify机制
作用:
- 拥有相同锁的线程才可以实现wait/notify机制
- wait()方法使线程暂停运行,而notify() 方法通知暂停的线程继续运行
- wait会释放锁,notify不会释放锁
wait()
- 阻塞功能:
- 当在某线程中,
对象上.wait()
, 在哪个线程中调用wait(), 导致哪个线程处于阻塞状态 - 当某线程,因为调用执行某对象的wait(),而处于阻塞状态,我们说,该线程在该对象上阻塞。
- 当在某线程中,
- 唤醒条件:
- 当某线程,因为某对象A的wait(), 而处于阻塞状态时,如果要唤醒该线程,只能在其他线程中,
再同一个对象(即对象A)上调用其notify()或notifyAll() - 即在线程的阻塞对象上,调用notify或notifyAll方法,才能唤醒,在该对象上阻塞的线程
- 当某线程,因为某对象A的wait(), 而处于阻塞状态时,如果要唤醒该线程,只能在其他线程中,
- 运行条件:
- 当前线程必须拥有此对象监视器。
- 监视器:指synchronized代码块中的锁对象
- 即我们只能在,当前线程所持有的synchronized代码块中的,锁对象上调用wait方法,
才能正常执行 - 如果没有锁对象就会有这样一个异常
IllegalMonitorStateException
- 当前线程必须拥有此对象监视器。
- 执行特征:
- a.该线程发布(release)对此监视器的所有权
- b.等待(阻塞)
- 注意:Thread的sleep方法,执行的时候:该线程不丢失任何监视器的所属权
wait方法的使用条件:必须有synchronized,在锁对象上调用wait
wait方法的执行特征:使当前线程暂停执行,处于阻塞状态,释放锁
eg:
public class Demo {// 定义一把锁public static final Object OBJECT = new Object();public static void main(String[] args) {// 创建并启动一个线程new Thread(()->{System.out.println("A is running");synchronized (OBJECT){System.out.println("进入A的同步代码块");try {TimeUnit.SECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}// 使用wait方法try {System.out.println("wait before");OBJECT.wait();System.out.println("wait after");} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();// main休眠try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 创建并启动一个线程new Thread(()->{System.out.println("B is running");synchronized (OBJECT){System.out.println("进入B的同步代码块");// 执行notify方法System.out.println("notify before");OBJECT.notify();System.out.println("notify after");try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();}
}
notify()
- 唤醒在此对象监视器上等待的单个线程。
- 如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
- 选择是任意性的
notifyAll()
唤醒多个等待的线程
为什么wait,notify,notifyAll方法不定义在Thread类中?
任意Java对象都能充当锁的角色
相关文章:
15_多线程
文章目录 OS中的基本概念进程(process)与线程(thread)串行(serial)、并行(parallel)与并发(concurrency)同步(synchronization)与异步(asynchronization) java程序运行原理java命令主类类名运行原理 多线程的实现方式一࿱…...
吉他打谱软件Guitar Pro8苹果Mac电脑简体中文特别版
Guitar Pro 8 Mac是一款吉他编曲学习软件,用于吉他、贝和其他弦乐器的制谱和演奏,这是一个多轨编辑器,具有集成的 MIDI 编辑器、合唱绘图仪、吉他、节拍器和其他音乐家工具。它使您能够编辑吉他、贝司和尤克里里、乐谱、指法谱,并…...
go study oneday
这段代码的详细解释,涵盖了Go 语言变量声明、函数调用、用户输入、类型转换以及结果输出。 package main import "fmt" func main() {var num1 intvar num2 float32var num3 intfmt.Println("请输入数字一:")fmt.Scanln(&num1)f…...
Avatar虚拟数字人方案,元宇宙时代的企业新动力
随着元宇宙概念的兴起,虚拟数字人技术逐渐成为各行业关注的焦点。为了满足市场需求,美摄科技凭借专业、自研的虚拟数字人技术,结合强大的ChatGPT能力,隆重推出Avatar虚拟数字人方案,助力企业实现营销及内容创作的生产力…...
用golang 实现给图片添加文字水印
package mainimport ("fmt""github.com/golang/freetype""image""image/draw""image/jpeg""io""os""time" )func main() {// 打开原始图片file, err : os.Open("004.jpeg")if err …...
苹果电脑Markdown文本编辑Typora mac功能介绍
Typora mac是一款跨平台的Markdown编辑器,支持Windows、MacOS和Linux操作系统。它具有实时预览功能,能够自动将Markdown文本转换为漂亮的排版效果,让用户专注于写作内容而不必关心格式调整。Typora Mac版除了支持常见的Markdown语法外&#x…...
大型语言模型与知识图谱的完美结合:从LLMs到RAG,探索知识图谱构建的全新篇章
最近,使用大型语言模型(LLMs)和知识图谱(KG)开发 RAG(Retrieval Augmented Generation)流程引起了很大的关注。在这篇文章中,我将使用 LlamaIndex 和 NebulaGraph 来构建一个关于费城费利斯队(Philadelphia Phillies)的 RAG 流程。 我们用的是开源的 NebulaGraph 来…...
Vue 缓存Hook:提高接口性能,减少重复请求
前言 在开发 Web 应用时,我们经常会遇到需要重复调用接口的场景。例如,当用户频繁刷新页面或进行某个操作时,我们可能需要多次请求相同的数据。这不仅会增加服务器负担,还会导致用户体验下降。为此,我们可以使用缓存机…...
【Python机器学习】用于回归的决策树
用于回归的决策树与用于分类的决策树类似,在DecisionTreeRegressor中实现。DecisionTreeRegressor不能外推,也不能在训练数据范围之外的数据进行预测。 利用计算机内存历史及格的数据进行实验,数据展示: import pandas as pd im…...
numpy库的一些常用函数
文章目录 广播(broadcast)迭代数组数组运算修改数组的形状 修改数组维度连接数组分割数组数组元素的添加与删除Numpy算术函数Numpy 统计函数Numpy排序、条件筛选函数条件筛选 import numpy as np anp.arange(15).reshape(3,5)aarray([[ 0, 1, 2, 3, …...
成员变量与局部变量的区别?
如果你现在需要准备面试,可以关注我的公众号:”Tom聊架构“,回复暗号:”578“,领取一份我整理的50W字面试宝典,可以帮助你提高80%的面试通过率,价值很高!! 语法形式&…...
ES6---判断对象是否为{}
介绍 使用es6语法判断一个对象是否为{} 示例 使用ES6的Object.keys()方法,返回值是对象中属性名组成的数组 let obj {}let keys Object.keys(obj) if(keys.length){alert(对象不为{}) }else{alert(对象为{}) }代码地址 https://gitee.com/u.uu.com/js-test/b…...
高性能、可扩展、分布式对象存储系统MinIO的介绍、部署步骤以及代码示例
详细介绍 MinIO 是一款流行的开源对象存储系统,设计上兼容 Amazon S3 API,主要用于私有云和边缘计算场景。它提供了高性能、高可用性以及易于管理的对象存储服务。以下是 MinIO 的详细介绍及优缺点: 架构与特性: 开源与跨平台&am…...
oracle重启数据库lsnrctl重启监听
oracle重启数据库lsnrctl重启监听 su到oracle用户下,命令 su - oracle切换需要启动的数据库实例: export ORACLE_SIDorcl进入Sqlplus控制台,命令: sqlplus /nolog以系统管理员登录,命令: connect / as sysdba如果是…...
08、Kafka ------ 消息存储相关的配置-->消息过期时间设置、查看主题下的消息存活时间等配置
目录 消息存储相关的配置★ 消息的存储介绍★ 消息过期时间及处理方式演示:log.cleanup.policy 属性配置 ★ 修改指定主题的消息保存时间演示:将 test2 主题下的消息的保存时间设为10个小时1、先查看test2主题下的配置2、然后设置消息的保存时间3、然后再…...
JAVA基础学习笔记-day15-File类与IO流
JAVA基础学习笔记-day15-File类与IO流 1. java.io.File类的使用1.1 概述1.2 构造器1.3 常用方法1、获取文件和目录基本信息2、列出目录的下一级3、File类的重命名功能4、判断功能的方法5、创建、删除功能 2. IO流原理及流的分类2.1 Java IO原理2.2 流的分类2.3 流的API 3. 节点…...
WPF ComboBox限制输入长度
在WPF中,你可以通过两种方式来限制ComboBox的输入长度: 使用PreviewTextInput事件:你可以在这个事件的处理程序中检查输入文本的长度,如果超过最大长度则阻止输入。 <ComboBox PreviewTextInput"ComboBox_PreviewTextIn…...
windows配置网络IP地址的方法
在Windows系统中配置网络IP地址,可以按照以下步骤进行: 打开“控制面板”,选择“网络和Internet”选项。在“网络和Internet”窗口中,单击“网络和共享中心”选项。在“网络和共享中心”窗口中,单击“更改适配器设置”…...
windows配置电脑网络IP的方法
通过控制面板配置IP地址: 打开控制面板: 可以通过在开始菜单中搜索“控制面板”来打开控制面板。选择“网络和Internet”或“网络和共享中心”: 在控制面板中,根据 Windows 版本不同,选中对应的选项进入网络设置。点击…...
MySQL,原子性rename
RENAME TABLE old_table TO backup_table, new_table TO old_table;...
FPGA之按键消抖
目录 1.原理 2.代码 2.1 key_filter.v 2.2 tb_key_filter.v 1.原理 按键分为自锁式按键和机械按键,图左边为自锁式按键 上图为RS触发器硬件消抖,当按键的个数比较多时常常使用软件消抖。硬件消抖会使用额外的器件占用电路板上的空间。 思路就是使用延…...
国内知名的技术平台
1、csdn,中文最大的技术交流平台 2、iteye,程序员的交流平台,归属csdn 3、cnblogs,这个也不错 4、简书也不错...
C#操作注册表
说明 今天用C#开发了一个简单的服务,需要设置成为自启动,网上有很多方法,放到启动运行等,但是今天想介绍一个,通过修改注册表实现,同时介绍一下操作注册表。 private void TestReg(){//仅对当前用户有效 H…...
Unity中BRP下的深度图
文章目录 前言一、在Shader中使用1、在使用深度图前申明2、在片元着色器中 二、在C#脚本中开启摄像机深度图三、最终效果 前言 在之前的文章中,我们实现了URP下的深度图使用。 Unity中URP下使用屏幕坐标采样深度图 在这篇文章中,我们来看一下BRP下深度…...
物联网的感知层、网络层与应用层分享
物联网的概念在很早以前就已经被提出,20世纪末期在美国召开的移动计算和网络国际会议就已经提出了物联网(Internet of Things)这个概念。 最先提出这个概念的是MIT Auto-ID中心的Ashton教授,他在研究RFID技术时,便提出了结合物品编码、互联网…...
kafka KRaft 集群搭建
kafka KRaft集群安装 包下载 https://downloads.apache.org/kafka/3.6.1/kafka_2.13-3.6.1.tgzkafka集群构建好后的数据目录结构 [rootlocalhost data]# tree /data/kafka /data/kafka ├── kafka-1 # 节点1源码目录 ├── kafka-2 # 节点2源码目录 ├── kafka-3 # 节点…...
oracle角色管理
常用角色 CONNECT,RESOURCE,DBA,EXP_FULL_DATABASE,IMP_FULL_DATABASE 1角色可以自定义,语法与创建用户一样 CREATE role role1 IDENTIFIED by 123; 2授权权限给角色 --自定义角色 CREATE role role1 IDENTIFIED by 123; --授权权限给角色 GRANT create view, …...
汽车信息安全--芯片厂、OEM安全启动汇总(2)
目录 1.STM32 X-CUBE-SBSFU 2.小米澎湃OS安全启动 3.小结 在汽车信息安全--芯片厂、OEM安全启动汇总-CSDN博客,我们描述了芯驰E3的安全启动机制,接下来我们继续看其他芯片、OEM等安全启动机制。 1.STM32 X-CUBE-SBSFU 该产品全称Secure Boot and Secure...
HarmonyOS 开发基础(五)Button
HarmonyOS 开发基础(五)Button Entry Component struct Index {build() {Row() {Column() {// Button:ArkUI 的基础组件 按钮组件// label 参数:文字型按钮Button(我是按钮)// width:属性方法,设置组件的宽…...
带前后端H5即时通讯聊天系统源码
带有前后端的H5即时通讯聊天系统源码。该源码是一个开源的即时通信demo,需要前后端配合使用。它的主要目的是为了促进学习和交流,并为大家提供开发即时通讯功能的思路。尽管该源码提供了许多功能,但仍需要进行自行开发。该项目最初的开发初衷…...
网站要怎么做/数据分析网
Top NSD NETWORK DAY02 案例1:划分VLAN 案例2:多交换机VLAN的划分 案例3:配置trunk中继链路 案例4:链路聚合配置 案例5:配置静态路由 案例6:三层交换机基本配置 1 案例1:划分VLAN 1.1 问题…...
网站建设基本知识/百度账号管理中心
linux 环境变量 ******************************* linux环境创建过程:用户进入系统后,会先读取全局配置文件,再读取用户主目录下的配置文件 shell会话类型:login shell、non-login shell login shell文件加载流程:先…...
济南市建设信用网站/c++线上培训机构哪个好
数值分析里面经常会涉及到用MATLAB程序实现用列主元消去法分别解方程组Axb具体的方法和代码以如下方程(3x3矩阵)为例进行说明:用列主元消去法分别解方程组Axb,用MATLAB程序实现:(1)1、 实现该方程的解的MATLAB代码可以分为两种,一…...
网站怎样做注册窗口/软文模板
连续自适应平移(CAMShift)算法基本上一个改进版的meanshift算法,可构建一个了解所选对象特征并自动跟踪它的对象跟踪器。meanshift算法理解,选择一个感兴趣区域,希望对象跟踪器跟踪该对象。在这个区域中,根…...
网站开发的安全问题/天津百度推广公司地址
前言 大家好,我是程序员manor。作为一名大数据专业学生、爱好者,深知面试重要性,很多学生已经进入暑假模式,暑假也不能懈怠,正值金九银十的秋招 接下来我准备用30天时间,基于大数据开发岗面试中的高频面试题…...
上海公司建立网站/关键词快速排名平台
RecyclerView没有像之前ListView提供divider属性,而是提供了方法 recyclerView.addItemDecoration()其中ItemDecoration需要我们自己去定制重写,一开始可能有人会觉得麻烦不好用,最后你会发现这种可插拔设计不仅好用,而且功能强大…...