Java五种方式实现多线程循环打印问题
作者:北城酒卿 发布时间:2023-03-07 20:34:12
目录
wait-notify
join方式
ReentrantLock
ReentrantLock+Condition
Semaphore
三个线程T1、T2、T3轮流打印ABC,打印n次,如ABCABCABCABC…
N个线程循环打印1-100…
wait-notify
循环打印问题可以通过设置目标值,每个线程想打印目标值,如果拿到锁后这次轮到的数不是它想要的就进入wait
class Wait_Notify_ABC {
private int num;
private static final Object Lock = new Object();
private void print_ABC(int target) {
synchronized (Lock) {
//循环打印
for (int i = 0; i < 10; i++) {
while (num % 3 != target) {
try {
Lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.print(Thread.currentThread().getName());
Lock.notifyAll();
}
}
}
public static void main(String[] args) {
Wait_Notify_ABC wait_notify_abc = new Wait_Notify_ABC();
new Thread(() -> {
wait_notify_abc.print_ABC(0);
}, "A").start();
new Thread(() -> {
wait_notify_abc.print_ABC(1);
}, "B").start();
new Thread(() -> {
wait_notify_abc.print_ABC(2);
}, "C").start();
}
}
打印1-100问题可以理解为有个全局计数器记录当前打印到了哪个数,其它就和循环打印ABC问题相同。
class Wait_Notify_100 {
private int num;
private static final Object LOCK = new Object();
private int maxnum = 100;
private void printABC(int targetNum) {
while (true) {
synchronized (LOCK) {
while (num % 3 != targetNum) {
if (num >= maxnum) {
break;
}
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (num >= maxnum) {
break;
}
num++;
System.out.println(Thread.currentThread().getName() + ": " + num);
LOCK.notifyAll();
}
}
}
public static void main(String[] args) {
Wait_Notify_100 wait_notify_100 = new Wait_Notify_100();
new Thread(() -> {
wait_notify_100.printABC(0);
}, "thread1").start();
new Thread(() -> {
wait_notify_100.printABC(1);
}, "thread2").start();
new Thread(() -> {
wait_notify_100.printABC(2);
}, "thread3").start();
}
}
join方式
一个线程内调用另一个线程的join()方法可以让另一个线程插队执行,比如Main方法里调用了A.join(),那么此时cpu会去执行A线程中的任务,执行完后再看Main是否能抢到运行权。所以对于ABC,我们可以对B说让A插队,对C说让B插队
class Join_ABC {
static class printABC implements Runnable {
private Thread beforeThread;
public printABC(Thread beforeThread) {
this.beforeThread = beforeThread;
}
@Override
public void run() {
if (beforeThread != null) {
try {
beforeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
Thread t1 = new Thread(new printABC(null), "A");
Thread t2 = new Thread(new printABC(t1), "B");
Thread t3 = new Thread(new printABC(t2), "C");
t1.start();
t2.start();
t3.start();
Thread.sleep(100);
}
}
}
ReentrantLock
同理,synchronized和reentrantlock都是我们常用的加锁方式,不过后者可以中断,可以实现公平锁,可以使用condition…但是需要我们手动释放锁。jdk8后二者性能差不多,毕竟synchronized有锁升级的过程嘛。
class ReentrantLock_ABC {
private int num;
private Lock lock = new ReentrantLock();
private void printABC(int targetNum) {
for (int i = 0; i < 100; ) {
lock.lock();
if (num % 3 == targetNum) {
num++;
i++;
System.out.print(Thread.currentThread().getName());
}
lock.unlock();
}
}
public static void main(String[] args) {
Lock_ABC lockABC = new Lock_ABC();
new Thread(() -> {
lockABC.printABC(0);
}, "A").start();
new Thread(() -> {
lockABC.printABC(1);
}, "B").start();
new Thread(() -> {
lockABC.printABC(2);
}, "C").start();
}
}
ReentrantLock+Condition
以上方式如果线程抢到锁后发现自己无法执行任务,那么就释放,然后别的线程再抢占再看是不是自己的…这种方式比较耗时,如果我们能实现精准唤醒锁呢,即A完成任务后唤醒它的下一个即B,这就用到我们的Condition啦
class ReentrantLock_Condition_ABC {
private int num;
private static Lock lock = new ReentrantLock();
private static Condition c1 = lock.newCondition();
private static Condition c2 = lock.newCondition();
private static Condition c3 = lock.newCondition();
private void printABC(int targetNum, Condition currentThread, Condition nextThread) {
for (int i = 0; i < 100; ) {
lock.lock();
try {
while (num % 3 != targetNum) {
currentThread.await(); //阻塞当前线程
}
num++;
i++;
System.out.print(Thread.currentThread().getName());
nextThread.signal(); //唤醒下一个线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
ReentrantLock_Condition_ABC reentrantLockConditionAbc = new ReentrantLock_Condition_ABC();
new Thread(() -> {
reentrantLockConditionAbc.printABC(0, c1, c2);
}, "A").start();
new Thread(() -> {
reentrantLockConditionAbc.printABC(1, c2, c3);
}, "B").start();
new Thread(() -> {
reentrantLockConditionAbc.printABC(2, c3, c1);
}, "C").start();
}
}
Semaphore
小伙伴们有没有想到过,在生产者消费者模型中我们有哪几种实现方式呢?wait\notify,ReentrantLock,Semaphone,阻塞队列,管道输入输出流。
对的就是Semaphone。
Semaphore有acquire方法和release方法。 当调用acquire方法时线程就会被阻塞,直到获得许可证为止。 当调用release方法时将向Semaphore中添加一个许可证。如果没有获取许可证的线程, Semaphore只是记录许可证的可用数量。
使用Semaphore也可以实现精准唤醒。
class SemaphoreABC {
private static Semaphore s1 = new Semaphore(1); //因为先执行线程A,所以这里设s1的计数器为1
private static Semaphore s2 = new Semaphore(0);
private static Semaphore s3 = new Semaphore(0);
private void printABC(Semaphore currentThread, Semaphore nextThread) {
for (int i = 0; i < 10; i++) {
try {
currentThread.acquire(); //阻塞当前线程,即信号量的计数器减1为0
System.out.print(Thread.currentThread().getName());
nextThread.release(); //唤醒下一个线程,即信号量的计数器加1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
SemaphoreABC printer = new SemaphoreABC();
new Thread(() -> {
printer.printABC(s1, s2);
}, "A").start();
Thread.sleep(100);
new Thread(() -> {
printer.printABC(s2, s3);
}, "B").start();
Thread.sleep(100);
new Thread(() -> {
printer.printABC(s3, s1);
}, "C").start();
}
}
来源:https://blog.csdn.net/weixin_46263596/article/details/121909171


猜你喜欢
- 1.首先是屏蔽浏览器右键菜单的问题,用以下代码可以让浏览器用自己的右键菜单:tempBrowser.ContextMenuStrip = t
- 我们在编写网络程序的时候,经常会进行如下操作:申请一个缓冲区从数据源中读入数据至缓冲区解析缓冲区的数据重复第2步表面上看来这是一个很常规而简
- 前言循环语句为众多程序员们提供了很大的便利,有while、do...while、for和 foreach。而且foreach语句很简洁,但是
- Spring Data Jpa复杂查询总结只是做一个总结所以就不多说废话了实体类@Entity@Table(name = "t_h
- 1、理论一般如果想将类注册到spring容器,让spring来完成实例化,常用方式如下:xml中通过bean节点来配置;使用@Service
- Log4j组件构成 Log4j由三个重要的组件构成:1.日志信息的优先级(Logger)2.日志信息的输出目的地(Appender
- 在上一篇文章:Flutter进阶—实现动画效果(二)的最后,我们实现了一个控件,其中包含各种布局和状态处理控件。以及使用自定义的动画感知绘图
- 一. 多维数组1. 概念多维数组可以看成是数组的数组。 比如二维数组就是一个特殊的一维数组,它的每个元素都是一个一维数组。其他多维数组的概念
- 前段时间写了一篇基于mybatis实现的多数据源博客。感觉不是很好,这次打算加入git,来搭建一个基于Mybatis-Plus的多数据源项目
- 关于springmvc上传图片的方法小编给大家整理了两种方法,具体内容如下所示:第一种:(放在该项目下的物理地址对应的位置)a. 路径写法:
- • 创建目录和文件1、通过Path类的Combine方法可以合并路径。string activeDir = @"C:\myDir&
- 介绍大家都知道微信支付的回调链接要求不能跟参数,但又要接收返回的xml数据。我开始使用@RequestBody注解在参数上,希望能获取xml
- springboot跳转到指定页面controller的写法必须是templates下面的页面,不经过配置,无法直接跳转到public,st
- 本文实例为大家分享了C#实现简单点餐系统的具体代码,供大家参考,具体内容如下实现简单点餐系统剖析1、设计界面(给菜品的序号(index),名
- 本文实例讲述了Android编程获取GPS数据的方法。分享给大家供大家参考,具体如下:GPS是Android系统中重要的组成部分,通过它可以
- 前言在SpringIOC中,我们熟知的BeanScope有单例(singleton)、原型(prototype), Bean的Scope影响
- Android仿微信activity滑动关闭功能1.利用具体利用v4包下的slidingPaneLayout实现透明的activity,代码
- 推荐教程:idea2021以下版本适用,通过无限重置试用持续激活:https://www.jb51.net/article/198343.h
- 一、树概念及结构1.1 树的概念树是一种非线性的数据结构,它是由n(n>=0)个有限结点组成一个具有层次关系的集合。把它叫做树是因 为
- Android DragVideo实现播放视频时任意拖拽DragVideoA Method to Drag the Video When P