CountDownLatch和CyclicBarrier 专题

 

4、Runnable接口和Callable接口的区别

有点深的问题了,也看出一个Java程序员学习知识的广度。

Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?
无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。

5、CyclicBarrier和CountDownLatch的区别

两个看上去有点像的类,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:

1)CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行。

2)CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务。

3) CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了。

6、volatile关键字的作用

一个非常重要的问题,是每个学习、应用多线程的Java程序员都必须掌握的。理解volatile关键字的作用的前提是要理解Java内存模型,这里就不讲Java内存模型了,可以参见第31点,volatile关键字的作用主要有两个:

1)多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据。

2)代码底层执行不像我们看到的高级语言----Java程序这么简单,它的执行是Java代码-->字节码-->根据字节码执行对应的C/C++代码-->C/C++代码被编译成汇编语言-->和硬件电路交互,
现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率。

从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger,更多详情请点击这里进行学习。

7、什么是线程安全

又是一个理论的问题,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。

这个问题有值得一提的地方,就是线程安全也是有几个级别的:

1)不可变

像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用

2)绝对线程安全

不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet

3)相对线程安全

相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个Vector、
有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。

4)线程非安全

这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程非安全的类,点击这里了解为什么不安全。

8、Java中如何获取到线程dump文件

死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:

1)获取到线程的pid,可以通过使用jps命令,在Linux环境下还可以使用ps -ef | grep java

2)打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid

另外提一点,Thread类提供了一个getStackTrace()方法也可以用于获取线程堆栈。这是一个实例方法,因此此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈。

https://mp.weixin.qq.com/s/MjOq3qO9tqxhHX7W0v6ZPw

 

javadoc里面的描述是这样的。

CountDownLatch: A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.(其它线程完成一些操作,但不等于这些线程要结束,也要不结束)

CyclicBarrier : A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.

可能是我的英语不够好吧, 我感觉从这个javadoc里面要准确理解他们的差异还是不容易的。
我的理解是

CountDownLatch: 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
CyclicBarrier: N个线程相互等待,只要有任何一个线程没有完成,其它的N-1个线程都必须等待。
这样应该就清楚一点了,
对于CountDownLatch来说,重点是那个“一个线程, 是它在等待, 而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止

而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待。

 

CountDownLatch 是计数器, 线程完成一个就记一个, 就像 报数一样, 只不过是递减的.

而CyclicBarrier更像一个水闸, 线程执行就想水流, 在水闸处都会堵住, 等到水满(线程到齐)了, 才开始泄流.

在网上看到很多人对于CountDownLatch和CyclicBarrier的区别简单理解为CountDownLatch是一次性的(因为整个流程已经完成了,不需要再来一次,所以一次性),而CyclicBarrier在调用reset之后还可以继续使用。那如果只是这么简单的话,我觉得CyclicBarrier简单命名为ResetableCountDownLatch好了,显然不是的。
上面是从设计目的去看这两个类。

http://blog.csdn.net/kjfcpua/article/details/7300286

 

用法:

一、CountDownLatch用法

  CountDownLatch类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

  CountDownLatch类只提供了一个构造器:

public CountDownLatch(int count) { }; //参数count为计数值

  然后下面这3个方法是CountDownLatch类中最重要的方法:

public void await() throws InterruptedException { };
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { }; //将count值减1

 

二、CyclicBarrier用法

  通过它可以实现让一组线程等待至某个状态之后再全部同时执行,并且当所有等待线程都被释放以后,CyclicBarrier还可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。

  CyclicBarrier类位于java.util.concurrent包下,CyclicBarrier提供2个构造器:

public CyclicBarrier(int parties, Runnable barrierAction) {
}
 
public CyclicBarrier(int parties) {
}

  参数parties指定让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。

  然后CyclicBarrier中最重要的方法就是await方法,它有2个重载版本:

public int await() throws InterruptedException, BrokenBarrierException { };
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };

  第一个版本比较常用,用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;

  第二个版本是让这些线程等待至一定的时间,如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。

http://www.cnblogs.com/codeOfLife/p/5687691.html

 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

public class CyclicBarrierDemo {
    private static final Logger LOGGER = LoggerFactory.getLogger(CyclicBarrierDemo.class);

    public static void main(String[] args) throws InterruptedException {

        int parties = 4;
        int count = parties + 1;
        CountDownLatch startGate = new CountDownLatch(count);
        CyclicBarrier barrier = new CyclicBarrier(parties);
        for (int i = 0; i < parties; i++) {
            Thread thread = new Thread(new Writer(barrier, startGate), "demo" + i);
            thread.start();
        }

        LOGGER.info(" ");
        int timeout = 6;
        LOGGER.info("主线程 还需要再阻塞{}s",timeout);
        new Thread(() -> {
            try {
                LOGGER.info("现在开始{}s等待。 CountDownLatch才允许主线程结束阻塞状态",timeout);
                TimeUnit.SECONDS.sleep(timeout);
                startGate.countDown();
                LOGGER.info("{}s等待结束.主线程可以往下走了",timeout);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        startGate.await();//当前线程(main线程)被阻塞,直到startGate的countDown方法的调用次数和设定的值 相同,其它的不要求

        LOGGER.info("====不管Writer线程是否完成,只要countDown的调用次数够了,CountDownLatch就会解除主线程的阻塞状态====");
        LOGGER.info(" ");
        TimeUnit.SECONDS.sleep(10);
        LOGGER.info(" ");
        LOGGER.info("======================CyclicBarrier的可重用性开始======================");
        LOGGER.info(" ");
        int lessParties = parties - 1;
        for (int i = 0; i < lessParties; i++) {
            Thread thread = new Thread(new Writer(barrier, startGate), "damo" + (i + parties));
            thread.start();
        }
        startGate.await();//不会阻塞主线程运行,但write线程会执行不完
        LOGGER.info("目前只调用{}次countDown,虽然小于设置的count {},但仍然不会阻塞主线程",lessParties,count);
        LOGGER.info("CyclicBarrier仍然需要{}个线程都至barrier状态,否则相关的Write线程都需要阻塞。目前只有{}个parties。" +
                "\n 因此所有的Writer线程会一起阻塞下去forever until server shutdown",parties,lessParties);
    }

    static class Writer implements Runnable {
        private CyclicBarrier cyclicBarrier;
        private CountDownLatch startGate;

        Writer(CyclicBarrier cyclicBarrier, CountDownLatch startGate) {
            this.cyclicBarrier = cyclicBarrier;
            this.startGate = startGate;
        }

        @Override
        public void run() {
            try {
                startGate.countDown();
                LOGGER.info("线程" + Thread.currentThread().getName() + "cyclicBarrier.await()前执行,等待其他线程写入完毕");
                cyclicBarrier.await();
                TimeUnit.SECONDS.sleep(10);//sleep一会。这样日志中可以很好的看到cyclicBarrier.await()的可重用性r
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            LOGGER.info(Thread.currentThread().getName() + "cyclicBarrier.await()后执行......");
        }
    }

}

 

Output:

22:15:31.632 [demo0] INFO CyclicBarrierDemo - 线程demo0cyclicBarrier.await()前执行,等待其他线程写入完毕
22:15:31.632 [demo3] INFO CyclicBarrierDemo - 线程demo3cyclicBarrier.await()前执行,等待其他线程写入完毕
22:15:31.632 [demo2] INFO CyclicBarrierDemo - 线程demo2cyclicBarrier.await()前执行,等待其他线程写入完毕
22:15:31.632 [demo1] INFO CyclicBarrierDemo - 线程demo1cyclicBarrier.await()前执行,等待其他线程写入完毕
22:15:31.632 [main] INFO CyclicBarrierDemo -  
22:15:31.637 [main] INFO CyclicBarrierDemo - 主线程 还需要再阻塞6s
22:15:31.763 [Thread-0] INFO CyclicBarrierDemo - 现在开始6s等待。 CountDownLatch才允许主线程结束阻塞状态
22:15:37.765 [main] INFO CyclicBarrierDemo - ====不管Writer线程是否完成,只要countDown的调用次数够了,CountDownLatch就会解除主线程的阻塞状态====
22:15:37.765 [main] INFO CyclicBarrierDemo -  
22:15:37.765 [Thread-0] INFO CyclicBarrierDemo - 6s等待结束.主线程可以往下走了
22:15:41.638 [demo3] INFO CyclicBarrierDemo - demo3cyclicBarrier.await()后执行......
22:15:41.638 [demo2] INFO CyclicBarrierDemo - demo2cyclicBarrier.await()后执行......
22:15:41.638 [demo0] INFO CyclicBarrierDemo - demo0cyclicBarrier.await()后执行......
22:15:41.638 [demo1] INFO CyclicBarrierDemo - demo1cyclicBarrier.await()后执行......
22:15:47.765 [main] INFO CyclicBarrierDemo -  
22:15:47.765 [main] INFO CyclicBarrierDemo - CyclicBarrier的可重用性开始:
22:15:47.765 [main] INFO CyclicBarrierDemo -  
22:15:47.765 [main] INFO CyclicBarrierDemo - 目前只调用3次countDown,虽然小于设置的count 5,但仍然不会阻塞主线程
22:15:47.765 [main] INFO CyclicBarrierDemo - 但CyclicBarrier仍然需要4个线程都至barrier状态。目前只有3个parties。
 因此所有的Writer线程会一起阻塞下去forever until server shutdown
22:15:47.765 [damo4] INFO CyclicBarrierDemo - 线程damo4cyclicBarrier.await()前执行,等待其他线程写入完毕
22:15:47.766 [damo5] INFO CyclicBarrierDemo - 线程damo5cyclicBarrier.await()前执行,等待其他线程写入完毕
22:15:47.766 [damo6] INFO CyclicBarrierDemo - 线程damo6cyclicBarrier.await()前执行,等待其他线程写入完毕

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值