Java并发线程 共享模型之管程 5

2024-09-05 00:28

本文主要是介绍Java并发线程 共享模型之管程 5,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. 生产者消费者

package cn.itcast.testcopy;import cn.itcast.n2copy.util.Sleeper;
import lombok.extern.slf4j.Slf4j;import java.util.LinkedList;/*** ClassName: Test21* Package: cn.itcast.testcopy* Description: 生产者消费者** @Author: 1043* @Create: 2024/9/4 - 11:11* @Version: v1.0*/
public class Test21 {public static void main(String[] args) {MessgeQueue queue=new MessgeQueue(2);for (int i = 0; i < 3; i++) {int id=i;new Thread(()->{queue.put(new Message(id,"值"+id));},"生产者"+i).start();}new Thread(()->{while (true){Sleeper.sleep(1);Message take = queue.take();}},"消费者").start();}
}// 消息队列类,rabbitmq是进程间通信,这个类比较简单是线程间通信
@Slf4j(topic = "c.MessgeQueue")
class MessgeQueue {// 消息的队列集合private LinkedList<Message> list = new LinkedList<>();// 队列容量private int capcity;public MessgeQueue(int capcity) {this.capcity = capcity;}public Message take() {// 检查队列是否为空synchronized (list) {while (list.isEmpty()) {try {log.debug("队列为空, 消费者线程等待");list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 从队列头部获取消息并返回Message message = list.removeFirst();log.debug("已消费消息 {}", message);list.notifyAll();return message;}}public void put(Message message) {synchronized (list) {// 检查对象是否已满while (list.size() == capcity) {try {log.debug("队列已满, 生产者线程等待");list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 将消息加入队列尾部list.addLast(message);log.debug("已生产消息 {}", message);list.notifyAll();}}
}final class Message {private int id;private Object message;public Message(int id, Object message) {this.id = id;this.message = message;}public int getId() {return id;}public Object getMessage() {return message;}@Overridepublic String toString() {return "Message{" +"id=" + id +", message=" + message +'}';}
}

 2. park和unpark

先看一段代码

@Slf4j(topic = "c.Testpark")
public class Testpark {public static void main(String[] args) {Thread t1 = new Thread(() -> {log.debug("start...");sleep(2);log.debug("park...");LockSupport.park();log.debug("resume...");}, "t1");t1.start();sleep(1);log.debug("unpark...");LockSupport.unpark(t1);}
}

运作结果如图

 与 Object 的 wait & notify 相比

  • waitnotify 和 notifyAll 必须配合 Object Monitor 一起使用,而 parkunpark 不必。
  • park 和 unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么【精确】。
  • park 和 unpark 可以先 unpark,而 wait 和 notify 不能先 notify

 原理

        每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond 和 _mutex 打个比喻线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
        调用 park 就是要看需不需要停下来歇息
                如果备用干粮耗尽,那么钻进帐篷歇息
                如果备用干粮充足,那么不需停留,继续前进
        调用 unpark,就好比令干粮充足
                如果这时线程还在帐篷,就唤醒让他继续前进
                如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留,继续前进,因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮

3. 多把锁

一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个对象锁)例如

class BigRoom2 {public void sleep() {synchronized (this) {log.debug("sleeping 2 小时");Sleeper.sleep(2);}}public void study() {synchronized (this) {log.debug("study 1 小时");Sleeper.sleep(1);}}
}
BigRoom bigRoom = new BigRoom();new Thread(() -> {bigRoom.study();}, "小南").start();new Thread(() -> {bigRoom.sleep();}, "小女").start();

 此时效率很低,可改进降低锁的粒度

public class TestMultiLock {public static void main(String[] args) {BigRoom bigRoom = new BigRoom();new Thread(() -> {bigRoom.study();}, "小南").start();new Thread(() -> {bigRoom.sleep();}, "小女").start();}
}@Slf4j(topic = "c.BigRoom")
class BigRoom {private final Object studyRoom = new Object();private final Object bedRoom = new Object();public void sleep() {synchronized (bedRoom) {log.debug("sleeping 2 小时");Sleeper.sleep(2);}}public void study() {synchronized (studyRoom) {log.debug("study 1 小时");Sleeper.sleep(1);}}}

此时两个小房间互不影响。不过这种情况容易造成死锁。

死锁

        有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁t1 线程 获得 A对象 锁,接下来想获取 B对象的锁 t2 线程 获得 B对象 锁,接下来想获取 A对象的锁 例:

@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {public static void main(String[] args) {test1();}private static void test1() {Object A = new Object();Object B = new Object();Thread t1 = new Thread(() -> {synchronized (A) {log.debug("lock A");sleep(1);synchronized (B) {log.debug("lock B");log.debug("操作...");}}}, "t1");Thread t2 = new Thread(() -> {synchronized (B) {log.debug("lock B");sleep(0.5);synchronized (A) {log.debug("lock A");log.debug("操作...");}}}, "t2");t1.start();t2.start();}
}

检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:

活锁

        活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如

@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {static volatile int count = 10;static final Object lock = new Object();public static void main(String[] args) {new Thread(() -> {// 期望减到 0 退出循环while (count > 0) {sleep(0.2);count--;log.debug("count: {}", count);}}, "t1").start();new Thread(() -> {// 期望超过 20 退出循环while (count < 20) {sleep(0.2);count++;log.debug("count: {}", count);}}, "t2").start();}
}

一个希望向下减一个希望向上加,互相正直在一个范围内。也是被锁住了。

饥饿

        很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题。如哲学家就餐问题将最后一个哲学家的左右手顺序颠倒一个就不会死锁了,但会由此引发饥饿问题。

4.ReentrantLock

相对于 synchronized 它具备如下特点
        可中断
        可以设置超时时间
        可以设置为公平锁
        支持多个条件变量
        与 synchronized 一样,都支持可重入,不过synchronized是在关键字级别保护临界区而ReentrantLock在对象级别保护临界区。
        基本语法

// 获取锁
reentrantLock.lock();
try {// 临界区
} finally {// 释放锁reentrantLock.unlock();
}

 第一行加锁的语句放try里跟上面效果完全等价。

特性1 可重入

示例

package cn.itcast.testcopy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;/*** ClassName: Test22* Package: cn.itcast.testcopy* Description:** @Author: 1043* @Create: 2024/9/4 - 16:09* @Version: v1.0*/
@Slf4j(topic = "c.Test22")
public class Test22 {private static ReentrantLock lock=new ReentrantLock();public static void main(String[] args) {lock.lock();try {log.debug("enter main");m1();}finally {lock.unlock();}}public static void m1() {lock.lock();try {log.debug("enter m1");m2();}finally {lock.unlock();}}public static void m2() {lock.lock();try {log.debug("enter m2");}finally {lock.unlock();}}
}

 

 特性2 可打断

package cn.itcast.testcopy;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;/*** ClassName: Test22kedaduan* Package: cn.itcast.testcopy* Description:** @Author: 1043* @Create: 2024/9/4 - 16:13* @Version: v1.0*/
@Slf4j(topic = "c.Test22kedaduan")
public class Test22kedaduan {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {try {// 如果没有竞争那么此方法就会获取 Lock 对象锁// 如果有竞争就进入阻塞队列,可以被其它线程用 interrupt 方法打断log.debug("尝试获取锁");lock.lockInterruptibly();} catch (InterruptedException e) {e.printStackTrace();log.debug("没有获得锁,返回");return;}try {log.debug("获取到锁");} finally {lock.unlock();}},"t1");t1.start();}
}

        lock.lock();//主线程先加锁t1.start();Sleeper.sleep(1);log.debug("打断t1");t1.interrupt();

t1可以被打断,不会死等下去, 这就为防止死锁发生提供了方式。

特性3 锁超时

@Slf4j(topic = "c.Test22")
public class Test22 {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) {Thread t1 = new Thread(() -> {log.debug("尝试获得锁");try {if (!lock.tryLock(2, TimeUnit.SECONDS)) {log.debug("获取不到锁");return;}} catch (InterruptedException e) {e.printStackTrace();log.debug("获取不到锁");return;}try {log.debug("获得到锁");} finally {lock.unlock();}}, "t1");lock.lock();log.debug("获得到锁");t1.start();sleep(3);log.debug("释放了锁");lock.unlock();}
}

主线程3s后才释放锁,t1的tryLock只等2s,2s后还获取不到就放弃等待。tryLock若是没参数就只尝试一次获取不到立即放弃。

应用 解决哲学家就餐问题


public class TestDeadLock {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(topic = "c.Philosopher")
class Philosopher extends Thread {Chopstick left;Chopstick right;public Philosopher(String name, Chopstick left, Chopstick right) {super(name);this.left = left;this.right = right;}@Overridepublic void run() {while (true) {// 尝试获得左手筷子if (left.tryLock()) {try {// 尝试获得右手筷子if (right.tryLock()) {try {eat();} finally {right.unlock();}}} finally {left.unlock();}}}}private void eat() {log.debug("eating...");Sleeper.sleep(1);}
}class Chopstick extends ReentrantLock {String name;public Chopstick(String name) {this.name = name;}@Overridepublic String toString() {return "筷子{" + name + '}';}
}

         核心在于获取右手筷子失败后放开左手的筷子,这样别人就有机会获取筷子,就不会发生死锁与饥饿了。

特性4. 公平锁

        ReentrantLock 默认是不公平的,公平锁一般没有必要,会降低并发度。

特性5 条件变量

        synchronized 中也有条件变量,就是我们前面分析原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待。
        ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比 synchronized 是那些不满足条件的线程都在一间休息室等消息。而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
        使用要点:
        await 前需要获得锁
        await 执行后,会释放锁,进入 conditionObject 等待
        await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
        竞争 lock 锁成功后,从 await 后继续执行

这篇关于Java并发线程 共享模型之管程 5的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1137462

相关文章

springboot security快速使用示例详解

《springbootsecurity快速使用示例详解》:本文主要介绍springbootsecurity快速使用示例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录创www.chinasem.cn建spring boot项目生成脚手架配置依赖接口示例代码项目结构启用s

java之Objects.nonNull用法代码解读

《java之Objects.nonNull用法代码解读》:本文主要介绍java之Objects.nonNull用法代码,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐... 目录Java之Objects.nonwww.chinasem.cnNull用法代码Objects.nonN

springboot security之前后端分离配置方式

《springbootsecurity之前后端分离配置方式》:本文主要介绍springbootsecurity之前后端分离配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的... 目录前言自定义配置认证失败自定义处理登录相关接口匿名访问前置文章总结前言spring boot secu

一文详解SpringBoot响应压缩功能的配置与优化

《一文详解SpringBoot响应压缩功能的配置与优化》SpringBoot的响应压缩功能基于智能协商机制,需同时满足很多条件,本文主要为大家详细介绍了SpringBoot响应压缩功能的配置与优化,需... 目录一、核心工作机制1.1 自动协商触发条件1.2 压缩处理流程二、配置方案详解2.1 基础YAML

java中使用POI生成Excel并导出过程

《java中使用POI生成Excel并导出过程》:本文主要介绍java中使用POI生成Excel并导出过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录需求说明及实现方式需求完成通用代码版本1版本2结果展示type参数为atype参数为b总结注:本文章中代码均为

springboot简单集成Security配置的教程

《springboot简单集成Security配置的教程》:本文主要介绍springboot简单集成Security配置的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录集成Security安全框架引入依赖编写配置类WebSecurityConfig(自定义资源权限规则

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

java中反射(Reflection)机制举例详解

《java中反射(Reflection)机制举例详解》Java中的反射机制是指Java程序在运行期间可以获取到一个对象的全部信息,:本文主要介绍java中反射(Reflection)机制的相关资料... 目录一、什么是反射?二、反射的用途三、获取Class对象四、Class类型的对象使用场景1五、Class

SpringBoot中封装Cors自动配置方式

《SpringBoot中封装Cors自动配置方式》:本文主要介绍SpringBoot中封装Cors自动配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot封装Cors自动配置背景实现步骤1. 创建 GlobalCorsProperties

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3