多线程交替执行
- 方案1 使用 Condition + ReentrantLock
Condition 是个接口,内部定义了方法await和signal,主要用于代替wait和notify方法。其实现对象为 ConditionObject,主要使用在AQS中,用于在AQS中利用Condition的特性来阻塞队列的读写。同时在 ReentrantLock 中,通过newCondition 方法来获取其在ReentrantLock中的Condition对象实例。
获取的对象实例需要在ReentrantLock的lock和unlock之间使用。
final ReentrantLock lock = new ReentrantLock();
final Condition condition1 = lock.newCondition();
final Condition condition2 = lock.newCondition();
final Condition condition3 = lock.newCondition();
final int[] signal = new int[1];
new Thread(() -> {
for (int i = 0; i < 5; i++) {
lock.lock();
if (signal[0] == 0) {
System.out.println("> 1 < " + i);
signal[0] = 2;
condition2.signal();
}
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
lock.lock();
if (signal[0] == 2) {
System.out.println("> 2 < " + i);
signal[0] = 3;
condition3.signal();
}
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
lock.lock();
if (signal[0] == 3) {
System.out.println("> 3 < " + i);
signal[0] = 0;
condition1.signal();
}
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}).start();
Thread.sleep(1000);
运行结果
> 1 < 0
> 2 < 0
> 3 < 0
> 1 < 1
> 2 < 1
> 3 < 1
> 1 < 2
> 2 < 2
> 3 < 2
> 1 < 3
> 2 < 3
> 3 < 3
> 1 < 4
> 2 < 4
> 3 < 4
- 方案2 使用 synchronized + wait + notify
使用 synchronized,表示同步代码块,其实含义和ReentrantLock基本作用一样,只是在一些方面上有些区别,但是配合wait使用时候,要求wait的对象必须是 synchronized 锁住的对象,所以这就要求我们无法在一个synchronized代码块中使用多个对象的wait,而只能使用其锁对象的wait和notify,否则会抛出异常 IllegalMonitorStateException。
final Object obj = new Object();
final int[] signal = new int[1];
new Thread(() -> {
for (int i = 0; i < 5; i++) {
synchronized (obj) {
if (signal[0] == 0) {
System.out.println("> 1 < " + i);
signal[0] = 1;
obj.notifyAll();
}
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
synchronized (obj) {
if (signal[0] == 1) {
System.out.println("> 2 < " + i);
signal[0] = 2;
obj.notifyAll();
}
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
synchronized (obj) {
if (signal[0] == 2) {
System.out.println("> 3 < " + i);
signal[0] = 0;
obj.notifyAll();
}
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
Thread.sleep(1000);
运行结果
> 1 < 0
> 2 < 0
> 3 < 1
> 1 < 2
> 2 < 2
> 3 < 3
> 1 < 4
> 2 < 4
多线程顺序运行
- 方案1 join
使用join的地方,当前线程会等待join的线程执行完毕后,再继续执行当前线程。join方法是线程内部的特有方法,实现原理其实是Thread内部定义了个Object对象作为锁lock。
join方法为Synchronized修饰,配合wait方法暂停当前线程的运行。等待run方法执行完毕后,才会释放该锁。
final Thread t1 = new Thread(() -> System.out.println("t1 start"));
final Thread t2 = new Thread(() -> {
System.out.println("i am t2 start");
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am t2 end");
});
final Thread t3 = new Thread(() -> {
System.out.println("i am t3 start");
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am t3 end");
});
t2.start();
t3.start();
t1.start();
Thread.sleep(2000);
运行结果
i am t2 start
t1 start
i am t3 start
i am t2 end
i am t3 end
- 方案2 CountDownLatch
该工具类专门为线程而生,实例化时候可以传入一个int值,表示想要阻拦几个异步动作,对于想要等待的位置调用方法await进行等待,其他地方调用countDown来消耗阻拦,当countDown方法调用次数等于实例化时候传入的int值时候,await方法执行结束,开始继续执行后边代码块。其内部使用了AQS机制进行等待和释放。
final CountDownLatch countDownLatch1 = new CountDownLatch(1);
final CountDownLatch countDownLatch2 = new CountDownLatch(1);
Thread t1 = new Thread(() -> {
System.out.println("i am t1 start");
System.out.println("i am t1 end");
countDownLatch1.countDown();
});
Thread t2 = new Thread(() -> {
System.out.println("i am t2 start");
try {
countDownLatch1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am t2 end");
countDownLatch2.countDown();
});
Thread t3 = new Thread(() -> {
System.out.println("i am t3 start");
try {
countDownLatch2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am t3 end");
});
t3.start();
t2.start();
t1.start();
Thread.sleep(2000);
运行结果
i am t1 start
i am t2 start
i am t3 start
i am t1 end
i am t2 end
i am t3 end
- 方案3 Condition + ReentrantLock
利用Condition的signal和await方法,按照顺序依次唤醒我们想要的线程来实现。
final ReentrantLock lock = new ReentrantLock();
final Condition condition1 = lock.newCondition();
final Condition condition2 = lock.newCondition();
Thread t1 = new Thread(() -> {
System.out.println("i am t1 start");
lock.lock();
System.out.println("i am t1 end");
condition1.signal();
lock.unlock();
});
Thread t2 = new Thread(() -> {
System.out.println("i am t2 start");
lock.lock();
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am t2 end");
condition2.signal();
lock.unlock();
});
Thread t3 = new Thread(() -> {
System.out.println("i am t3 start");
lock.lock();
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am t3 end");
lock.unlock();
});
t3.start();
t2.start();
t1.start();
Thread.sleep(1000);
运行结果
i am t3 start
i am t1 start
i am t2 start
i am t1 end
i am t2 end
i am t3 end
- 方案4 ConditionVariable
该类其实是对synchroized + wait + notifyAll三者的一个封装,对外暴露open,block和close方法。作用和CountDownLatch类似,但是它可以重复使用。重复使用时候,需要调用close方法进行重置。
ConditionVariable variable1 = new ConditionVariable();
ConditionVariable variable2 = new ConditionVariable();
Thread t1 = new Thread(() -> {
Log.d("zzz","i am t1 " + System.currentTimeMillis());
variable1.open();
});
Thread t2 = new Thread(() -> {
variable1.block();
Log.d("zzz","i am t2 " + System.currentTimeMillis());
variable2.open();
});
Thread t3 = new Thread(() -> {
variable2.block();
Log.d("zzz", "i am t3 " + System.currentTimeMillis());
});
t1.start();
t2.start();
t3.start();
Thread.sleep(500);
运行结果
i am t1 1687867126908
i am t2 1687867126908
i am t3 1687867126908
多线程同时运行
- 方案1 CountDownLatch
使用CountDownLatch的await方法在各个线程的run方法中进行等待,当countDown被调用时候,各个线程同时触发await方法,线程得以继续运行。
final CountDownLatch countDownLatch = new CountDownLatch(1);
Thread t1 = new Thread(() -> {
System.out.println("i am t1 start");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am t1 end, time:" + System.currentTimeMillis());
});
Thread t2 = new Thread(() -> {
System.out.println("i am t2 start");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am t2 end, time:" + System.currentTimeMillis());
});
Thread t3 = new Thread(() -> {
System.out.println("i am t3 start");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i am t3 end, time:" + System.currentTimeMillis());
});
t1.start();
t2.start();
t3.start();
Thread.sleep(500);
countDownLatch.countDown();
Thread.sleep(1000);
运行结果
i am t3 start
i am t2 start
i am t1 start
i am t3 end, time:1687856571244
i am t2 end, time:1687856571244
i am t1 end, time:1687856571244
- 方案2 Semaphore
Semaphore是为了约束当前代码/资源可以同时被多少个线程访问而存在。Semaphore实例化时候需要传入一个int,表示并发数,传3则表示可以被acquire方法调用三次。线程中调用acquire方法时候,内部计数器会自动+1,如果计数器大于int值,那么acquire方法就会阻塞,直到其他地方调用了release方法释放了一个锁,那么这里才可以继续执行。
对于多线程同时开始运行,就可以使用该类,实例化传入对应并发数,且直接通过acquire获取完其并发额度,这样后续每个线程执行中,遇到这个方法都会阻塞。线程起来后,直接通过release方法释放完所有的额度,这样所有阻塞的线程可以同时继续执行。
final Semaphore semaphore = new Semaphore(3);
semaphore.acquire(3);
Thread t1 = new Thread(() -> {
System.out.println("i am t1 start");
try {
semaphore.acquire(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("i am t1 end, time:" + System.currentTimeMillis());
});
Thread t2 = new Thread(() -> {
System.out.println("i am t2 start");
try {
semaphore.acquire(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("i am t2 end, time:" + System.currentTimeMillis());
});
Thread t3 = new Thread(() -> {
System.out.println("i am t3 start");
try {
semaphore.acquire(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("i am t3 end, time:" + System.currentTimeMillis());
});
t2.start();
t1.start();
t3.start();
Thread.sleep(500);
semaphore.release(3);
Thread.sleep(1000);
运行结果
i am t2 start
i am t3 start
i am t1 start
i am t3 end, time:1687856648821
i am t1 end, time:1687856648821
i am t2 end, time:1687856648821
- 方案3 【不是很准确】 cyclicBarrier
cyclicBarrier表面意思是栅栏的意思,就是直到所有目标都到达了栅栏这里,栅栏才会打开,所有目标继续前进。比如打王者,我方队友5个玩家,相当于5个线程,只有当5个线程都执行完匹配的动作,那么才会进行下一步,进入到选择人物界面。这里就可以利用cyclicBarrier.保证当前的多个线程可以在不结束的情况下,同时到达一个地点,然后再各自继续执行。
这里也可以使用cyclicBarrier来控制,但是每个线程开始的时间总会有几毫秒的误差,所以精度很高的情况下,这种方法就不合适了。
final CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
Thread t1 = new Thread(() -> {
System.out.println("i am t1 start");
try {
cyclicBarrier.await();
System.out.println("i am t1 end, time:" + System.currentTimeMillis());
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
System.out.println("i am t2 start");
try {
cyclicBarrier.await();
System.out.println("i am t2 end, time:" + System.currentTimeMillis());
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
});
Thread t3 = new Thread(() -> {
System.out.println("i am t3 start");
try {
cyclicBarrier.await();
System.out.println("i am t3 end, time:" + System.currentTimeMillis());
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
运行结果
i am t1 start
i am t3 start
i am t2 start
i am t2 end, time:1687856779900
i am t1 end, time:1687856779900
i am t3 end, time:1687856779900
- 方案4 ConditionVariable
仿照CountDownLatch可以实现该功能。
final ConditionVariable variable = new ConditionVariable();
Thread t1 = new Thread(() -> {
variable.block();
Log.d("zzz","i am t1 " + System.currentTimeMillis());
});
Thread t2 = new Thread(() -> {
variable.block();
Log.d("zzz","i am t2 " + System.currentTimeMillis());
});
Thread t3 = new Thread(() -> {
variable.block();
Log.d("zzz", "i am t3 " + System.currentTimeMillis());
});
t1.start();
t2.start();
t3.start();
Thread.sleep(50);
variable.open();
运行结果
i am t3 1687867425101
i am t1 1687867425102
i am t2 1687867425102
多线程全部执行完毕后,执行其他线程
- 方案1 cyclicBarrier
CyclicBarrier 本意就是为此而存在,实例化时候可以传一个runnable,当所有目标线程执行结束后,会先运行该Runnable,然后再继续向下运行。
final CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> System.out.println("i am barrier"));
Thread t1 = new Thread(() -> {
System.out.println("i am t1 start");
try {
cyclicBarrier.await();
System.out.println("i am t1 end, time:" + System.currentTimeMillis());
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
System.out.println("i am t2 start");
try {
cyclicBarrier.await();
System.out.println("i am t2 end, time:" + System.currentTimeMillis());
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
});
Thread t3 = new Thread(() -> {
System.out.println("i am t3 start");
try {
cyclicBarrier.await();
System.out.println("i am t3 end, time:" + System.currentTimeMillis());
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
运行结果
i am t3 start
i am t2 start
i am t1 start
i am barrier
i am t1 end, time:1687856872010
i am t3 end, time:1687856872010
i am t2 end, time:1687856872010
- 方案2 CountDownLatch
CyclicBarrier可以通过reset重新利用,而CountDownLatch只可以用一次,下次需要重新实例化。
final CountDownLatch countDownLatch = new CountDownLatch(3);
Thread barrier = new Thread(() -> {
System.out.println("i am barrier start");
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("i ma barrier");
});
barrier.start();
Thread t1 = new Thread(() -> {
System.out.println("i am t1 start");
countDownLatch.countDown();
});
Thread t2 = new Thread(() -> {
System.out.println("i am t2 start");
countDownLatch.countDown();
});
Thread t3 = new Thread(() -> {
System.out.println("i am t3 start");
countDownLatch.countDown();
});
t1.start();
t2.start();
t3.start();
Thread.sleep(1000);
运行结果
i am barrier start
i am t3 start
i am t1 start
i am t2 start
i ma barrier