两个线程交替打印到多线程交替打印
一. Synchronized 中 wait()和notify()
1.两个线程之间Synchronized交替输出
public static void main(String[] args) {
char[] chars1 = "1234567".toCharArray();
char[] charsA = "ABCDEFG".toCharArray();
Thread thread1 =new Thread(new Runnable() {
@Override
public void run() {
synchronized (Object.class) {
for (char c:
chars1) {
Object.class.notify();
System.out.print(c);
try {
Object.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object.class.notify();
}
}
},"数字");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Object.class) {
for (char c :
charsA) {
Object.class.notify();
System.out.print(c);
try {
Object.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object.class.notify();
}
}
}, "字母");
thread1.start();
thread2.start();
}
把Object类模板当做一把锁,两个线程去争夺这一把锁,最后的结果为预期中交替打印,但是这里会发现
1.1 为什么要在最后循环结束后在唤醒一次对方?
这里打断点可以看到,当打印最后的信息的时候进入了wait方法,使得当前线程等待,如果不最后唤醒对方,一定会使得有一个线程一直处于无限制的等待中。
2 Synchronized三个线程到多个线程
我们以上面通用的思考方式去写下面的代码。
public class ThreadSynchronizedWaitNotify {
public static void main(String[] args) {
char[] chars1 = "1234567".toCharArray();
char[] charsA = "ABCDEFG".toCharArray();
char[] charsa = "abcdefg".toCharArray();
Thread thread1 =new Thread(new Runnable() {
@Override
public void run() {
synchronized (Object.class) {
for (char c:
chars1) {
Object.class.notify();
System.out.print(c);
try {
Object.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object.class.notify();
}
}
},"数字");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Object.class) {
for (char c :
charsA) {
Object.class.notify();
System.out.print(c);
try {
Object.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object.class.notify();
}
}
}, "字母");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Object.class) {
for (char c:
charsa) {
Object.class.notify();
System.out.print(c);
try {
Object.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object.class.notify();
}
}
},"小字母");
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果
发现有很多种可能性,答案是不确定的,最基本的输出就是两个数组总的全队列输出,在加第三个数组中的一个值输出,比如1a2b3c4d5e6f7gA。
最好的情况下就是全部输出但是程序不停,而且可能无法保证交替1A2B3C4aDbEcFdGef5g67。
即使完成了交替也无法关闭所有线程,单数下一定有一个线程会在最后等待。
3.通过上面可以看出 synchronized 的问题
synchronized保证只有一个线程拿到锁,能够进入同步代码块。
相当于外部的等待线程形成了一个队列,无法保证稳定交替输出,并不能指定顺序。
而synchronized的方法wait()是让当前线程进入睡眠,他还有notify()是随机唤醒一个,而无法指定。还有一个nitifyAll()这个是唤醒所有的但是至于谁能抢到锁那就看自己的本事了。
3.1 notify() 相当于形成了一个队列
这个队列会依次让线程先进先出的获取锁对象,但是这个排队的效果也无法控制,增加了结果的不确定性,并且无法稳定交替
而notifyAll()就是唤醒所有除了本身的线程,然他们进行新一轮的争夺。不排队了谁拳头大谁说话算数。
二. ReentrantLock newCondition()
在ReentrantLock 就得到了简单的解决,每一个Condition 就相当于两个线程之间的一个队列
private volatile static int count = 0;
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
char[] chars1 = "1234567".toCharArray();
char[] charsA = "ABCDEFG".toCharArray();
char[] charsa = "abcdefg".toCharArray();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
for (char c :
chars1) {
if (count % 3 != 0){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.print(c);
condition1.signal();
}
lock.unlock();
}
}, "数字");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
for (char c :
charsA) {
if (count % 3 != 1) {
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count++;
System.out.print(c);
condition2.signal();
}
lock.unlock();
}
}, "字母");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
for (char c :
charsa) {
if (count % 3 != 2) {
try {
condition2.await();
} catch (InterruptedException e) {
}
}
count++;
System.out.print(c);
condition.signal();
}
lock.unlock();
}
}, "小字母");
thread1.start();
thread2.start();
thread3.start();
}
1. ReentrantLock和synchronized的区别,这里可以看出
synchronized无法实现精确唤醒,而ReentrantLock可以通过绑定Condition实现精确唤醒。有一个有序的线程交替结果。
三. LockSupport 的 unpark() 和 park()
static Thread thread2 = null;
static Thread thread3 = null;
static Thread thread = null;
public static void main(String[] args) {
char[] chars1 = "1234567".toCharArray();
char[] charsA = "ABCDEFG".toCharArray();
char[] charsa = "abcdefg".toCharArray();
thread = new Thread(new Runnable() {
@Override
public void run() {
for (char c :
charsa) {
LockSupport.park();
System.out.print(c);
LockSupport.unpark(thread2);
}
}
}, "小字母");
thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (char c :
chars1) {
LockSupport.park();
System.out.print(c);
LockSupport.unpark(thread3);
}
}
}, "数字");
thread3 = new Thread(new Runnable() {
@Override
public void run() {
for (char c:
charsA) {
LockSupport.unpark(thread);
System.out.print(c);
LockSupport.park();
}
}
},"字母");
thread.start();
thread2.start();
thread3.start();
}
也可以做到多线程交替打印,他有两个关节方法park(停车阻塞线程)和unpark(启动唤醒线程),他底层是调用的Unsafe类,这个直接操作内存,提供了硬件级别的的原子性操作,从而控制了线程交替输出的需求。
如有错误欢迎指正