1、CountDownLatch 倒数门栓
代码解释:
门栓计数100,每一个线程结束的时候我让latch.countDown(),然后所有线程start(),再latch.await()
最后结束。
作用:
latch.await() 的作用就相当于门卫,每个线程执行完成都会减一,直到=0时,门卫就会开门。
然后执行>后面的代码。
相比join更为灵活
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
final CountDownLatch latch = new CountDownLatch(100);
final Map<Long, Integer> map = new ConcurrentHashMap();
for (int i = 0; i < 100; i++) {
new Thread(() -> {
for (int s = 0; s < 2000; s++) {
long snowID = SnowIdUtils.uniqueLong();
log.info("生成雪花ID={}",snowID);
Integer put = map.put(snowID, 1);
if (put != null) {
throw new RuntimeException("主键重复");
}
}
latch.countDown();
}).start();
}
latch.await();
log.info("生成20万条雪花ID总用时={}", System.currentTimeMillis() - start);
}
join 用法示例
代码解释:
启动10个线程去累加一万次。正确结果为10万。
count 是使用CAS原子类的计数方法
c是传统的synchronized锁的计数方法
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class Test {
AtomicInteger count = new AtomicInteger(0);
int c = 0;
void m(){
for(int i=0;i<10000;i++){
count.incrementAndGet();
synchronized (this){
c++;
}
}
}
public static void main(String[] args) {
Test test = new Test();
List<Thread> threadList = new ArrayList<>();
for(int i=0;i<10;i++){
threadList.add(new Thread(test::m,"thread-"+i));
}
threadList.forEach((o) -> o.start());
threadList.forEach((o) -> {
try{
o.join();
}catch (Exception e){
e.printStackTrace();
}
});
System.out.println(test.count);
System.out.println(test.c);
}
}
2、CyclicBarrier 循环栅栏
什么时候人满了就把栅栏推到,让、把人放出去,然后栅栏又立起来继续循环。。。
代码解释:
两个参数,第二个参数可以不传也可以,就是满了什么也不做。
第一个参数是20,满了之后就帮我调用第二个参数指定的动作,就是一个Runnable对象。等够20人
则执行barrier.await()
CyclicBarrier barrier = new CyclicBarrier(20,()-> 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();
}
3、Phaser 阶段
它更像是结合了 CountDownLatch CyclicBarrier 。Phaser 是按照不同的阶段来对线程进行执行。
代码解释:
模拟结婚的场景,有好多人参加,因此写了一个Person类,是一个Runnable可以new处理,扔给Thread去执行,
模拟我们每个人要做一些操作,有这些方法:arrive()到达,eat()吃,leave()离开,hug()拥抱。
作为一个婚礼来说它会分为好几个阶段,第一阶段大家都得到齐了,第二阶段大家开始吃饭,三阶段大家离开,
第四个阶段新郎新娘入洞房。每个人都有这几个方法,在方法的实现里头就简单的睡1000毫秒。
再看主程序,一共有5个人参加婚礼了,接下来新郎新娘参加婚礼,一共7个人。它一start就好调用我们得run()
方法,它会挨着牌的调用每一个阶段的方法。那好,我们在每一个阶段是不是得控制人数,第一个阶段得要人到期
了才能开始,二阶段所有人吃饭,三阶段所有人都离开,但是,到了四阶段进入洞房的时候就只能是新郎新娘参与了
所以,要模拟一个程序就要把整个过程很好几个阶段,而且每个阶段必须要等这些线程给我刚完事了你才能进入
下一个阶段。
怎么模拟阶段,首先定义了一个phaser,这个phaser是从Phaser这个类继承,重写onAdvance方法,前进,
新城抵达这个栅栏的时候,所有的线程都满足了这个第一个栅栏的条件了onAdvance会被自动调用,
目前我们有好几个阶段,这个阶段是被写死的,必须是数字0开始,onAdvance会传来两个参数phaser
是第几个阶段registeredParties是目前这个阶段有几个人参加,每一个阶段都有一个打印,返回false,
一直到最后一个阶段返回true,所有线程结束,整个栅栏组,Phaser栅栏组就结束了。
怎么才能让我的线程再一个栅栏面前给停住呢,就是调用phaser.arriveAndAwaitAdvance()这个方法,
这个方法的意思是到达等待继续往前走,知道新郎新娘入洞房,其他人不再参与,
调用phaser.arriveAndDeregister()这个方法。还有可以调用方法phaser.register()往上加,
不仅可以控制栅栏上的个数还可以控制栅栏上的等待数量,这个就叫做phaser。这个主要拓宽知识面用的
import java.util.Random;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
public class TestPhaser {
static Random r = new Random();
static MarriagePhaser phaser = new MarriagePhaser();
static void milliSleep(int milli){
try {
TimeUnit.MICROSECONDS.sleep(milli);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
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();
}
static class MarriagePhaser extends Phaser{
@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();
}
public void hug(){
if(name.equals("新郎")||name.equals("新娘")){
milliSleep(r.nextInt(1000));
System.out.printf("%s 洞房!\n",name);
phaser.arriveAndAwaitAdvance();
}else{
phaser.arriveAndDeregister();
}
}
@Override
public void run() {
arrive();
eat();
leave();
hug();
}
}
}
4、ReadWriteLock 读写锁
概念其实就是共享锁和排他锁,读锁就是共享锁,写锁就排他锁。
代码解释:
例如获取公司架构,读多写少。要求他不产生数据不一致的情况。可以做成这种锁,当读线程上来的时候加一把锁
是允许其他读线程可以读,写线程来了我不给它,你先别写,等我读完了你在写。读线程进来的时候我们大家
一起读,因为你不改原来的内容,写线程上来把整个线程全锁定,你先不要读,等我写完你再读。
读写锁怎么用,我们这有两个方法,read()读一个数据,write()写一个数据。read这个数据的是否我们需要
你往里面传一把锁,这个传那把锁你自己定,我们可以划船自己定义的全部都是排他锁,也可以传读写锁里面的
读锁或写锁。write的时候也需要传把锁,同事需要你传一个新值完成后解锁unlock。
我们现在的问题是往里传这个lock有两种方法,第一种直接new ReentrantLock()传进去,分析下这种方法,
主程序定义了一个Runnable对象,第一个是调用read()方法,第二个是调用write()方法同时往里头扔一个
随机值,然后起了18个读线程,起了2个写线程,这个两个我要想执行完的话,我现在传的是一个ReentranLock,
这把锁上了之后没有其他任何人可以拿到这把锁,而这里面每一个线程执行都需要1秒钟,在这种情况下你必须
得等20秒才能干完这事儿;
第二种,我们换了锁 new ReentrantReadWriteLock()是ReadWriteLock的一种实现,
在这种思想里头我又分出两把锁来,读锁我就拿到了。
通过readWriteLock.writeLock()拿到writeLock对象。
这两把锁在我读的时候登进去,因此,读线程是可以一起读的,也就是说这18个线程可以一秒钟完成工作结束,
所以使用读写锁效率会大大的提升。
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriterLock {
static Lock lock = new ReentrantLock();
private static int value;
static ReentrantReadWriteLock readWriterLock = new ReentrantReadWriteLock();
static Lock readLock = readWriterLock.readLock();
static Lock writeLock = readWriterLock.writeLock();
public static void read(Lock lock){
try{
lock.lock();
Thread.sleep(1000);
System.out.println("read over!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void write(Lock lock,int v){
try{
lock.lock();
Thread.sleep(1000);
value = v;
System.out.println("write over!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
Runnable readR = ()->read(readLock);
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();
}
}
5、Semaphore 信号灯
可以往里面传一个数,permits是允许的数量,你可以想着有几盏信号灯,一个灯你们闪着数字表示到底
允许几个来参考我这个信号灯。
s.acquire()这个方法叫阻塞方法,阻塞方法的意思是说我大概acquire不到的话我就停在这,
acquire的意思就是得到。如果我Semaphore s = new Semaphore(1)写的是1,我取一下,
acquire一下他就变成0,当变成0之后别人是acquire不到的,然后继续执行,线程结束之后注意
要s.release(),执行完该方法就会把0变成1,还原化。
Semaphore的含义就是限流,比如说你在买票,Semaphore写5就是只能有5个人可以同时买票。
acquire的意思叫获得这把锁,线程如果想要继续往下执行,必须得从Semaphore里面获得一个许可,
他一共有5个许可用到0你就得给我等着。
例如有一个8条车道的机动车,这里只有2个收费站,到这儿,谁acquire得到其中一个谁执行,
默认Semaphore是非公平的,new Semaphore(2,true)第二个值传true才是设置公平。
公平这个事儿是有一堆队列在哪儿等,大家伙过来排队,用这个车道和收费站来举例,
就是我们有4辆车都在等着进入一个车道,然后买再来一辆新的时候,他不会超到前面去,
要在后面排队这叫公平。所以说内部是有队列的。从上面的CountDownLatch 、CyclicBarrier 、Phaser、
ReadWriteLock、Semaphore、Exchanger都是用用一个队列,同一个类来实现的,这个类叫AQS
import java.util.concurrent.Semaphore;
public class TestSemaphore {
public static void main(String[] args) {
Semaphore s = new Semaphore(2,true);
new Thread(()->{
try{
s.acquire();
System.out.println("T1 running...");
Thread.sleep(1000);
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(1000);
System.out.println("T2 running...");
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
6、Exchanger 交换机
Exchanger 主要是拓展知识面用的,两人之间互相交换数据用的。
代码解释:
第一个相册有一个成员变量s,然后exchanger.exchange(s),第二个也是这样,t1线程名字叫T1,
第二个线程名字叫T2。到最后,打印出来你会发现他们两交换了一下。线程间通信的方式非常多,
这只是其中一种,就是线程之间交换数据用的。
exchanger可以想象是一个容器,容器中有2个值,两个线程,有两个格子的未知,第一个相册执行到
exchanger.exchange的时候,阻塞,但是要注意我这个exchange方法的是否是往里面扔了一个值,
你可以认为把T1扔到了第一个格子,然后第二个线程开始执行,也执行到这句话了,exchange,
他把自己的这个值T2扔到第二个格子里,接下来这两个值交换了一个下,T1扔给T2,T2扔给T1。
两个线程继续往前跑。exchange只能是两个线程之间,交换这个东西只能两两进行。
exchange的使用场景,比如在游戏中两个人装备交换。
import java.util.concurrent.Exchanger;
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();
}
}