目录
wait notify
原理
Owner发现条件不满足,调用wait方法,即可进入WaitSet编程WAITING状态
BLOCKED和WAITING的线程都是处于阻塞状态,不占用时间片
BLOCKED线程会在Owner线程释放锁时唤醒
WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味着获得锁,仍需进入EntryList重新竞争
API介绍
- obj.wait()让进入到Object监视器的线程到waitSet等待
- obj.notify()从监视器的waitSet等待的线程挑一个唤醒
- obj.notifyAll()唤醒全部在Object上waitSet等待的线程
- obj.wait(Long)让该线程等待一定的时间,到时间自动唤醒
示例
@Slf4j
public class Test9 {
static final Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
synchronized (obj){
log.debug("我是线程一");
try {
obj.wait();//让线程在obj上一直等待下去
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("我是线程一,我被唤醒了");
}
}).start();
new Thread(()->{
synchronized (obj){
log.debug("我是线程二");
try {
obj.wait();//让线程在obj上一直等待下去
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("我是线程二,我被唤醒了");
}
}).start();
Thread.sleep(2000);
synchronized (obj){
obj.notify();
}
}
}
17:56:47.401 [Thread-0] DEBUG com.example.com.yunlong.test1.Test9 - 我是线程一
17:56:47.416 [Thread-1] DEBUG com.example.com.yunlong.test1.Test9 - 我是线程二
17:56:49.401 [Thread-0] DEBUG com.example.com.yunlong.test1.Test9 - 我是线程一,我被唤醒了
sleep(long n)和wait(long n)的区别
- sleep是Thread方法,而wait是Object方法
- sleep不需要强制和synchroniezd使用,wait需要
- sleep在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放对象锁
park unpark
定义
park()
和 unpark()
是 Java 中用于线程阻塞和解除阻塞的方法,它们通常与 LockSupport
类一起使用。
示例
@Slf4j
public class Test11 {
public static void main(String[] args) throws InterruptedException {
Thread t1 =new Thread(()->{
log.debug("t1启动");
try {
Thread.sleep(2000);//睡两秒之后,给park住
LockSupport.park();//调用park,暂停线程
log.debug("park t1");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
Thread.sleep(1000);
log.debug("unpark t1");
LockSupport.unpark(t1);//不暂停线程
}
}
特点
与Object的wait和notify相比
- wait,notify,notifyall必须配合Object Monitor(锁对象)一起使用,而park,unpark不需要
- park和unpark是以线程为单位来阻塞和唤醒线程,而notify只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么精确
- park&unpark可以先unpark,而wait¬ify不能先notify
原理
底层采用c语言编写,在java层面不体现,感兴趣可自行搜索研究
线程状态转换
情况1 new-> runnable
- 当调用t.start()方法时,由new ->runnable
情况2 runnable<-->waiting
- t线程用synchronized(obj)获取了对象锁后
- 调用wait方法时,t线程从runnable->waiting
- 调用obj.notify(),obj.notifyAll(),t.interrupt()时候
- 竞争锁成功,t线程从waiting->runnable
- 竞争锁失败,t线程从waiting->blocked
情况3 runnable<-->waiting
- 当前线程调用t.join方法时候 ,当前线程从runnable->waiting
- 注意当前线程在t线程对象的监视器上等待
- t线程运行结束后,或者调用了当前线程的interrupt()时,当前线程从waiting->runnable
情况4 runnable <-->waiting
- 当前线程调用LockSupport.park()方法会让当前线程从runnable->waiting
- 调用LockSupport.unpark()方法时候或者调用了线程的interrupt(),会让目标线程从waiting->runnable
情况5 runnable<->timed_waiting
- t线程用synchronized(obj)获取了对象锁后
- 调用obj.wait(long n)方法时,t线程从runnable->timed_waiting
- t线程等待时间超过了n毫秒,或者调用了obj.notify(),obj.notifyAll(),t.interrupt()时
- 竞争锁成功,t线程从timed_waiting ->runnable
- 竞争锁失败,t线程从timed_waiting->blocked
情况6 runnable<->timed_waiting
- 当线程调用t.join(long n)方法时,当前线程从runnable->timed_waiting
- 注意是当前线程在t线程对象的监视器等待
- 当前线程等待时间超过了n毫秒,或者t线程结束,或者调用了当前线程的interrupt()时,当前线程从timed_waiting->runnable
情况7 runnable<->timed_waiting
- 当前线程调用了Thread.sleep(long n),当前线程从runnable->timed_waiting
- 当前线程等待时间超过了n毫秒,当前线程从timed_waiting->runnable
情况8 runnable<->timed_waiting
- 当前线程调用LockSupport.parkNanos(long nanos),当前线程从runnable->timed_waiting
- 调用LockSupport.unpark(目标线程)或者调用了线程的interrupt(),或是等待超时,会让目标线程从timed_waiting->runnable
情况9 runnable->blocked
- t线程用synchronized(obj)获取了对象锁时如果竞争失败,从runnable->blocked
- 持obj锁线程的同步代码块执行完毕,会唤醒该对象上的所有blocked的线程重新竞争,如果其中t线程竞争成功,从blocked->runnable,其他失败的线程仍然是blocked
情况10 runnable<->terminated
- 当前线程所有代码执行完毕,进入terminated
多把锁
两个互不相干的锁,称之为多把锁
将锁的粒度细分
- 好处,是可以增强并发度
- 坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
活跃性
死锁
定义
死锁(Deadlock)是指两个或多个线程在互相等待对方释放资源而无法继续执行的状态。具体来说,当线程 A 持有资源 X 并等待获取资源 Y,同时线程 B 持有资源 Y 并等待获取资源 X,导致两个线程都无法继续执行,这种情况就称为死锁。
示例
public class DeadlockExample {
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Holding resource 1...");
try {
Thread.sleep(100); // 加入睡眠,增加死锁的可能性
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for resource 2...");
synchronized (resource2) {
System.out.println("Thread 1: Holding resource 1 and resource 2...");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 2...");
try {
Thread.sleep(100); // 加入睡眠,增加死锁的可能性
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for resource 1...");
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 1 and resource 2...");
}
}
});
thread1.start();
thread2.start();
}
}
哲学家就餐问题
描述
有五位哲学家,围坐在圆桌旁
- 他们只做两件事,思考和吃饭,思考一会吃口饭,吃口饭之后接着思考
- 吃饭时要用两根筷子,桌子上一共有无根筷子,每位哲学家左右手各有一根筷子,
- 如果筷子被身边的人拿着,自己就要等待
示例
public class Test12 {
public static void main(String[] args) {
Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底",c1,c2).start();
new Philosopher("张飞",c2,c3).start();
new Philosopher("关羽",c3,c4).start();
new Philosopher("刘备",c4,c5).start();
new Philosopher("上官",c5,c1).start();
}
}
@Slf4j
class Philosopher extends Thread{//哲学家类
Chopstick left;
Chopstick right;
public Philosopher(String name ,Chopstick left,Chopstick right){
super(name);
this.left = left;
this.right = right;
}
@Override
public void run(){
while(true){
synchronized (left){
synchronized (right){
try {
eat();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
public void eat() throws InterruptedException {
log.debug("eating");
Thread.sleep(1000);
}
}
class Chopstick{//筷子类
String name;
public Chopstick(String name){this.name = name;}
@Override
public String toString(){return "筷子{"+name+"}";}
}
活锁
定义
活锁(Livelock)是多线程编程中的一种特殊情况,类似于死锁,但不同之处在于线程不会被阻塞,它们会一直尝试执行某个操作,但始终无法取得进展,导致系统无法继续正常运行。
活锁通常发生在多个线程互相响应对方的动作,但每个线程都在避免某种情况发生,结果导致所有线程都无法继续前进的情况。尽管线程没有被阻塞,但它们却没有做任何有意义的工作,而是一直忙于处理某些条件,导致系统资源被浪费。
示例
@Slf4j
public class Test13 {
static int count =10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(()->{//对count进行--操作,直到count=0,退出循环
while (count>0){
try {
Thread.sleep(200);
count--;
log.debug("count{}",count);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
new Thread(()->{//对count进行++操作,直到count=20,退出循环
while (count<20){
try {
Thread.sleep(200);
count++;
log.debug("count{}",count);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
}
}
饥饿
在多线程环境中,饥饿(Starvation)是指某个线程长时间无法获取所需的资源而无法继续执行的情况。饥饿可能发生在竞争共享资源的情况下,导致某些线程无法获得足够的执行机会,从而无法完成其任务。