JUC并发编程(上)
一、JUC学习准备
核心知识点:进程、线程、并发(共享模型、非共享模型)、并行
预备知识:
基于JDK8,对函数式编程、lambda有一定了解
采用了slf4j打印日志
采用了lombok简化java bean编写
二、进程与线程
进程和线程概念
两者对比
并行和并发
操作系统cpu任务调度器
同一时间能够应对多件事件的能力称为并发
应用
三、java线程
创建线程
方法一:使用Thread
创建线程对象
给线程命名t1
启动线程
@Slf4j(topic = "c.Test1")
public class Test1 {public static void main(String[] args) {Thread t = new Thread(){@Overridepublic void run() {log.debug("running");}};t.setName("t1");t.start();log.debug("running");}
}
方法二:使用Runnable
把线程和任务分开
@Slf4j(topic = "c.Test2")
public class Test2 {public static void main(String[] args) {Runnable r = new Runnable() {@Overridepublic void run() {log.debug("running");}};Thread t = new Thread(r,"t2");t.start();}
}
使用lambda简化
idea快捷键alt+enter
@Slf4j(topic = "c.Test2")
public class Test2 {public static void main(String[] args) {Runnable r = () -> log.debug("running");Thread t = new Thread(r,"t2");t.start();}
}
Thread与Runnable的关系
方法三:使用FutureTask
间接使用了Runnable
能够接收Callable
可以把任务的结果传给其他线程
@Slf4j(topic = "c.Test3")
public class Test3 {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {log.debug("running");Thread.sleep(1000);return 0;}});Thread t1 = new Thread(task, "t1");t1.start();log.debug("{}", task.get());}
}
线程运行
现象-两个线程交替运行
@Slf4j(topic = "c.TestMultiThread")
public class Test4 {public static void main(String[] args) throws ExecutionException, InterruptedException {new Thread(() -> {while (true) {log.debug("running");}}, "t1").start();new Thread(() -> {while (true) {log.debug("running");}}, "t2").start();}
}
查看进程线程
线程运行原理
原理
图解
多线程
多线程的栈桢之间互不干扰,相互独立
debug时采用Thread查看
线程上下文切换
线程API
start和run比较
start主要作用启动另一个线程调用run方法,如果直接用创建的线程对象调用run方法,占用的还是主线程。
创建线程后,调用start前处于new状态,未被cpu运行
创建线程后,调用start后处于runnable状态,可以被cpu调度运行
sleep与yield
sleep会让当前调用的线程进入睡眠
睡眠结束后要等待cpu时间片(任务调度器)分配才能得到执行
线程优先级
public class Test6 {public static void main(String[] args) {Runnable task1 = () -> {int count = 0;while (true) {System.out.println("t1---------->" + count++);}};Runnable task2 = () -> {int count = 0;while (true) {//Thread.yield();System.out.println(" t2---------->" + count++);}};Thread t1 = new Thread(task1,"t1");Thread t2 = new Thread(task2,"t2");t1.setPriority(Thread.MAX_PRIORITY);t2.setPriority(Thread.MIN_PRIORITY);t1.start();t2.start();}}
sleep应用
join方法
@Slf4j(topic = "c.Test7")
public class Test7 {static int r = 0;public static void main(String[] args) {test();}private static void test() {log.debug("main start...");Thread t1 = new Thread(() -> {log.debug("t1 start...");try {sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}log.debug("t1 end...");r = 10;}, "t1");t1.start();log.debug("结果:{}", r);log.debug("main end...");}
}
join方法作用:等待调用join方法的线程运行结束
在start后加join即可
t1.start();
t1.join();
应用同步
@Slf4j(topic = "c.Test8")
public class Test8 {static int r1 = 0;static int r2 = 0;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}r1 = 10;}, "t1");Thread t2 = new Thread(() -> {try {sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}r2 = 20;}, "t2");t1.start();t2.start();long start = System.currentTimeMillis();log.debug("join begin");t1.join();log.debug("t1 join end");t2.join();log.debug("t2 join end");long end = System.currentTimeMillis();log.debug("r1:{},r2:{},cost:{}", r1, r2, end - start);}
}
多线程start时已经开始运行,当join调用时,如果线程已经运行结束,则不会等待。
限时同步
超过join的等待时间,则不会等待线程的结果。
如果设置的join等待时间内,线程提前结束,也会提前结束等待。
interrupt方法
打断sleep,wait,join的阻塞线程
打断特殊的阻塞线程会清空打断状态,打断状态重置为false,正常为true
打断正常的运行线程
主线程调用interrupt方法只是通知打断t1线程,但不会真的打断,此时t1线程的打断标记为true,t1线程可以通过打断标记决定是否中断自己。
@Slf4j(topic = "c.Test9")
public class Test9 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (true) {boolean interrupted = Thread.currentThread().isInterrupted();if (interrupted) {log.debug("线程被中断");break;}}}, "t1");t1.start();sleep(1000);log.debug("打断t1线程");t1.interrupt();}
}
设计模式:两阶段终止模式
1.启动监控线程
2.定时循环执行监控记录
3.在循环过程中判断监控线程是否被打断
4.打断标记为true时执行打断操作
@Slf4j(topic = "c.Test10")
public class Test10 {public static void main(String[] args) throws InterruptedException {TwoPhaseTermination tpt = new TwoPhaseTermination();tpt.start();sleep(3500);tpt.stop();}
}
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {private Thread monitor;//启动监控线程public void start() {monitor = new Thread(() -> {while (true) {Thread current = Thread.currentThread();if(current.isInterrupted()) {log.debug("料理后事");break;}try {sleep(2000);//情况一:阻塞被打断log.debug("执行监控记录");//情况二:正常运行被打断} catch (InterruptedException e) {e.printStackTrace();monitor.interrupt();}}});monitor.start();}//停止监控线程public void stop() {monitor.interrupt();}
}
打断park
park被打断后,此时打断状态为true,如果继续执行park会失效,可以将状态设为false后恢复。
过时方法
守护线程
线程状态
五种状态
六种状态
四、共享模型之管程
悲观锁思想解决并发问题
共享问题
由线程上下文切换引起指令交错导致的多线程访问共享资源的线程安全问题
静态变量 i 存储在主内存中,多个线程都可以读取 i 值,计算是在线程中完成,完成后需写回主内存。如果计算完成还未写入主存时出现上下文切换则会导致线程安全问题。
临界区
竞态条件

synchronized
需要一个多线程共享的对象
语法
@Slf4j(topic = "c.Test11")
public class Test11 {static int count = 0;static final Object lock = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (lock) {count++;}}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {synchronized (lock) {count--;}}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("count:{}", count);}
}
理解
线程1获取锁对象后,如果临界区还未执行完,发生上下文切换到其他线程获取不到会被blocked,继续上下文切换,直到临界区代码执行结束释放锁,唤醒阻塞的线程,其他线程才能获取到锁。
synchronized放在for循环外面,会保证整个循环的原子性
如果多线程获取的不是同一个锁对象,不能保证线程安全;要给同一对象加锁保护共享资源
如果t1加锁,t2未加锁,上下文切换到t2时,此时不需要获取对象锁,则不会被阻塞,t2线程会运行
锁对象面向对象改进
@Slf4j(topic = "c.Test12")
public class Test12 {public static void main(String[] args) throws InterruptedException {Room room = new Room();Thread t1 = new Thread(() -> {for (int i = 0; i < 5000; i++) {room.add();}}, "t1");Thread t2 = new Thread(() -> {for (int i = 0; i < 5000; i++) {room.reduce();}}, "t2");t1.start();t2.start();t1.join();t2.join();log.debug("count:{}", room.getCount());}
}@Slf4j(topic = "c.room")
class Room {private static int count;public void add() {synchronized (this) {count++;}}public void reduce() {synchronized (this) {count--;}}public int getCount() {synchronized (this) {return count;}}
}
方法上的synchronized
成员方法:锁this对象
静态方法:锁类对象

线程安全分析
变量线程安全分析
局部变量的自增和成员变量自增不同。局部变量只用了一步
局部变量在栈中互相独立
子类继承父类并重写方法,可能会再加一个线程,此时有多个线程访问共享资源list,造成局部变量引用暴露给了其它线程,有线程安全问题
private修饰父类方法会限制子类不能覆盖
final修饰父类方法防止子类重写
常见线程安全类
多方法组合不能保证原子性
不可变线程安全性
通过如下源码可知,这些方法重新创建了一个新的字符串对象,并把新值赋给新字符串的value,没有改变原有的String对象
无状态(成员变量)的类一般是线程安全的
如果有成员变量,最好变成局部变量,否则要看多线程访问是否是同一个
如果是成员变量,看是否向外暴露引用。如下所示局部变量向外泄露导致并发不安全
Monitor概念
java对象头
对象头占8个字节,对象的类型通过Klass Word指针找到
Monitor工作原理
图文角度
字节码角度
synchronized进阶原理
轻量级锁
加锁、锁重入、解锁
锁膨胀
自旋优化
锁竞争发生阻塞会进行上下文切换比较耗时,通过自旋(循环重试获取锁)优化
自旋需要cpu,不适合单核。
偏向锁
加偏向锁前、中、后测试代码
测试hashCode()
调用hashCode会禁用偏向锁,因为对象头没有空间存储31位hashcode,需要将偏向状态变成正常状态,再将hashcode放进去。
在轻量级锁中,hashcode存储在线程栈桢的锁记录中
在重量级锁中,hashcode存储在monitor对象中
让两个线程错开访问锁对象,如果发生交替会产生锁竞争
批量重偏向
批量撤销
锁消除
java代码在编译时会对代码进行锁优化,如果发现代码中加的锁没有任何意义,JIT会把锁优化掉。
如图局部变量o不会逃离方法作用范围,表明对象不会被共享,代码真正执行时没有sychronized。
锁消除禁用:-XX:-EliminateLocks
wait/notify
当一个线程占着锁后,由于需要一些条件暂时无法工作,导致后续想获取锁的线程阻塞,此时用wait方法该线程进入waitset并释放锁,让其他线程运行。等待条件满足通知该线程离开waitset,进入竞争锁队列entrylist
工作原理
API
线程必须先获取到锁对象后才能调用wait方法,进入该锁对象的休息室
wait(1000)代表等待一秒后,退出等待,如果等待时间内被唤醒,则提前退出等待
wait和sleep区别
wait/notify正确用法
1.如果只有一个线程wait,等待另一个线程将条件满足后notify即可
2.如果有多个线程wait,还用notify可能会出现虚假唤醒,可以改用notifyAll唤醒全部;此时肯定唤醒了其他不必要的线程,可以使用循环条件判断 while 让他们再次进入wait等待下次唤醒。
同步模式之保护性暂停
定义
代码实现
1.使用join后线程不能执行其他操作,而保护性暂停可以在等待过程中执行其他操作
2.使用join获得的结果的变量是全局的,而保护性暂停的变量是局部的
@Slf4j(topic = "c.Test15")
public class Test15 {public static void main(String[] args) throws InterruptedException {GuardedObject guardedObject = new GuardedObject();Thread t1, t2;t1 = new Thread(() -> {log.debug("等待结果..");String result = (String) guardedObject.get();log.debug("结果是:{}", result);}, "t1");t2 = new Thread(() -> {log.debug("进行下载..");// 等待下载完成结果try {sleep(1000);String result = "下载完成";guardedObject.complete(result);} catch (InterruptedException e) {throw new RuntimeException(e);}}, "t2");t1.start();t2.start();}
}class GuardedObject {private Object response;public Object get() {synchronized (this) {while (response == null) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}return response;}}public void complete(Object response) {synchronized (this) {this.response = response;this.notifyAll();}}}
扩展--增加超时等待
1.记录等待时间和经历时间:超时时间 - 经历时间 = 等待时间
2.防止虚假唤醒对等待时间的影响
join原理
join源码使用的就是保护性暂停模式
扩展--解耦中间类
产生结果线程和消费结果线程一一对应
收信人通知信箱要收信,并准备接收;
信箱创建邮件并附上唯一的id,每创建一份就把邮件放到集合里;
快递员拿到邮件id和邮件内容开始送信,信件送达后通知收信人。
@Slf4j(topic = "c.Test16")
public class Test16 {public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 3; i++) {new People().start();}sleep(1000);for (Integer id : Mailboxes.getIds()) {new Postman(id, "mail" + id).start();}}
}
@Slf4j(topic = "c.People")
class People extends Thread {@Overridepublic void run() {//收信GauardedObject gauardObject = Mailboxes.createGauardedObject();log.debug("开始收信 id:{}", gauardObject.getId());Object mail = gauardObject.get(5000);log.debug("收到信 id:{} mail:{}", gauardObject.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() {GauardedObject gauardedObject = Mailboxes.getGauardedObject(id);log.debug("开始送信 id:{} mail:{}", id, mail);gauardedObject.complete(mail);}
}class Mailboxes {private static Map<Integer, GauardedObject> boxes = new Hashtable<>();private static int id = 1;private static synchronized int generateId() {return id++;}public static GauardedObject getGauardedObject(int id) {return boxes.remove(id);}public static GauardedObject createGauardedObject() {GauardedObject go = new GauardedObject(generateId());boxes.put(go.getId(), go);return go;}public static Set<Integer> getIds() {return boxes.keySet();}}class GauardedObject {private int id;public GauardedObject(int id) {this.id = id;}public int getId() {return id;}private Object response;public Object get(long timeout) {synchronized (this) {long passedtime = 0;long start = System.currentTimeMillis();while (response == null) {long waitTime = timeout - passedtime;if (waitTime < 0){break;}try {this.wait(waitTime);} catch (InterruptedException e) {e.printStackTrace();}passedtime = System.currentTimeMillis() - start;}return response;}}public void complete(Object response) {synchronized (this) {this.response = response;this.notifyAll();}}
}
异步模式之生产者/消费者
定义
存放在消息队列里的消息不会立刻被消费,所以归类为异步。
注:这个消息队列是线程间通信的,而RabbitMQ里消息队列是进程间通信的
消息队列里要有容量限制
填充队列时要检查是否容量已满
拿去消息时要检查队列消息是否为空
代码实现
@Slf4j(topic = "c.Test15")
public class Test17 {public static void main(String[] args) throws InterruptedException {MessageQueue queue = new MessageQueue(2);for (int i = 0; i < 3; i++) {int id = i;new Thread(() -> {queue.put(new Message(id, "msg" + id));log.debug("id:{},生产消息:{}", id, "msg" + id);}, "生产者" + i).start();}sleep(1000);new Thread(() -> {while (true) {Message message = queue.take();log.debug("id:{},消费消息:{}", message.getId(), message.getValue());}}).start();}
}@Slf4j(topic = "c.MessageQueue")
class MessageQueue {private int capacity;private LinkedList<Message> queue = new LinkedList<>();public MessageQueue(int capacity) {this.capacity = capacity;}public Message take() {synchronized (this) {while (queue.isEmpty()) {log.debug("队列为空,等待消息填充!");try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}this.notifyAll();return queue.removeFirst();}}public void put(Message message) {synchronized (this) {while (queue.size() == capacity) {log.debug("队列已满,等待消费!");try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}queue.addLast(message);this.notifyAll();}}
}final class Message {private int id;private Object value;public Message(int id, Object value) {this.id = id;this.value = value;}public int getId() {return id;}public Object getValue() {return value;}
}
park&unpark
与wait¬ify区别
原理
线程状态转换
单向箭头表示只能一种状态向另一种状态转换
双向箭头表示可以互相转换
多把锁
如图所示,房间可以睡觉和学习,此时如果有人要睡觉,会把屋子锁上,而要进屋学习的人就必须等待睡觉结束,导致并发度很低。显然两者不冲突,需要优化。
用多把锁优化(保证业务不关联)
将锁的粒度细分
好处,是可以增强并发度
坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
线程活跃性
死锁
死锁定位
方法一:jstack
利用 jps 定位线程id
使用 "jstack 线程id" 命令查看
方法二:jconsole
命令行输入 jconsole 打开
连接到所在线程
切换线程窗口,检测死锁
哲学家就餐
问题原因:syschronized获取不到锁会一直等待。
public class Test21 {public static void main(String[] args) throws InterruptedException {Chopstick c1 = new Chopstick("C1");Chopstick c2 = new Chopstick("C2");Chopstick c3 = new Chopstick("C3");Chopstick c4 = new Chopstick("C4");Chopstick c5 = new Chopstick("C5");new philosopher("p1", c1, c2).start();new philosopher("p2", c2, c3).start();new philosopher("p3", c3, c4).start();new philosopher("p4", c4, c5).start();new philosopher("p5", 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;}@Overridepublic void run() {while (true) {synchronized (left) {synchronized (right) {eat();}}}}private void eat() {log.debug("eating....");try {sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}class Chopstick{String name;public Chopstick(String name){this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
活锁
解决活锁方法:让两个线程指令交错执行,下图的活锁问题可以通过增加一个线程的睡眠时间解决
饥饿
死锁问题是两个线程要获取对方拥有的锁
顺序加锁是让两个线程都先获取A锁,必然有一个线程获取不到锁导致阻塞,另一个线程就能再次获取到B锁
造成问题:有的锁可能会一直得不到锁,导致无法得到执行。
ReentrantLock
相对于synchronized它具备如下特点
可中断(syschronized锁加上后不可被其他方法中断破坏)
可以设置超时时间(syschronized的阻塞线程会进入EntryList里一直等待,而RL可以设置等待时间,超过时间会放弃锁竞争)
可以设置为公平锁(防止饥饿锁问题)
支持多个条件变量(多个细分的休息室)
与synchronized一样,都支持可重入(同一线程可以对同一对象反复加锁)
可重入锁
@Slf4j(topic = "c.Test19")
public class Test19 {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {lock.lock();try {log.debug("main");m1();} finally {lock.unlock();}}private static void m1() {lock.lock();try {log.debug("m1");m2();} finally {lock.unlock();}}private static void m2() {lock.lock();try {log.debug("m2");} finally {lock.unlock();}}
}
可打断
lock方法不支持打断
加入打断机制,防止无限制的运行,避免产生死锁
其他线程调用interrupt打断无限制的等待
public class Test20 {private static ReentrantLock lock = new ReentrantLock();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {log.debug("尝试获取锁");lock.lockInterruptibly();} catch (InterruptedException e) {log.debug("没有获取到锁");e.printStackTrace();return;}try {log.debug("获取到锁");} finally {lock.unlock();}}, "t1");lock.lock();t1.start();log.debug("开启打断");t1.interrupt();}
}
锁超时
主动避免无限制等待的手段
超过等待时间,主动放弃获取锁,避免死锁
加入等待时间,且tryLock也支持打断
解决哲学家就餐问题
将Chopstick的对象做为一个ReentrantLock的锁对象
在获取不到锁时主动释放锁,从而避免了每个人都拿一个筷子的死锁问题
public class Test21 {public static void main(String[] args) throws InterruptedException {Chopstick c1 = new Chopstick("C1");Chopstick c2 = new Chopstick("C2");Chopstick c3 = new Chopstick("C3");Chopstick c4 = new Chopstick("C4");Chopstick c5 = new Chopstick("C5");new philosopher("p1", c1, c2).start();new philosopher("p2", c2, c3).start();new philosopher("p3", c3, c4).start();new philosopher("p4", c4, c5).start();new philosopher("p5", 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;}@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....");try {sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}class Chopstick extends ReentrantLock{String name;public Chopstick(String name){this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
公平锁
不公平锁:在锁释放时,处于阻塞队列的线程抢锁,谁抢到谁执行,不按照在阻塞队列的顺序先入先得获得锁
ReentrantLock默认时不公平的
条件变量
@Slf4j(topic = "c.Test22")
public class Test22 {static boolean hasCigarette = false;static boolean hasTakeout = false;static ReentrantLock ROOM = new ReentrantLock();static Condition waitCigaretteSet = ROOM.newCondition();static Condition waitTakeoutSet = ROOM.newCondition();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {ROOM.lock();try {log.debug("有烟没?");while (!hasCigarette) {log.debug("没有烟,先去休息!");try {waitCigaretteSet.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("可以开始干活了");} finally {ROOM.unlock();}}, "t1");Thread t2 = new Thread(() -> {ROOM.lock();try {log.debug("外卖到了没?");while (!hasTakeout) {log.debug("没有外卖,先去休息!");try {waitTakeoutSet.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("有外卖了,开始干活!");} finally {ROOM.unlock();}}, "t2");t1.start();t2.start();Thread.sleep(1000);new Thread(() -> {ROOM.lock();try {log.debug("烟到了!");hasCigarette = true;waitCigaretteSet.signal();} finally {ROOM.unlock();}}, "送烟的").start();new Thread(() -> {ROOM.lock();try {log.debug("外卖到了!");hasTakeout = true;waitTakeoutSet.signal();} finally {ROOM.unlock();}}, "送外卖的").start();}
}
同步模式之顺序控制(先2后1)
固定运行顺序--wait notify
用一个变量记录t2是否执行,t1根据变量来决定是否要等待。
@Slf4j(topic = "c.Test23")
public class Test23 {static Object lock = new Object();static boolean t2Runned = false;public static void main(String[] args) throws InterruptedException {new Thread(() -> {synchronized (lock) {while (!t2Runned) {try {lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}log.debug("t1");}}, "t1").start();new Thread(() -> {synchronized (lock) {log.debug("t2");t2Runned = true;lock.notifyAll();}}, "t2").start();}
}
固定运行顺序--await signal
@Slf4j(topic = "c.Test23")
public class Test23 {static ReentrantLock lock = new ReentrantLock();static boolean t2Runned = false;static Condition t1Room = lock.newCondition();public static void main(String[] args) throws InterruptedException {new Thread(() -> {lock.lock();try {while (!t2Runned) {try {t1Room.await();} catch (InterruptedException e) {e.printStackTrace();}}log.debug("t1");} finally {lock.unlock();}}, "t1").start();new Thread(() -> {lock.lock();try {log.debug("t2");t2Runned = true;t1Room.signal();} finally {lock.unlock();}}, "t2").start();}
}
固定运行顺序--park unpark
@Slf4j(topic = "c.Test24")
public class Test24 {public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{LockSupport.park();log.debug("t1");},"t1");Thread t2 = new Thread(()->{log.debug("t2");LockSupport.unpark(t1);},"t2");t1.start();t2.start();}
}
交替输出(1输出a5次,2输出b5次,3输出c5次)
交替输出--wait notify
@Slf4j(topic = "c.Test25")
public class Test25 {public static void main(String[] args) throws InterruptedException {WaitNotify waitNotify = new WaitNotify(1, 5);new Thread(() -> {waitNotify.print("a", 1, 2);}, "t1").start();new Thread(() -> {waitNotify.print("b", 2, 3);}, "t2").start();new Thread(() -> {waitNotify.print("c", 3, 1);}, "t3").start();}
}class WaitNotify {private int flag;private int loopNum;public WaitNotify(int flag, int loopNum) {this.flag = flag;this.loopNum = loopNum;}public void print(String name, int waitFlag, int nextFlag) {for (int i = 0; i < loopNum; i++) {synchronized (this) {while (flag != waitFlag) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print(name);flag = nextFlag;this.notifyAll();};}}
}
交替输出--await signal
@Slf4j(topic = "c.Test27")
public class Test27 {static ReentrantLock lock = new ReentrantLock();static Condition t1Room = lock.newCondition();static Condition t2Room = lock.newCondition();static Condition t3Room = lock.newCondition();public static void main(String[] args) throws InterruptedException {AwaitSignal awaitSignal = new AwaitSignal(5, lock);new Thread(() -> {awaitSignal.print("a", t1Room, t2Room);}, "t1").start();new Thread(() -> {awaitSignal.print("b", t2Room, t3Room);}, "t2").start();new Thread(() -> {awaitSignal.print("c", t3Room, t1Room);}, "t3").start();Thread.sleep(1000);lock.lock();try {t1Room.signal();} finally {lock.unlock();}}
}class AwaitSignal {private int loopNum;private ReentrantLock lock;public AwaitSignal(int loopNum, ReentrantLock lock) {this.loopNum = loopNum;this.lock = lock;}public void print(String name, Condition currentRoom, Condition nextRoom) {for (int i = 0; i < loopNum; i++) {lock.lock();try {currentRoom.await();System.out.print(name);nextRoom.signal();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}}
}
交替输出--park unpark
@Slf4j(topic = "c.Test26")
public class Test26 {static Thread t1, t2, t3;public static void main(String[] args) throws InterruptedException {t1 = new Thread(() -> {for (int i = 0; i < 5; i++) {LockSupport.park();System.out.print("a");LockSupport.unpark(t2);}}, "t1");t2 = new Thread(() -> {for (int i = 0; i < 5; i++) {LockSupport.park();System.out.print("b");LockSupport.unpark(t3);}}, "t2");t3 = new Thread(() -> {for (int i = 0; i < 5; i++) {LockSupport.park();System.out.print("c");LockSupport.unpark(t1);}}, "t3");t1.start();t2.start();t3.start();LockSupport.unpark(t1);}
}
相关文章:
JUC并发编程(上)
一、JUC学习准备 核心知识点:进程、线程、并发(共享模型、非共享模型)、并行 预备知识: 基于JDK8,对函数式编程、lambda有一定了解 采用了slf4j打印日志 采用了lombok简化java bean编写 二、进程与线程 进程和线程概念 两者对比…...
postgres--MVCC
PostgreSQL 的 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是其实现高并发和高性能的核心机制,支持多个事务同时读写数据库而无需加锁阻塞。它的核心思想是通过保留数据的多个版本来避免读写冲突,从而提…...
nanodet配置文件分析
以下是针对 NanoDet-Plus-M-1.5x_416 配置文件的逐模块解析,以及调整参数的作用和影响范围: 1. 模型架构(model) Backbone(骨干网络) backbone:name: ShuffleNetV2model_size: 1.5x # 控制网络宽度&…...
【Linux网络】HTTP
应用层协议 HTTP 前置知识 我们上网的所有行为都是在做IO,(我的数据给别人,别人的数据给我)图片。视频,音频,文本等等,都是资源答复前需要先确认我要的资源在哪台服务器上(网络IP&…...
Unity中AssetBundle使用整理(一)
一、AssetBundle 概述 AssetBundle 是 Unity 用于存储和加载游戏资源(如模型、纹理、预制体、音频等)的一种文件格式。它允许开发者将游戏资源打包成独立的文件,在运行时动态加载,从而实现资源的按需加载、更新以及减小初始安装包…...
CMOS内存的地址空间在主内存空间中吗?
CMOS内存(即CMOS RAM)的地址空间不位于主内存地址空间(如0x00000-0xFFFFF)内,而是通过独立的I/O端口地址进行访问,具体如下: 1. CMOS内存的物理存储与地址机制 CMOS RAM芯片通常集成在主板…...
大模型应用中常说的Rerank是什么技术?
Rerank技术详解 一、定义与基本原理 Rerank(重排序)是一种在信息检索系统中用于优化搜索结果排序的技术,其核心目标是通过二次评估和排序候选文档,提升结果的相关性和准确性。其运作机制通常分为两阶段: 初步检索:使用传统方法(如BM25关键词匹配或Embedding向量检索)…...
Python-MCPInspector调试
Python-MCPInspector调试 使用FastMCP开发MCPServer,熟悉【McpServer编码过程】【MCPInspector调试方法】-> 可以这样理解:只编写一个McpServer,然后使用MCPInspector作为McpClient进行McpServer的调试 1-核心知识点 1-熟悉【McpServer编…...
C 语言数据结构基石:揭开数组名的面纱与计算数组大小
各类资料学习下载合集 https://pan.quark.cn/s/8c91ccb5a474 在前面的文章中,我们已经学习了 C 语言一维数组的定义和初始化。我们知道数组是用来存储一系列相同类型数据的集合,并通过下标来访问每个元素。但是,除了通过下标访问单个元素,数组名本身在 C 语言中也…...
Java高频面试之并发编程-15
hello啊,各位观众姥爷们!!!本baby今天又来报道了!哈哈哈哈哈嗝🐶 面试官:as-if-serial 是什么?单线程的程序一定是顺序执行的吗? as-if-serial 规则 定义: …...
MySQL数据库迁移SQL语句指南
MySQL数据库迁移SQL语句指南 一、基础迁移方法 1. 使用mysqldump进行全量迁移 -- 导出源数据库(在命令行执行) mysqldump -u [源用户名] -p[源密码] --single-transaction --routines --triggers --events --master-data2 [数据库名] > migration…...
Vue:生命周期钩子
深入理解 Vue 的钩子函数(生命周期函数) Vue 的钩子函数(生命周期函数)是 Vue 实例在不同阶段自动调用的函数。可以在 Vue 实例的创建、更新、销毁等阶段插入自己的逻辑。 钩子函数的作用 想象一下,Vue 实例的生命周…...
深入理解设计模式之原型模式(Prototype Pattern)
一、为什么需要原型模式? 在传统对象创建方式中,我们通过new关键字直接调用构造函数创建实例。但当遇到以下场景时: 对象初始化需要消耗大量资源(如数据库连接)需要创建的对象与现有实例高度相似希望屏蔽对象创建的复…...
K8S cgroups详解
以下是 Kubernetes 中 cgroups(Control Groups) 的详细解析,涵盖其核心原理、在 Kubernetes 中的具体应用及实践操作: 一、cgroups 基础概念 1. 是什么? cgroups 是 Linux 内核提供的 资源隔离与控制机制,…...
ARMV8 RK3399 u-boot TPL启动流程分析 --start.S
上电后运行的第一支文件:arch/arm/cpu/armv8/start.S CONFIG_ENABLE_ARM_SOC_BOOT0_HOOK1 #include <asm/arch/boot0.h> 跳转到 arch/arm/include/asm/arch-rockchip/boot0.h CONFIG_SPL_BUILD1 b 1f ROCKCHIP_EARLYRETURN_TO_BROMno TINY_FRAMEWORKno …...
【网络原理】数据链路层
目录 一. 以太网 二. 以太网数据帧 三. MAC地址 四. MTU 五. ARP协议 六. DNS 一. 以太网 以太网是一种基于有线或无线介质的计算机网络技术,定义了物理层和数据链路层的协议,用于在局域网中传输数据帧。 二. 以太网数据帧 1)目标地址 …...
保姆级教程|YOLO11改进】【卷积篇】【4】使用RFAConv感受野注意力卷积,重塑空间特征提取,助力高效提点
《------往期经典推荐------》 一、AI应用软件开发实战专栏【链接】 项目名称项目名称1.【人脸识别与管理系统开发】2.【车牌识别与自动收费管理系统开发】3.【手势识别系统开发】4.【人脸面部活体检测系统开发】5.【图片风格快速迁移软件开发】6.【人脸表表情识别系统】7.【…...
虚幻引擎5-Unreal Engine笔记之常用核心类的继承关系
虚幻引擎5-Unreal Engine笔记之常用核心类的继承关系 code review! 文章目录 虚幻引擎5-Unreal Engine笔记之常用核心类的继承关系1.UE5中常用核心类的继承关系1.1.简化版1.2.plantuml图1.3.plantuml代码1.4.关于大写字母U和A2.1.组件和类的关系,组件也是类吗&…...
力扣2680题解
记录 2025.5.9 题目: 思路: 1.计算初始或值:首先计算数组中所有元素的按位或结果 allOr,这表示在不进行任何左移操作时数组的或值。 2.计算固定或值:在计算 allOr 的同时,计算一个 fixed 值,…...
搭建基于chrony+OpenSSL(NTS协议)多层级可信时间同步服务
1、时间同步服务的层级概念 在绝大多数IT工程师实际工作过程中,针对于局域网的时间同步,遇到最多的场景是根据实际的需求,搭建一个简单的NTP时间同步服务以时间对局域网中的服务器、网络设备、个人电脑等基础设施实现同步授时功能。虽然这样…...
虚拟内存:深入解析与性能优化
文章目录 虚拟内存的概念虚拟内存的实现方式虚拟内存的页面置换算法虚拟内存的性能影响结论 在现代计算机系统中,虚拟内存(Virtual Memory)是一种至关重要的技术,它极大地提高了系统的多任务处理能力和内存利用率。本文将深入探讨…...
元数据和主数据
元数据和主数据是数据管理中的两个关键概念,其核心区别如下: 1. 定义与本质 元数据(Metadata) “关于数据的数据”,用于描述数据的属性、结构、来源、用途等上下文信息。 示例:数据库表的字段名称、数据类型…...
JavaScript事件处理全解析:从基础到最佳实践
在现代Web开发中,事件处理是构建交互式应用的核心技术。JavaScript提供了多种事件绑定方式,每种方法都有其适用场景和特点。本文将深入探讨7种主流的事件绑定方法,通过代码示例和原理分析,帮助开发者选择最合适的解决方案。 一、…...
高级数据结构:线段树
线段树概述 线段树是一种处理区间问题的优越算法,也是算法竞赛的常客。 线段树的特点是,类似于一棵二叉树,将一个序列分解成多个区间并储存在二叉树上。 例如,把区间 [ 1 , 10 ] [1,10] [1,10]作为树的根节点,然后把…...
精讲C++四大核心特性:内联函数加速原理、auto智能推导、范围for循环与空指针进阶
前引:在C语言长达三十余年的演进历程中,每一次标准更新都在试图平衡性能与抽象、控制与安全之间的微妙关系。从C11引入的"现代C"范式开始,开发者得以在保留底层控制能力的同时,借助语言特性大幅提升代码的可维护性与安全…...
用ffmpeg压缩视频参数建议
注意:代码中的斜杠\可以删除 一、基础压缩命令(画质优先) ffmpeg -i input.mp4 \-c:v libx264 -preset slow -crf 23 \ # H.264编码,平衡速度与质量-c:a aac -b:a 128k \ # 音频压缩-vf "scaleif(gt(a,16/9),1920,-2):if(…...
uni-app学习笔记(二)--vue页面代码的构成和新建页面
vue页面的构成 一.template 模板区,主要放html布局,注意,如果是开发uni-app,模板区不要放div,h1等标签了,用了在小程序和app端起不到作用。具体应该使用哪些组件,可在uni-app官网上查看:组件-…...
机器语言程序、汇编语言程序、硬件描述语言程序、编译程序、解释程序和链接程序
程序类型定义与核心特征处理对象 / 输入输出结果所属领域典型例子 / 作用机器语言程序由二进制指令(0/1 序列)构成,可被 CPU 直接执行,与硬件架构强绑定。无(直接执行)无(直接运行)低…...
智能语音助手的未来:从交互到融合
摘要 随着人工智能技术的不断进步,智能语音助手已经成为我们生活中不可或缺的一部分。从简单的语音指令到复杂的多模态交互,语音助手正在经历一场深刻的变革。本文将探讨智能语音助手的发展历程、当前的技术瓶颈以及未来的发展方向,特别是其在…...
Redis从基础到高阶应用:核心命令解析与延迟队列、事务消息实战设计
Redis基础知识 #切换数据库 bd:0>select 2 "OK" bd:2>dbsize "0" #清空数据库 bd:0>flushdb "OK" #设置值 bd:0>set name "lyt" "OK" #查看所有key bd:0>keys *1) "name" #获取key bd:0>get …...
操作系统原理实验报告
操作系统原理课程的实验报告汇总 实验三:线程的创建与撤销 实验环境:计算机一台,内装有VC、office等软件 实验日期:2024.4.11 实验要求: 1.理解:Windows系统调用的基本概念,进程与线程的基…...
Python爬虫实战:研究nodejs aes加密
1. 引言 1.1 研究背景与意义 在当今数字化时代,Web 数据的价值日益凸显。通过爬虫技术获取公开数据并进行分析,能够为企业决策、学术研究等提供有力支持。然而,为了保护数据安全和隐私,许多网站采用了加密技术对数据进行保护,其中 AES 加密是一种常见且安全的加密算法。…...
线程的一些事(2)
在java中,线程的终止,是一种“软性”操作,必须要对应的线程配合,才能把终止落实下去 然而,系统原生的api其实还提供了,强制终止线程的操作,无论线程执行到哪,都能强行把这个线程干掉…...
基于 PostgreSQL 的 ABP vNext + ShardingCore 分库分表实战
🚀 基于 PostgreSQL 的 ABP vNext ShardingCore 分库分表实战 📑 目录 🚀 基于 PostgreSQL 的 ABP vNext ShardingCore 分库分表实战✨ 背景介绍🧱 技术选型🛠️ 环境准备✅ Docker Compose(多库 & 读…...
御网杯2025 Web,Msic,密码 WP
Web YWB_Web_xff 审计代码,发现需要$cip2.2.2.1 使用burpsuite抓包,添加X-Forwarded-For:2.2.2.1 然后得到flag YWB_Web_未授权访问 更加题目描述知道需要admin登录,但是现在是guest。 使用burpsuite抓包 发现cookie里面存在userÿ…...
tensorflow 1.x
简介 TensorFlow:2015年谷歌,支持python、C,底层是C,主要用python。支持CNN、RNN等算法,分CPU TensorFlow/GPU TensorFlow。 TensorBoard:训练中的可视化。 快捷键:shiftenter执行命令,Tab键进…...
[ERTS2012] 航天器星载软件形式化模型驱动研发 —— 对 Scade 语言本身的影响
在《从ERTS学习SCADE发展》中提到,在 ERTS 会议中,Scade团队会在该会议中介绍与Scade相关的工作。在 ERTS 2012 中,Scade 团队介绍了使用Scade作为主要工具,应用在航天器星载软件开发中的相关话题。原材料可参考 《Formal Model D…...
Spring Boot 集成 Flink CDC 实现 MySQL 到 Kafka 实时同步
Spring Boot 集成 Flink CDC 实现 MySQL 到 Kafka 实时同步 📌 项目背景 在大数据实时处理场景中,数据库变更数据的捕获与传输是关键环节。Flink CDC 提供了从 MySQL 等数据库中实时捕获数据变更的能力,并通过 Apache Flink 引擎实现流式处理。 本项目使用 Spring Boot …...
软件体系结构(Software Architecture)
文章目录 1. 分层架构(Layered Architecture)核心逻辑代码示例(伪代码)典型场景优缺点 2. 客户端-服务器(Client-Server)核心逻辑典型交互流程应用场景代码示例(RESTful API)优缺点 …...
RS485和RS232 通信配置
RS232 目前硬件上支持RS232的有以下板卡: LubanCat-5IO底板(含有RS232x2) 7.1. 引脚定义 具体的引脚定义可以参考背面的丝印 LubanCat-5IO底板 引脚定义图 7.2. 跳帽配置 LubanCat-5IO底板 鲁班买5IO底板上的RS485和RS232是共用同一组…...
【高数上册笔记篇02】:数列与函数极限
【参考资料】 同济大学《高等数学》教材樊顺厚老师B站《高等数学精讲》系列课程 (注:本笔记为个人数学复习资料,旨在通过系统化整理替代厚重教材,便于随时查阅与巩固知识要点) 仅用于个人数学复习,因为课…...
【网络安全】——大端序(Big-Endian)和小端序(Little-Endian)
字节序(Endianness)是计算机系统中多字节数据(如整数、浮点数)在内存中存储或传输时,字节排列顺序的规则。它分为两种类型:大端序(Big-Endian)和小端序…...
机器学习极简入门:从基础概念到行业应用
有监督学习(supervised learning) 让模型学习的数据包含正确答案(标签)的方法,最终模型可以对无标签的数据进行正确处理和预测,可以分为分类与回归两大类 分类问题主要是为了“尽可能分开整个数据而画线”…...
MIT XV6 - 1.5 Lab: Xv6 and Unix utilities - xargs
接上文 MIT XV6 - 1.4 Lab: Xv6 and Unix utilities - find xargs 继续实验,实验介绍和要求如下 (原文链接 译文链接) : Write a simple version of the UNIX xargs program for xv6: its arguments describe a command to run, it reads lines from the standard …...
Springboot整合Swagger3
Springboot整合Swagger3、常用注解解释、访问Swagger地址出现404、403、拒绝访问等问题_swagger3注解-CSDN博客...
经典音乐播放器——完美歌词 Poweramp Music Player 3 build
—————【下 载 地 址】——————— 【本章单下载】:https://drive.uc.cn/s/d6c480bc47604 【百款黑科技】:https://ucnygalh6wle.feishu.cn/wiki/HPQywvPc7iLZu1k0ODFcWMt2n0d?fromfrom_copylink —————【下 载 地 址】——————— 本…...
锚定基础与拥抱融合:C 语言在编程教育与技术社区的破圈之路
引言 在 Python 占据 TIOBE 指数榜首的 2025 年,C 语言以 23.4% 的稳定份额(2025 年 5 月数据)持续稳居前三,这一现象在编程教育领域尤为显著:全球 92% 的计算机科学本科课程仍将 C 语言作为必修基础课,而…...
深度学习入门:从神经网络基础到前向传播全面解析
深度学习入门:从神经网络基础到前向传播全面解析 🔥 重磅干货! 本文是《深度学习基础与核心技术详解》专栏的开篇之作,将系统性地带你走进深度学习的世界!建议收藏+关注,错过可能要找很久哦~ 目录 深度学习概述神经网络基础 2.1 生物神经元与人工神经元2.2 感知机模型2.…...
Lambda表达式能用在哪些场景?
Lambda表达式是Java 8引入的一种强大特性,它允许以简洁的方式表示匿名函数(即没有名字的函数)。Lambda表达式可以用于许多场景,尤其是在与函数式接口、Stream API、并发编程等结合时,能够显著简化代码并提高开发效率。…...
英语听力口语词汇--2.宣传类
1.approach uk /əˈprəʊtʃ/ n.(思考问题的)方式,方法,态度 2.foreign uk /ˈfɒr.ən/ adj.外国的 3.alliance uk /əˈlaɪ.əns/ n.结盟国家(或团体),同盟国家(或团体)&...