记录笔试时碰到的一个多线程题目…
做题时的感觉:
1.很多线程方法都忘了,而且对某些关键字的理解会有些刻板印象
2.现在的想法是…看看能不能通过这道题,练习利用idea来进行多线程debug
3.归纳易混淆的和线程生命周期有关的方法(记得当时记在了微信的对话框上)
4.新的内容:阻塞队列及应用,CountDownLatch是基于阻塞队列的应用
当前进度:看了一些多线程打印题,但是不能复写
题目
package com.exampledb.demo.po;
import lombok.SneakyThrows;
public class ThreadTest {
@SneakyThrows
public static void main(String[] args) {
StringBuilder stringBuilder = new StringBuilder();
Thread t1 = new Thread(() -> {
synchronized (stringBuilder) {
stringBuilder.append("A");
try {
stringBuilder.wait();
} catch (InterruptedException e) {
System.out.println("hhhhhhhh");
}
stringBuilder.append("B");
}
});
Thread t2 = new Thread(() -> {
synchronized (stringBuilder) {
stringBuilder.append("C");
stringBuilder.notify();
stringBuilder.append("D");
}
});
t1.start();
Thread.sleep(300);
t2.start();
t1.join();
t2.join();
System.out.println(stringBuilder.toString());
}
}
写题时的疑惑
- join方法是让调用者插队吗?如果A线程先调join,B线程跟着调join,那么B会等A先执行完?还是会先越过B先执行?
- sync一定能保证代码块的原子性嘛
- notify和wait的作用
- wait是阻塞哪个线程,调用它的线程还是阻塞当前正在运行的线程?它要等别的线程调notify唤醒??
运行结果
ACDB
参考资料
Java 并发编程:线程间的协作(waittifyeep/yield/join)
Java线程之sleep(), wait(), yield() 三个方法的作用和特点
wait()、notify()、notifyAll()与sync,monitor、IllegalMonitorStateException异常
wait方法的使用必须在同步的范围内,否则就会抛出IllegalMonitorStateException异常,wait方法的作用就是阻塞当前线程等待notify/notifyAll方法的唤醒,或等待超时后自动唤醒
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/a229d4f59d936e38bc987b6f901485c7.png)
wait和notify、notifyAll方法要搭配sync一起用。。不然会爆异常。。。
这些方法的调用者持有的锁一定要和sync锁住的内容一致,不然也会报异常
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/48a2b48cb568a2e5e4928a595db414ba.png)
笔记
关键:是否会让出锁,是否会让出CPU资源。。还有sync是否能保持原子性,在于线程有没有调用方法把锁放掉,如果没放掉锁,那么就可以保持代码块的原子性
资料
CAS与sync
CAS(Compare-and-Swap,即比较并替换)算法
适用场景不同:
CAS与synchronized的对比
简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),
synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
一道题
阻塞队列
阻塞队列:
多线程环境下控制数据的生产和消费端的数据处理效率匹配。。
比如,生产者有多个线程生产数据,这些线程都放在阻塞队列A里;
消费者有多个线程消费数据,这些线程都放在阻塞队列B里;
当A生产数据太多,B消费不过来时,A里面的线程将会被阻塞,被挂起
阻塞队列demo
代码copy自原文
生产者线程
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 生产者线程
*
* @author jackyuj
*/
public class Producer implements Runnable {
private volatile boolean isRunning = true;//是否在运行标志
private BlockingQueue queue;//阻塞队列
private static AtomicInteger count = new AtomicInteger();//自动更新的值
private static final int DEFAULT_RANGE_FOR_SLEEP = 1000;
//构造函数
public Producer(BlockingQueue queue) {
this.queue = queue;
}
public void run() {
String data = null;
Random r = new Random();
System.out.println("启动生产者线程!");
try {
while (isRunning) {
System.out.println("正在生产数据...");
Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));//取0~DEFAULT_RANGE_FOR_SLEEP值的一个随机数
data = "data:" + count.incrementAndGet();//以原子方式将count当前值加1
System.out.println("将数据:" + data + "放入队列...");
if (!queue.offer(data, 2, TimeUnit.SECONDS)) {//设定的等待时间为2s,如果超过2s还没加进去返回true
System.out.println("放入数据失败:" + data);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
System.out.println("退出生产者线程!");
}
}
public void stop() {
isRunning = false;
}
}
消费者
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 消费者线程
*
* @author jackyuj
*/
public class Consumer implements Runnable {
private BlockingQueue<String> queue;
private static final int DEFAULT_RANGE_FOR_SLEEP = 1000;
//构造函数
public Consumer(BlockingQueue<String> queue) {
this.queue = queue;
}
public void run() {
System.out.println("启动消费者线程!");
Random r = new Random();
boolean isRunning = true;
try {
while (isRunning) {
System.out.println("正从队列获取数据...");
String data = queue.poll(2, TimeUnit.SECONDS);//有数据时直接从队列的队首取走,无数据时阻塞,在2s内有数据,取走,超过2s还没数据,返回失败
if (null != data) {
System.out.println("拿到数据:" + data);
System.out.println("正在消费数据:" + data);
Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));
} else {
// 超过2s还没数据,认为所有生产线程都已经退出,自动退出消费线程。
isRunning = false;
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
} finally {
System.out.println("退出消费者线程!");
}
}
}
测试类
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public class BlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
// 声明一个容量为10的缓存队列
BlockingQueue<String> queue = new LinkedBlockingQueue<String>(10);
//new了三个生产者和一个消费者
Producer producer1 = new Producer(queue);
Producer producer2 = new Producer(queue);
Producer producer3 = new Producer(queue);
Consumer consumer = new Consumer(queue);
// 借助Executors
ExecutorService service = Executors.newCachedThreadPool();
// 启动线程
service.execute(producer1);
service.execute(producer2);
service.execute(producer3);
service.execute(consumer);
// 执行10s
Thread.sleep(10 * 1000);
producer1.stop();
producer2.stop();
producer3.stop();
Thread.sleep(2000);
// 退出Executor
service.shutdown();
}
}
并发社区文章
多线程顺序打印总结
新的疑惑
- 制造死锁,观察死锁导致的CPU飙高
- 制造优先级
- 各种方法的操作主体对象是谁。究竟是谁调用谁就让出?
还是无论是谁主动调这个方法,被作用的对象总是当前在跑的那个线程?? - yield和notify打配合?
- yield会让出锁?但sleep不会,所以用sleep记得设置超时时间
- jvm对线程的调度是无序的!这一点超级重要,开发者是无法百分百控制优先级的。
因为会出现这样的情况,尽管某线程已经主动将资源让出,但是由于jvm对线程的调度是无序的,
那么就会出现第二次jvm还是选中该线程继续执行的情况!! - 如果要加锁,那么锁的粒度越小越好,不要影响并发!!
- 怎么往线程里传入参数?(构造方法,set方法,回调函数)回调方法体现的是解耦思想
- 回调函数分为:同步回调,异步回调