建个网站需要多少钱圣宝电动车大架号在哪里,wordpress定制主题,wordpress 封面图像,企业管理咨询考试题及答案1、同步模式保护性暂停 用一个线程等待另一个线程的执行结果
有一个结果需要从一个线程传递到另一个线程#xff0c;让他们关联同一个中间类。如果有结果不断从一个线程到另一个线程那么可以使用消息队列#xff08;见生产者/消费者#xff09;。JDK 中#xff0c;join 的…1、同步模式保护性暂停 用一个线程等待另一个线程的执行结果
有一个结果需要从一个线程传递到另一个线程让他们关联同一个中间类。如果有结果不断从一个线程到另一个线程那么可以使用消息队列见生产者/消费者。JDK 中join 的实现、Future 的实现采用的就是此模式。因为要等待另一方的结果因此归类到同步模式。 Slf4j(topic c.Demo1)
public class Demo1 {public static void main(String[] args) {ObjectResponse response new ObjectResponse();Thread t1 new Thread(() - {try {Thread.sleep(5000);response.notifyWaitResult(运行结果);} catch (InterruptedException e) {throw new RuntimeException(e);}}, 生产者);Thread t2 new Thread(() - {Object res response.waitResult(2000);log.info(获取结果:{}, res);}, 消费者);//生产者和消费者线程同时启动当消费者接收到消息后会唤醒生产者并且给response赋值生产者返回结果log.info(生产者启动);t1.start();log.info(消费者启动);t2.start();}
}class ObjectResponse {private static final Logger log LoggerFactory.getLogger(ObjectResponse.class);Object response;public Object waitResult(long waitTime) {synchronized (this) {//记录开始时间long startTime System.currentTimeMillis();//记录一次循环执行了多久long workTime 0L;while (response null) {if (workTime waitTime){log.error(已超时);break;}try {//避免虚假唤醒的问题。//假设等待时间为2s,然后wait了1秒被虚假唤醒下次还需要等2s,实际上只需要等1sthis.wait(waitTime - workTime);} catch (InterruptedException e) {throw new RuntimeException(e);}//本次循环的执行时间workTime System.currentTimeMillis() - startTime;}return response;}}public void notifyWaitResult(Object response) {synchronized (this) {this.response response;this.notifyAll();}}} 2、join原理
public final synchronized void join(long millis)throws InterruptedException {//记录当前时间long base System.currentTimeMillis();long now 0;//传递参数的时间小于0抛出异常if (millis 0) {throw new IllegalArgumentException(timeout value is negative);}//调用join时未传递参数if (millis 0) {//当线程存活时一直等待while (isAlive()) {wait(0);}//调用join时传入了参数} else {//线程存活时while (isAlive()) {//计算剩余等待时间long delay millis - now;//剩余等待时间为0时结束循环if (delay 0) {break;}//等待剩余时间wait(delay);//记录本轮消耗的时间now System.currentTimeMillis() - base;}}} 3、多任务版同步模式保护性暂停 下图中 Futures 就好比居民楼一层的信箱每个信箱有房间编号左侧的 t0t2t4 就好比等待邮件的居民右 侧的t1t3t5 就好比邮递员。 如果需要在多个类之间使用 GuardedObject 对象作为参数传递不是很方便因此设计一个用来解耦的中间类 这样不仅能够解耦【结果等待者】和【结果生产者】还能够同时支持多个任务的管理。 消息中间类需要增加一个ID进行不同消息的区分。
class GuardedObject {// 标识 Guarded Objectprivate int id;public GuardedObject(int id) {this.id id;}public int getId() {return id;}// 结果private Object response;// 获取结果// timeout 表示要等待多久 2000public Object get(long timeout) {synchronized (this) {// 开始时间 15:00:00long begin System.currentTimeMillis();// 经历的时间long passedTime 0;while (response null) {// 这一轮循环应该等待的时间long waitTime timeout - passedTime;// 经历的时间超过了最大等待时间时退出循环if (timeout - passedTime 0) {break;}try {this.wait(waitTime); // 虚假唤醒 15:00:01} catch (InterruptedException e) {e.printStackTrace();}// 求得经历时间passedTime System.currentTimeMillis() - begin; // 15:00:02 1s}return response;}}// 产生结果public void complete(Object response) {synchronized (this) {// 给结果成员变量赋值this.response response;this.notifyAll();}}
}
创建信箱类
这里的信箱使用线程安全的HashTable集合。
class mailBoxes{private static MapInteger,GuardedObject boxes new Hashtable();private static int id 0;public synchronized static int getId(){return id;//获取自增id}public static GuardedObject getBox(int id){return boxes.remove(id);//取出id后删除对应信息}/*** 创建GuardedObject对象并且放入boxes管理* return*/public static GuardedObject createGuardedObject(){GuardedObject guardedObject new GuardedObject(getId());boxes.put(guardedObject.getId(),guardedObject);return guardedObject;}public static SetInteger getIds(){return boxes.keySet();}}
创建居民类模拟接受信件
居民首先要在邮箱内创建一个属于自己的格子放一个信件进去
/*** 居民,投递信件*/
Slf4j(topic c.People)
class People extends Thread{Overridepublic void run() {//创建GuardedObject对象并且放入boxes管理相当于创建了一个信箱格子(GuardedObject)并且放了一封信进去(id,mail)GuardedObject guardedObject mailBoxes.createGuardedObject();log.info(放一封信进入信箱格子信件id:{}, guardedObject.getId());Object mail guardedObject.get(2000);log.info(接收到邮递员送信完成反馈,id:{},内容:{},guardedObject.getId(),mail);}
}
创建邮递员类模拟发送信件
从格子中拿走信件并且派送给予居民反馈
/*** 投递员发送信件*/
Slf4j(topic c.postMan)
class postMan extends Thread{private int id;private String mail;public postMan(int id, String mail) {this.id id;this.mail mail;}Overridepublic void run() {//从信箱中取信GuardedObject box mailBoxes.getBox(id);log.info(送信id{}内容:{},id,mail);//送信,并且给居民反馈。box.complete(mail);}
} 模拟三个居民在信箱中创建了三个格子三个邮递员取出信件送信然后给予居民反馈。
public class Demo2 {public static void main(String[] args) throws InterruptedException {for (int i 0; i 3; i) {new People().start();}Thread.sleep(1000);for (Integer id : mailBoxes.getIds()) {new postMan(id, 内容 id).start();}}
}
可以看到线程一的信件由线程三去邮递并且给予了线程一反馈。 4、异步模式生产者/消费者
与前面的保护性暂停中的 GuardObject 不同不需要产生结果和消费结果的线程一一对应。消费队列可以用来平衡生产和消费的线程资源。生产者仅负责产生结果数据不关心数据该如何处理而消费者专心处理结果数据。消息队列是有容量限制的满时不会再加入数据空时不会再消耗数据。JDK 中各种阻塞队列采用的就是这种模式。 首先封装一个消息类
/*** 封装消息类*/
class Message{private Integer id;private Object message;public Message() {}public Message(Integer id, Object message) {this.id id;this.message message;}/*** 获取* return id*/public Integer getId() {return id;}/*** 设置* param id*/public void setId(Integer id) {this.id id;}/*** 获取* return message*/public Object getMessage() {return message;}/*** 设置* param message*/public void setMessage(Object message) {this.message message;}public String toString() {return Message{id id , message message };}
}
创建消息队列中间类
当队列中消息数量达到队列最大长度时生产者陷入阻塞消费者消费完消息后将队列头部的消息移除唤醒生产者。
当队列中消息数量为0时消费者陷入阻塞生产者向队列的尾部存入一条消息唤醒消费者。
*** 线程消息队列通信*/
Slf4j(topic c.MessageQueue)
class MessageQueue {//模拟消息队列private LinkedListMessage queue new LinkedList();//最大消息数量private Integer maxMessageSize;public MessageQueue(Integer maxMessageSize) {this.maxMessageSize maxMessageSize;}/*** 向队列写入消息* param message*/public void setMessage(Message message) {synchronized (queue){while (queue.size() maxMessageSize) {try {log.error(Message queue is full);queue.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}queue.addLast(message);log.info(add message: {}, message);queue.notifyAll();}}/*** 读取消息* return*/public Message getMessage() {synchronized (queue){while (queue.size() 0) {log.error(Message queue is empty);try {queue.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}Message message queue.removeFirst();log.info(remove message: {}, message);queue.notifyAll();return message;}}}
public class Demo1 {public static void main(String[] args) {//创建容量为2的消息队列MessageQueue messageQueue new MessageQueue(2);//创建三个生产者for (int i 0; i 3; i) {int id i;new Thread(()-{messageQueue.setMessage(new Message(id,消息id));},生产者i).start();}new Thread(()-{while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}Message message messageQueue.getMessage();}},消费者).start();}
}
此时队列的长度为2有三个生产者当发现队列已满唤起消费者消费者将消息消费后此时队列中还有1个消息唤起生产者。 5、parkunpark
5.1、基本概念 park当一个线程被挂起时它暂时停止执行并进入一种等待或睡眠状态。线程可以被挂起因为等待某些条件的发生例如等待某个资源可用或者等待某个条件变为真。 unpark当一个线程被解除挂起时它会从挂起状态恢复到可运行状态可以继续执行。解除挂起的操作通常由其他线程触发例如当某个条件满足时唤醒等待的线程。
5.2、和waitnotify的区别 park和unpark是Thread类的方法用于线程的挂起和解除挂起而wait和notify是Object类的方法用于对象之间的线程协作。 park方法不需要持有对象锁可以直接使用而wait方法需要在同步块或同步方法中调用会释放对象锁。 unpark可以指定唤醒具体的线程而notify只是唤醒等待队列中的一个线程notifyAll则唤醒所有等待的线程。unpark唤醒线程更加精确并且可以先unpark再park此时线程不会陷入阻塞。
先unpark再park
Slf4j(topic c.Demo1)
public class Demo1 {public static void main(String[] args) throws InterruptedException {//park案例//wait,notify和notifyAll必须配合Object Monitor一起使用park unpark不用//park unpark可以精准到线程单位进行唤醒//park unpark可以先unparkThread t1 new Thread(() - {log.info(start);try {
// Thread.sleep(1000);//可以先unpark再parkThread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}log.info(park);LockSupport.park();log.info(resume);}, t1);t1.start();Thread.sleep(1000);log.debug(unpark);LockSupport.unpark(t1);}
}可以看到此时t1线程没有阻塞而是继续打印出了resume。 5.3、原理
park
当线程调用park方法时它会进入一个挂起状态暂停执行。此时线程的状态会被设置为WAITING或者TIMED_WAITING。park方法会将线程放入一个等待队列中并暂时释放掉线程所持有的所有锁。等待队列中的线程会等待被其他线程调用unpark方法来唤醒。
unpark
当线程调用unpark方法时它会尝试唤醒指定的线程使其从等待队列中移出并转换为可运行状态。如果目标线程还没有进入挂起状态那么调用unpark方法后目标线程在下一次调用park时将不会被挂起。
为什么先unpark再park线程不会阻塞的原因在于线程的状态管理和unpark的作用 当线程调用unpark方法时即使目标线程还未进入挂起状态也会记录一个许可证表示目标线程在未来调用park时可以立即返回而不会被挂起。 如果线程先调用了unpark然后再调用park那么park方法会立即返回不会阻塞因为已经有一个许可证允许它不被挂起。 6、重新理解线程状态转换 假设有一个线程t
new-runnable当调用t线程的.start()方法时状态由new转变成runnable。runnable-blocked当t线程与其他线程争抢synchronized锁失败时进入blocked状态。当其他线程执行完同步代码块中的代码后会释放锁线程间重新竞争锁未竞争到锁的线程同样会进入blocked状态。runnable-waiting情况一t线程获取到synchronized锁后调用对象的wait方法。如果再次调用对象的notify/notifyAll方法竞争锁成功会进入runnable状态。竞争锁失败会进入blocked状态。情况二当前线程调用t线程的join方法时当前线程进入waiting状态。当前线程在t线程对象的监视器上等待。t线程运行结束或者调用当前线程的interrput方法时当前线程重新回到runnable状态。情况三当前线程调用park方法进入waiting状态。当前线程调用unpark(目标线程)或调用了interrupt方法会让目标线程重新回到runnable状态。runnable-timed_waiting与runnable-waiting相似此时调用的是有时间参数的wait,join,park方法。runnable-terminated所有方法运行结束转变为terminated状态。 7、线程活跃性
7.1、多把锁
在多个操作不会互相影响一个操作不必等待另一个操作的结果时降低锁的粒度可以提高并发度。
public class Demo4 {public static void main(String[] args) {BigRoom bigRoom new BigRoom();new Thread(bigRoom::study,t1).start();new Thread(bigRoom::sleep,t2).start();}
}Slf4j(topic c.BigRoom)
class BigRoom{final Object study new Object();final Object sleep new Object();public void study(){synchronized (study){try {log.info(学习一小时);Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}public void sleep(){synchronized (sleep){try {log.info(休息两小时);Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}
7.2、死锁
7.2.1、基本概念 在多线程或多进程环境中两个或多个线程或进程互相等待对方释放资源而无法继续执行的情况。这种情况下所有涉及的线程或进程都被阻塞无法继续向前执行导致整个系统陷入僵局。 死锁发生的原因通常包括以下四个必要条件
互斥条件资源只能被一个线程或进程独占如果另一个线程或进程想要使用该资源就必须等待。持有和等待条件线程或进程持有至少一个资源并且在等待获取另一个资源时保持对已有资源的持有。非抢占条件资源不能被抢占即已经被一个线程或进程持有时其他线程或进程无法直接抢占只能等待。循环等待条件存在一个循环等待序列即线程 A 等待线程 B 持有的资源线程 B 等待线程 C 持有的资源而线程 C 又在等待线程 A 持有的资源形成一个闭环等待。
Slf4j(topic c.Demo1)
public class Demo1 {public static void main(String[] args) {test1();}public static void test1(){Object A new Object();Object B new Object();new Thread(()-{synchronized (A){log.info(Thread.currentThread().getName()获取到了A锁);try {Thread.sleep(1000);synchronized (B){log.info(Thread.currentThread().getName()获取到了B锁);}} catch (InterruptedException e) {throw new RuntimeException(e);}}},线程一).start();new Thread(()-{synchronized (B){log.info(Thread.currentThread().getName()获取到了B锁);try {Thread.sleep(500);synchronized (A){log.info(Thread.currentThread().getName()获取到了A锁);}} catch (InterruptedException e) {throw new RuntimeException(e);}}},线程二).start();}
}
在这个案例中
线程一尝试获取A锁获取到锁后睡眠1s然后尝试获取B锁。
线程二尝试获取B锁在获取到锁后睡眠0.5s然后尝试获取A锁。
由于线程一获取到A锁后没有释放锁所以B线程获取到B锁后无法获取到A锁由于线程二已经获取到了B锁所以A也无法获取到B锁反之也相同。
7.2.2、定位死锁
终端输入jconsole 7.2.3、哲学家就餐死锁问题
假设有五位哲学家围坐在圆桌旁。
他们只做两件事思考和吃饭思考一会吃口饭吃完饭后接着思考。吃饭时要用两根筷子吃桌上共有 5 根筷子每位哲学家左右手边各有一根筷子。如果筷子被身边的人拿着自己就得等待。
筷子类
/*** 筷子类*/
class Chopsticks extends ReentrantLock {private String name;public Chopsticks() {}public Chopsticks(String name) {this.name name;}/*** 获取* return name*/public String getName() {return name;}/*** 设置* param name*/public void setName(String name) {this.name name;}public String toString() {return Chopsticks{name name };}
}
哲学家类
Slf4j(topic c.Philosopher)
class Philosopher extends Thread{//左手筷子private Chopsticks left;//右手筷子private Chopsticks right;public Philosopher() {}public Philosopher(Chopsticks left, Chopsticks right,String name) {super(name);this.left left;this.right right;}public void eat() {log.info(getName()思考);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}Overridepublic void run() {while (true) {// 获得左手筷子synchronized (left) {// 获得右手筷子synchronized (right) {// 吃饭eat();}// 放下右手筷子}// 放下左手筷子}}/*** 获取* return left*/public Chopsticks getLeft() {return left;}/*** 设置* param left*/public void setLeft(Chopsticks left) {this.left left;}/*** 获取* return right*/public Chopsticks getRight() {return right;}/*** 设置* param right*/public void setRight(Chopsticks right) {this.right right;}public String toString() {return Philosopher{left left , right right , log log };}
}主类测试
/*** 哲学家就餐问题模拟死锁*/
Slf4j(topic c.Dining)
public class Dining {public static void main(String[] args) {Chopsticks t1 new Chopsticks(t1);Chopsticks t2 new Chopsticks(t2);Chopsticks t3 new Chopsticks(t3);Chopsticks t4 new Chopsticks(t4);Chopsticks t5 new Chopsticks(t5);new Philosopher(t1,t2,苏格拉底).start();new Philosopher(t2,t3,柏拉图).start();new Philosopher(t3,t4,亚里士多德).start();new Philosopher(t4,t5,赫拉克利特).start();new Philosopher(t5,t1,阿基米德).start();}
}
运行一段时间就会发生死锁问题。后续我们会通过ReentranLock进行解决 7.3、活锁
7.3.1、基本概念 活锁Livelock是另一种并发编程中的问题类似于死锁但有所不同。在活锁情况下线程或进程不是被阻塞在等待对方释放资源的状态而是在尝试解决冲突时一直互相让步导致无法继续向前执行。 活锁通常发生在需要互相等待对方执行某个操作的情况下例如两个线程都在等待对方放弃某个资源或者执行某个操作但双方都不愿意先让步。这种情况下线程不会被阻塞它们会不断尝试解决冲突但最终导致系统无法进展。
7.3.2、与死锁的区别
活锁线程或进程不断尝试解决冲突但始终没有进展系统一直处于忙碌但无法完成任务的状态。死锁线程或进程因为互相等待对方释放资源而被阻塞导致系统陷入僵局无法继续执行。
Slf4j(topic c.Demo1)
public class Demo1 {static volatile int counter 10;public static void main(String[] args) {//模拟活锁new Thread(() - {while (counter 20) {try {Thread.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}counter;log.debug(counter {}, counter);}},线程一).start();new Thread(() - {while (counter 0) {try {Thread.sleep(20);} catch (InterruptedException e) {throw new RuntimeException(e);}counter--;log.debug(counter {}, counter);}},线程二).start();}
}此时两个线程对共享变量counter进行并发访问。两个线程互相改变对方的结束条件最后谁也无法结束。
7.4、锁饥饿 锁饥饿Lock Starvation是指在并发编程中某些线程由于竞争锁资源失败而长时间无法获取所需的锁导致它们无法继续执行或者执行效率低下的情况。 锁饥饿通常发生在以下情况下
优先级倾斜如果某些线程的优先级较低而且高优先级的线程频繁地获取锁资源并且长时间不释放低优先级线程可能由于竞争失败而长时间无法获取锁导致饥饿现象。饥饿阻塞当多个线程竞争同一个锁资源时如果某个线程总是被其他线程抢先获取锁那么它可能会长时间无法获取锁导致饥饿现象。此前提到的非公平锁synchronized就可能导致这样的问题 解决锁饥饿的方法通常包括
公平性策略使用公平锁来确保锁资源的公平分配按照线程请求锁的顺序分配锁资源避免某些线程长时间无法获取锁。优先级调整合理设置线程的优先级避免高优先级线程长时间独占锁资源导致低优先级线程无法获取锁。超时等待设置获取锁资源的超时时间在超时后放弃获取锁并进行适当的处理避免线程长时间阻塞。 8、ReentrantLock 相比较于synchronized具有以下的特点 可重入性ReentrantLock是可重入的也就是说同一个线程可以多次获取同一个对象而不会被阻塞。这意味着线程可以在持有锁的情况下多次进入同步代码块而不会因为自己已经持有锁而阻塞。 公平性ReentrantLock支持公平性和非公平性两种方式。在公平模式下锁会按照线程请求锁的顺序进行分配而非公平模式下则可能会将锁分配给一个刚刚释放锁的线程以提高整体的吞吐量。 可中断性ReentrantLock支持可中断的锁获取方式。线程可以在等待获取锁的过程中被中断并且可以通过响应中断来处理中断请求这在处理死锁等情况下非常有用。 锁的条件变量ReentrantLock提供了Condition对象可以通过该对象实现线程间的等待/通知机制。这使得ReentrantLock可以更加灵活地控制线程的等待和唤醒。 手动加锁和解锁与synchronized关键字相比ReentrantLock需要手动显式地加锁和解锁。可以更加精细地控制锁的范围和持有时间从而避免死锁等问题。
8.1、可重入
Slf4j(topic c.Demo1)
public class Demo1 {static ReentrantLock reentrantLock new ReentrantLock();public static void main(String[] args) {//演示reentrantLock的可重入reentrantLock.lock();try {log.info(开始执行主方法);method1();} finally {reentrantLock.unlock();}}public static void method1(){reentrantLock.lock();try {log.info(开始执行m1方法);method2();} finally {reentrantLock.unlock();}}public static void method2(){reentrantLock.lock();try {log.info(开始执行m2方法);} finally {reentrantLock.unlock();}}
}
主方法获取到了锁并且要执行method1方法 method1方法获取到了同一把锁 8.2、可打断
Slf4j(topic c.Demo2)
public class Demo2 {public static void main(String[] args) throws InterruptedException {ReentrantLock reentrantLock new ReentrantLock();Thread t1 new Thread(() - {try {//如果没有竞争那么这个方法可以获取到lock锁//如果有竞争就进入阻塞队列可以被其他线程通过interrput打断log.info(开始获取锁);reentrantLock.lockInterruptibly();} catch (InterruptedException e) {e.printStackTrace();log.info(被打断);return;}try {log.info(获取到锁执行其他操作...);} finally {reentrantLock.unlock();}}, t1);log.info(开始获取锁);reentrantLock.lock();t1.start();Thread.sleep(1000);log.info(执行打断操作);t1.interrupt();}
}
通过reentrantLock.lockInterruptibly();设置锁为可打断。 8.3、有时限等待
Slf4j(topic c.Demo3)
public class Demo3 {public static void main(String[] args) throws InterruptedException {ReentrantLock reentrantLock new ReentrantLock();Thread t1 new Thread(() - {//有时限的等待try {//会等待2s如果2s没有获得锁就执行下面的代码if (!reentrantLock.tryLock(2, TimeUnit.SECONDS)){log.info(获取不到锁);return;}} catch (InterruptedException e) {e.printStackTrace();log.info(获取锁被打断);return;}try {log.info(获取到锁执行其他操作);} finally {reentrantLock.unlock();}},t1);reentrantLock.lock();log.info(Thread.currentThread().getName()获取到了锁);t1.start();//主线程获取到锁1s之后释放锁Thread.sleep(1000);log.info(Thread.currentThread().getName()释放了锁);reentrantLock.unlock();}
} 8.4、解决哲学家就餐死锁问题
改写Philosopher中的run方法。
此时假设某位哲学家获取到了左手的筷子要去尝试获取右手筷子结果失败了会将左手筷子也一起放下相当于破坏了死锁中的持有和等待条件。 Overridepublic void run() {while (true){
// synchronized (left){
// synchronized (right){
// log.info(getName() eat);
// }
// }if (left.tryLock()){try {//获取右手筷子如果失败了就放下左手筷子if (right.tryLock()){try {eat();}finally {right.unlock();}}}finally {left.unlock();}}}}
tryLock和Lock的区别 tryLock是非阻塞的尝试获取锁的方法。如果当前锁没有被其他线程持有tryLock会立即获取锁并返回true表示获取成功如果锁被其他线程持有会立即返回false表示获取失败。常用于实现一种非阻塞的获取锁机制可以用来避免线程因为等待锁而被阻塞从而提高程序的响应性。还支持超时参数可以指定获取锁的最长等待时间在超时后如果还未获取到锁则返回false。 lock是阻塞的获取锁的方法。如果当前锁已经被其他线程持有会使当前线程进入等待状态直到获取到锁为止。在获取到锁之前会一直阻塞当前线程。 8.5、条件变量
在synchronized中也有条件变量图中的waitSet 可以把它理解成一个房间。而 ReentrantLock的条件变量支持创建不同的房间。
使用要点
await 前需要获得锁 。await 执行后会释放锁进入 conditionObject 等待 。await 的线程被唤醒或打断、或超时取重新竞争 lock 锁 。竞争 lock 锁成功后从 await 后继续执行。
Slf4j(topic c.Demo5)
public class Demo5 {static ReentrantLock lock new ReentrantLock();static Boolean hasCigarette false;static Boolean hasTakeOut false;//创建单独的休息室static Condition cigaretteSet lock.newCondition();static Condition takeOutSet lock.newCondition();public static void main(String[] args) throws InterruptedException {new Thread(() - {lock.lock();//上锁try {log.debug(是否有烟:{}, hasCigarette);if (!hasCigarette) {log.debug(等待香烟);try {cigaretteSet.await();//进入休息室等烟} catch (InterruptedException e) {throw new RuntimeException(e);}}log.debug(开始干活);} finally {lock.unlock();}}, 壹号).start();new Thread(() - {lock.lock();try {log.info(是否有外卖:{}, hasTakeOut);if (!hasTakeOut) {log.debug(等待外卖);takeOutSet.await();}log.info(开始干活);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}, 贰号).start();Thread.sleep(1000);new Thread(() - {lock.lock();try {hasTakeOut true;log.info(送外卖:{}, hasTakeOut);takeOutSet.signal();} finally {lock.unlock();}}, 外卖员).start();new Thread(() - {lock.lock();try {hasCigarette true;log.info(送烟:{},hasCigarette);cigaretteSet.signal();} finally {lock.unlock();}}, 送烟).start();}
}
在上面的案例中首先创建了两个休息室分别用于等烟和等外卖。如果没有烟或者外卖则陷入等待。送烟或送外卖线程在执行完送烟/送外卖操作后会唤醒相应休息室的线程继续执行。 9、固定顺序执行
现在有两个线程t1和t2要求t2完成后再执行t1
9.1、解法一waitnotify
思路定义一个t2是否执行完成的标记t1在执行前先判断如果为false证明t2没有执行就陷入阻塞等待t2执行完成后唤醒t1。
Slf4j(topic c.Demo6)
public class Demo6 {static final Object lock new Object();static Boolean t2Runned false;public static void main(String[] args) {//要求t2运行完成后再运行t1Thread t1 new Thread(() - {synchronized (lock) {while (!t2Runned) {try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}log.info(t1);}}, t1);Thread t2 new Thread(() - {synchronized (lock) {log.info(t2);t2Runned true;lock.notify();}},t2);t1.start();t2.start();}
}9.2、解法二parkunpark
同样是设置标记位的思路但是区别在于使用parkunpark无需关联对象的锁。
Slf4j(topic c.Demo6)
public class Demo7 {static Boolean t2HasRun false;public static void main(String[] args) {Thread t1 new Thread(() - {if (!t2HasRun){LockSupport.park();}log.info(t1);}, t1);Thread t2 new Thread(() - {log.info(t2);t2HasRun true;LockSupport.unpark(t1);},t2);t1.start();t2.start();}
}10、交替执行
要求交替打印abc五次。
10.1、解法一waitnotify
思路使用标记位记录执行线程以及本轮标记和下一轮标记
线程 本轮标记 下轮标记
A 1 2
B 2 3
C 3 1
创建WaitNotify对象时设置循环五次初始标记为1。
假设此时线程二争抢到了锁的执行权发现本轮标记2和初始标记1不相同就会陷入阻塞让出锁的执行权。
然后线程一争抢到了锁的执行权发现本轮标记1和初始标记1一致就打印a并且将初始标记设置成为下一轮的标记2。执行完成后解锁。
最后线程二又争抢到了锁的执行权发现本轮标记2和初始标记2一致就打印b并且将初始标记设置成为下一轮的标记3。执行完成后解锁。
Slf4j(topic c.Demo8)
public class Demo8 {public static void main(String[] args) {//交替打印abc五次WaitNotify waitNotify new WaitNotify(5, 1);new Thread(()-{waitNotify.print(a,1,2);},线程一).start();new Thread(()-{waitNotify.print(b,2,3);},线程二).start();new Thread(()-{waitNotify.print(c,3,1);},线程三).start();}
}class WaitNotify{private int loopNumber;private int flag;/*** 打印* param msg 打印信息* param waitFlag 本轮标记* param nextFlag 下一轮标记*/public void print(String msg,int waitFlag,int nextFlag){for (int i 0; i loopNumber; i) {synchronized (this) {while (flag ! waitFlag) {try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.print(msg);flag nextFlag;this.notifyAll();}}}public WaitNotify() {}public WaitNotify(int loopNumber, int flag) {this.loopNumber loopNumber;this.flag flag;}/*** 获取* return loopNumber*/public int getLoopNumber() {return loopNumber;}/*** 设置* param loopNumber*/public void setLoopNumber(int loopNumber) {this.loopNumber loopNumber;}/*** 获取* return flag*/public int getFlag() {return flag;}/*** 设置* param flag*/public void setFlag(int flag) {this.flag flag;}public String toString() {return WaitNotify{loopNumber loopNumber , flag flag };}
}
10.2、解法二awaitsignal
此时将标记替换成为了本间休息室和下一间休息室的对象。
主线程会先唤醒线程一线程一被唤醒后打印a然后唤醒下一个处于休息室的线程b...
Slf4j(topic c.Demo9)
public class Demo9 {public static void main(String[] args) throws InterruptedException {AwaitSignal awaitSignal new AwaitSignal(5);Condition aSet awaitSignal.newCondition();Condition bSet awaitSignal.newCondition();Condition cSet awaitSignal.newCondition();new Thread(() - {awaitSignal.print(a, aSet, bSet);}, 线程一).start();new Thread(() - {awaitSignal.print(b, bSet, cSet);}, 线程二).start();new Thread(() - {awaitSignal.print(c, cSet, aSet);}, 线程三).start();Thread.sleep(1000);awaitSignal.lock();try {//唤醒a休息室aSet.signal();}finally {//解锁awaitSignal.unlock();}}
}class AwaitSignal extends ReentrantLock {//循环次数private int loopNumber;public AwaitSignal() {}public AwaitSignal(int loopNumber) {this.loopNumber loopNumber;}/*** 打印方法* param msg 信息* param current 现在的休息室* param next 下一个休息室*/public void print(String msg, Condition current, Condition next) {for (int i 0; i 5; i) {//获取锁this.lock();try {//自己进入休息室current.await();//被唤醒后执行System.out.print(msg);//唤醒下一个next.signal();} catch (InterruptedException e) {throw new RuntimeException(e);} finally {//释放锁this.unlock();}}}/*** 获取* return loopNumber*/public int getLoopNumber() {return loopNumber;}/*** 设置* param loopNumber*/public void setLoopNumber(int loopNumber) {this.loopNumber loopNumber;}public String toString() {return AwaitSignal{loopNumber loopNumber };}
}
10.3、解法三parkunpark
Slf4j(topic c.Demo10)
public class Demo10 {static Thread t1;static Thread t2;static Thread t3;public static void main(String[] args) throws InterruptedException {ParkUnpark parkUnpark new ParkUnpark(5);t1 new Thread(() - {parkUnpark.print(a,t2);},线程一);t2 new Thread(() - {parkUnpark.print(b,t3);},线程二);t3 new Thread(() - {parkUnpark.print(c,t1);},线程三);t1.start();t2.start();t3.start();Thread.sleep(1000);LockSupport.unpark(t1);}
}class ParkUnpark{//循环次数private int loopNumber;public ParkUnpark() {}public ParkUnpark(int loopNumber) {this.loopNumber loopNumber;}public void print(String msg,Thread unpark){for (int i 0; i 5; i) {LockSupport.park();System.out.print(msg);LockSupport.unpark(unpark);}}/*** 获取* return loopNumber*/public int getLoopNumber() {return loopNumber;}/*** 设置* param loopNumber*/public void setLoopNumber(int loopNumber) {this.loopNumber loopNumber;}public String toString() {return ParkUnpark{loopNumber loopNumber };}
}