同时启动三个线程, 按顺序打印 ABC三个字符串.
要求: 三个线程: 第一个只能打印 A, 第二个只能打印 B, 第三个只能打印 C, 同时执行, 要求打印结果: ABC
public class SequencePrint {
public static void main(String[] args) throws InterruptedException {
print();
}
private static void print() {
Thread t1 = new Thread(new Print("A", null));
Thread t2 = new Thread(new Print("B", t1));
Thread t3 = new Thread(new Print("C", t2));
// 这里建立一个依赖关系
// t2 依赖于 t1, 传入 t 后, 就会进入 if 语句, 等待 t1 执行完毕才会执行 t2, 打印 B
// t3 依赖于 t2, 传入 t 后, 只有等 t2 执行完毕才会执行 t3, 也即只有 t1 和 t2 执行完毕, 才会打印 C
// 这里这个依赖关系就直接导致了这三个线程传入后, 无论传入的顺序如何(这三个线程的系统轮转调度如何), 都会按顺序打印
t2.start();
t1.start();
t3.start();
}
private static class Print implements Runnable{
private String content;
private Thread t;
public Print(String content, Thread t) {
this.content = content;
this.t = t;
}
@Override
public void run() {
try {
if (t != null) {
// 如果传入的 t 不为 null, 那就是 t2 和 t3 线程, 那就让他 join 等待前一个线程执行结束在执行
t.join();
}
// 如果不是 null, 那此时传入的就是 t1 线程, 直接打印即可
System.out.println(content);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同时启动三个线程, 按顺序同时打印10次 A, B, C(进阶)
思路:
public class SequencePrint2 {
public static void main(String[] args) {
print2();
}
private static void print2() {
Thread t1 = new Thread(new Print2("A"));
Thread t2 = new Thread(new Print2("B"));
Thread t3 = new Thread(new Print2("C"));
t2.start();
t1.start();
t3.start();
}
private static class Print2 implements Runnable{
private String content;
// 创建两个和类绑定起来的属性: 字符串数组和下标
private static String[] ARRAY = {"A", "B", "C"};
private static int INDEX;
public Print2(String content) {
this.content = content;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
synchronized (ARRAY) {
while (!ARRAY[INDEX].equals(content)) {
// 这里要进行等待操作, wait 操作必须用在 synchronized 代码块里, 所以要进行加锁操作
// 这里每执行一次就得把锁释放掉, 重新判断是否等待, 所以 synchronized 加锁操作就得在 while外面
ARRAY.wait();
}
System.out.print(content);
// 这里还需要考虑的输出是否规范, 也就是换行符的打印
// 逻辑就是: 如果下标指向的是 C 这个字符串, 那就打印换行符
if (INDEX == ARRAY.length - 1) {
System.out.println();
}
// 打印完以后, 怎么唤醒
// 1. 给下标加一, 更新下标
INDEX = (INDEX + 1) % ARRAY.length; // 这里类似于循环队列的操作
// 2. 唤醒线程
ARRAY.notifyAll();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 实现阻塞式队列
package Java05_26;
// 阻塞式队列
// (1) 基于数组的循环队列实现
// (2) 提供一个队列, 消费的时候(取出元素), 如果队列为空, 阻塞等待.
// 如果队列为满, 生产的时候(存元素), 阻塞等待
public class MyBlockingQueue<T> {
private Object[] table;
private int getIndex; // 取元素的索引
private int putIndex; // 存元素的索引
private int size; // 数组有效元素个数
public MyBlockingQueue(int capacity) {
table = new Object[capacity];
}
public synchronized void put(T element) throws InterruptedException {
while (size == table.length) {
// 如果队列满了, 就等待
wait(); // this 对象绑定的 wait 方法
}
// 1. 把元素放到 putIndex 的位置
table[putIndex] = element; // 存放元素
// 2. 更新 putIndex 和 size 的值
size++;
putIndex = (putIndex + 1) % table.length;
notifyAll();
Thread.sleep(500);
}
public synchronized T get() throws InterruptedException {
while (size == 0) {
// 如果队列为空, 等待
wait();
}
// 1. 把 getIndex 的元素取出
Object element = table[getIndex]; // 取出元素
// 2. 更新 getIndex 和 size 的值
getIndex = (getIndex + 1) % table.length; // 注意这里是加1, 不是减1, 因为这相当于是一个循环队列
size--;
notifyAll();
Thread.sleep(500);
return (T)element;
}
// 这里必须使用 synchronized 加锁, 不能使用 volatile 不加锁, 如果不加锁, 会导致原子性丢失
// 在 get/put 方法没有结束的时候, 就有可能获取到 size, 此时是没有原子性的
public synchronized int getSize() {
return size;
}
// 模拟使用自定义阻塞式队列
public static void main(String[] args) {
MyBlockingQueue<Integer> queue = new MyBlockingQueue<>(100);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
while (true) {
synchronized (queue) {
// 模拟生产面包
queue.put(1);
System.out.println("存放面包+1 " + queue.getSize());
// 这里直接打印出来的 size 值是不对的, 因为 put 方法和 getSize 方法都是加了锁的操作, 两个方法的实行时间间隔不能保证
// put 方法执行完后应该立刻打印此时的 size , 但是 getSize 操作还要竞争对象锁, 所以是没有原子性的, 所以会出现问题
// 解决方案: 加锁
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
while (true) {
synchronized (queue) {
// 模拟消费面包
Integer e = queue.get();
System.out.println("消费面包-1 " + queue.getSize());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}