CyclicBarrier(循环栅栏)
满人了之后再发车(满人了之后将栅栏去掉发车)
CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println(“满人”));
其中第一个参数代表执行前置的数量,第二个参数代表满人了执行什么操作。
使用场景:
如一个特别复杂的操作:
需要访问数据库需要访问网络,同时也需要访问文件,然后需要并发执行,必须要这些线程都执行完毕了才能去执行其他的操作,这个时候就可以用CyclicBarrier
public class TestCyclicBarrier {
public static void main(String[] args) {
//CyclicBarrier barrier = new CyclicBarrier(20);
CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("满人"));
/*CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
@Override
public void run() {
System.out.println("满人,发车");
}
});*/
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
执行结果如下:
Phaser
按照不同的阶段对线程执行,它本身是维护着一个阶段的成员变量,每个阶段执行相应的业务(遗传算法)
如下代码模拟结婚的一个场景,定义一个Person类表示婚礼现场人物的行为。然后我们的业务是要每个人都到场了才能进行下一步eat,然后要每个人都eat完了才能进行下一步离开,等所有人离开了才能调用hug方法。这里使用了一个MarriagePhaser继承Phaser类来表示我们的信号量。
public class TestPhaser {
static Random r = new Random();
static MarriagePhaser phaser = new MarriagePhaser();
static void milliSleep(int milli) {
try {
TimeUnit.MILLISECONDS.sleep(milli);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//当前注册的阶段有7个人参加
phaser.bulkRegister(7);
//首先五个人参加婚礼
for (int i = 0; i < 5; i++) {
new Thread(new Person("p" + i)).start();
}
//然后新娘新郎参加婚礼
new Thread(new Person("新郎")).start();
new Thread(new Person("新娘")).start();
}
/**
* Phaser就是分成好几个阶段的栅栏 这里为 0 1 2 3
* 0:五个客人两个主角来全了才能进行下一阶段
* 依次类推
* 到3除了新娘、新郎其他人都arriveAndDeregister了如果有下一阶段的业务那么只能新郎新娘参加
*
*/
static class MarriagePhaser extends Phaser {
/**
* 渐进行为
* @param phase 代表第几个阶段
* @param registeredParties 代表这个阶段有几个人参加
* @return 当return为true的时候代表所有的线程都结束了
*/
@Override
protected boolean onAdvance(int phase, int registeredParties) {
switch (phase) {
case 0:
System.out.println("所有人都到齐了!" + registeredParties);
System.out.println();
return false;
case 1:
System.out.println("所有人都吃完了!" + registeredParties);
System.out.println();
return false;
case 2:
System.out.println("所有人都离开了!" + registeredParties);
System.out.println();
return false;
case 3:
System.out.println("婚礼结束!新郎新娘抱抱!" + registeredParties);
return true;
default:
return true;
}
}
}
static class Person implements Runnable {
String name;
public Person(String name) {
this.name = name;
}
public void arrive() {
milliSleep(r.nextInt(1000));
System.out.printf("%s 到达现场!\n", name);
//到达等待着继续往前走
phaser.arriveAndAwaitAdvance();
}
public void eat() {
milliSleep(r.nextInt(1000));
System.out.printf("%s 吃完!\n", name);
phaser.arriveAndAwaitAdvance();
}
public void leave() {
milliSleep(r.nextInt(1000));
System.out.printf("%s 离开!\n", name);
phaser.arriveAndAwaitAdvance();
}
private void hug() {
if (name.equals("新郎") || name.equals("新娘")) {
milliSleep(r.nextInt(1000));
System.out.printf("%s 洞房!\n", name);
phaser.arriveAndAwaitAdvance();
} else {
//解除注册,没这个线程什么事了
phaser.arriveAndDeregister();
//phaser.register()
}
}
@Override
public void run() {
arrive();
eat();
leave();
hug();
}
}
}
执行结果如下:
p0 到达现场!
p3 到达现场!
新郎 到达现场!
p4 到达现场!
新娘 到达现场!
p1 到达现场!
p2 到达现场!
所有人都到齐了!7
新娘 吃完!
新郎 吃完!
p1 吃完!
p2 吃完!
p0 吃完!
p3 吃完!
p4 吃完!
所有人都吃完了!7
p2 离开!
p3 离开!
p4 离开!
p1 离开!
p0 离开!
新郎 离开!
新娘 离开!
所有人都离开了!7
新娘 洞房!
新郎 洞房!
婚礼结束!新郎新娘抱抱!2
Process finished with exit code 0
ReadWriteLock
读锁:共享锁(上了读锁其他线程可以继续读)
写锁:排他锁
如下代码分别采用ReentrantLock和ReentrantReadWriteLock来分别模拟一下读写锁的过程。比较执行结果发现ReentrantReadWriteLock执行效率快很多。
public class TestReadWriteLock {
static Lock lock = new ReentrantLock();
private static int value;
static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
static Lock readLock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();
public static void read(Lock lock) {
lock.lock();
try {
SleepHelper.sleepSeconds(1);
System.out.println("read over!");
//模拟读取操作
} finally {
lock.unlock();
}
}
public static void write(Lock lock, int v) {
lock.lock();
try {
SleepHelper.sleepSeconds(1);
value = v;
System.out.println("write over!");
//模拟写操作
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
// Runnable readR = () -> read(lock);
Runnable readR = ()-> read(readLock);
//Runnable writeR = () -> write(lock, new Random().nextInt());
Runnable writeR = ()->write(writeLock, new Random().nextInt());
for (int i = 0; i < 18; i++) new Thread(readR).start();
for (int i = 0; i < 2; i++) new Thread(writeR).start();
}
}
Semaphore(信号量)
信号灯亮着的时候能执行,信号灯灭了不能执行。
如下Semaphore的数量代表了最多允许多少个线程同时运行,如下代码中设置为1表示最大只允许一个线程执行,只有当线程T1执行完毕之后,T2才能做后续的操作。
一般运用于限流
/**
* 注意此处为try-catch-finally
*/
public class TestSemaphore {
public static void main(String[] args) {
//Semaphore s = new Semaphore(2);
//第二个参数代表公平以及非公平,默认是非公平的
Semaphore s = new Semaphore(1, true);
//允许一个线程同时执行
//Semaphore s = new Semaphore(1);
new Thread(() -> {
try {
//阻塞方法,先取到,取不到则等待,获得锁
s.acquire();
System.out.println("T1 running...");
Thread.sleep(200);
System.out.println("T1 running...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
s.release();
}
}).start();
new Thread(() -> {
try {
s.acquire();
System.out.println("T2 running...");
Thread.sleep(200);
System.out.println("T2 running...");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
Exchanger
用于两线程交换彼此的值
可以把Exchanger看成一个容器,调用exchange方法是阻塞执行的,在容器里面有两个位置,执行exchange的时候首先将T1放在1位置,T2放在2位置,然后再将1、2位置的T1和T2互换值,然后两个线程继续往前跑。
public class TestExchanger {
static Exchanger<String> exchanger = new Exchanger<>();
public static void main(String[] args) {
new Thread(() -> {
String s = "T1";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
}, "t1").start();
new Thread(() -> {
String s = "T2";
try {
s = exchanger.exchange(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " " + s);
}, "t2").start();
}
}
执行结果如下:
线程1和线程2对应的数据交换了
面试题
1、实现一个容器提供两个方法,add,size
写两个线程,线程1添加十个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束。
首先你可能会想到如下解法:
但是会出现两个问题:
1、首先是这个list的add方法并不是线程安全的,可能第一个线程才把元素加进去的时候,第一个线程还没有更新当前list的大小的时候,第二个线程来读取他的size了,那么读取到的数据还是原来的那个大小,并不是加了元素之后的大小
2、线程之间不可见的问题,size()的值改变了,第二个线程不能马上可见,第二个线程可能读取的是线程的本地缓存的值。
public class WithoutVolatile {
List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
WithoutVolatile c = new WithoutVolatile();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
new Thread(() -> {
while (true) {
// System.out.println("--");
if (c.size() == 5) {
break;
}
}
System.out.println("t2 结束");
}, "t2").start();
}
}
应该改成如下方式:
public class WithoutVolatile {
// List lists = new ArrayList();
volatile List lists = Collections.synchronizedList(new LinkedList<>());
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
WithoutVolatile c = new WithoutVolatile();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
new Thread(() -> {
while (true) {
if (c.size() == 5) {
break;
}
}
System.out.println("t2 结束");
}, "t2").start();
}
}
但是如上方式volatile修饰的是一个引用,但是我们操作的时候是操作的是引用里面的内容,如果把 TimeUnit.SECONDS.sleep(1);这段代码去掉可能会出现如下结果:
也可能会出现如下结果:
如上只能理解为在另一个线程sleep期间,会把引用对应的内容给写回去。
如果采用wait(),notify方式?
public class NotifyHoldingLock { //wait notify
//添加volatile,使t2能够得到通知
volatile List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
NotifyHoldingLock c = new NotifyHoldingLock();
final Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
System.out.println("t2 start");
if (c.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 end");
}
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
new Thread(() -> {
System.out.println("t1 start");
synchronized (lock) {
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
lock.notify();
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1").start();
}
}
会得出如下结果:
产生原因:notify不释放锁,线程t2等待的过程中t1执行,当执行到i=5的时候,t2执行notify方法,但是由于没有释放锁,所以线程t2会等到线程t1执行完毕之后再执行。
那么我们应该在调用notify的同时释放锁才能让我们的程序正确:
public class NotifyFreeLock {
List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
NotifyFreeLock c = new NotifyFreeLock();
final Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
System.out.println("t2 start");
if (c.size() != 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 end");
lock.notify();
}
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
new Thread(() -> {
System.out.println("t1 start");
synchronized (lock) {
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
lock.notify();
//释放锁,让t2得以执行
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1").start();
}
}
如上在t1执行notify的时候让t1执行wait()方法让t1释放锁,然后t2再获得锁执行完毕之后再调用notify方法唤醒t1执行t1的逻辑。
如果采用CountDownLatch的方式?
首先可能会想到如下写法:
在如下代码中定义一个大小为1的门闩,当lists的size等于5的时候打开门闩
public class CountDownLatch {
List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
CountDownLatch c = new CountDownLatch();
CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
System.out.println("t2启动");
if (c.size() != 5) {
try {
latch.await();
//latch.await(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("t2 结束");
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
// 打开门闩让t2得以执行
latch.countDown();
}
}
}, "t1").start();
}
}
但是会得到如下结果:
产生如上结果的原因是,当t1执行到lists的size等于5的时候确实把门闩给打开了,但是由于t1线程的时间片还没用完,就导致虽然t1打开了门闩但还没轮到t2去执行它的后续代码,就会导致如上图所示的输出结果。
如果一定要用门闩去实现如上的功能呢?应该如何去实现?
答案:用两只门闩
代码如下所示:
t1等于5的时候来个门闩,然后t2上来是先把自己给闩住,然后t1执行一个countDown先把t2的门闩给打开,t2执行完毕之后再用一个countDown把t1的门闩给打开。
public class T05_CountDownLatch {
List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
T05_CountDownLatch c = new T05_CountDownLatch();
CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch1 = new CountDownLatch(1);
new Thread(() -> {
System.out.println("t2启动");
if (c.size() != 5) {
try {
latch1.await();
//latch.await(5000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
}
System.out.println("t2 结束");
}, "t2").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
latch1.countDown();
// 打开门闩让t2得以执行
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "t1").start();
}
}
如何通过LockSupport的方式来实现?
你可能会率先想到如下这种写法:
采用LockSupport的park以及unpark方法,当t2拿到的size不等于5的时候就把t2给停在那,当t1拿到的size等于5的时候就让t1执行t2的unpark,让t2动起来。
public class LockSupport {
List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
public static void main(String[] args) {
LockSupport c = new LockSupport();
Thread t2 = new Thread(() -> {
System.out.println("t2 启动");
if (c.size() != 5) {
LockSupport.park();
}
System.out.println("t2 结束");
}, "t2");
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
new Thread(() -> {
System.out.println("t1启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
LockSupport.unpark(t2);
}
/*try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}, "t1").start();
}
}
但是得到的结果如下图所示:
这个其实和门闩产生的问题原因是一样的,那么解决方式应该采用两个LockSupport,代码如下:
public class LockSupport_WithoutSleep {
List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
static Thread t1 = null, t2 = null;
public static void main(String[] args) {
LockSupport_WithoutSleep c = new LockSupport_WithoutSleep();
t1 = new Thread(() -> {
System.out.println("t1 启动");
for (int i = 0; i < 10; i++) {
c.add(new Object());
System.out.println("add " + i);
if (c.size() == 5) {
LockSupport.unpark(t2);
LockSupport.park();
}
}
}, "t1");
t2 = new Thread(() -> {
System.out.println("t2 启动");
//也可不用判断size != 5
// if (c.size() != 5) {
LockSupport.park();
//}
System.out.println("t2 结束");
LockSupport.unpark(t1);
}, "t2");
t2.start();
t1.start();
}
}
如上代码需注意如果t1先启动的话,他会把t2先unpark,当t2执行park的时候则不需要unpark了,相当于就是标志位已经改变了。(unpark可以先于park调用,后面要停车就停不住了)
采用Semaphore如何实现?
代码如下所示:
public class Semaphore {
List lists = new ArrayList();
public void add(Object o) {
lists.add(o);
}
public int size() {
return lists.size();
}
static Thread t1 = null, t2 = null;
public static void main(String[] args) {
Semaphore c = new Semaphore();
Semaphore s = new Semaphore(1);
t1 = new Thread(() -> {
try {
s.acquire();
for (int i = 0; i < 5; i++) {
c.add(new Object());
System.out.println("add " + i);
}
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.start();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
s.acquire();
for (int i = 5; i < 10; i++) {
System.out.println(i);
}
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t2 = new Thread(() -> {
try {
s.acquire();
System.out.println("t2 启动");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2");
//t2.start();
t1.start();
}
}
面试题2
写一个固定容量同步容器,拥有put和get方法,能够支持50个生产线程以及50个消费者线程阻塞调用。
有如下两种方式:
public class MyContainer1<T> {
final private LinkedList<T> lists = new LinkedList<>();
final private int MAX = 10; //最多10个元素
private int count = 0;
public synchronized void put(T t) {
while (lists.size() == MAX) { //想想为什么用while而不是用if?
//此处while循环的作用就是为了防止线程在哪wait就在哪里被唤醒,如果一个if的话可能当前线程被唤醒时,list已经处于一个Max的状态了,这时就需要while再去做循环判断
try {
//this.notifyAll()
this.wait(); //effective java
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lists.add(t);
++count;
this.notifyAll(); //通知消费者线程进行消费
}
public synchronized T get() {
T t = null;
while (lists.size() == 0) {
try {
//this.notifyAll()
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t = lists.removeFirst();
count--;
this.notifyAll(); //通知生产者进行生产
return t;
}
public static void main(String[] args) {
MyContainer1<String> c = new MyContainer1<>();
//启动消费者线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) System.out.println(c.get());
}, "c" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 25; j++) c.put(Thread.currentThread().getName() + " " + j);
}, "p" + i).start();
}
}
}
但是如上程序有个问题就在于NotifyAll()是唤醒所有被sychronized阻塞的线程如果有多个生产者线程的话那么可能重复唤醒生产者线程,就显得不是那么友好。
应采用lock的condition来指定生产者和消费者
public class MyContainer2<T> {
final private LinkedList<T> lists = new LinkedList<>();
final private int MAX = 10; //最多10个元素
private int count = 0;
private Lock lock = new ReentrantLock();
//condition的本质就是等待队列,此处是将两个线程放在两个等待队列上面
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public void put(T t) {
lock.lock();
try {
while (lists.size() == MAX) { //想想为什么用while而不是用if?
producer.await();
}
lists.add(t);
++count;
consumer.signalAll(); //通知消费者线程进行消费
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public T get() {
T t = null;
lock.lock();
try {
while (lists.size() == 0) {
consumer.await();
}
t = lists.removeFirst();
count--;
producer.signalAll(); //通知生产者进行生产
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}
public static void main(String[] args) {
MyContainer2<String> c = new MyContainer2<>();
//启动消费者线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 5; j++) System.out.println(c.get());
}, "c" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//启动生产者线程
for (int i = 0; i < 2; i++) {
new Thread(() -> {
for (int j = 0; j < 25; j++) c.put(Thread.currentThread().getName() + " " + j);
}, "p" + i).start();
}
}
}