14

线程的三个同步器

 4 years ago
source link: http://www.cnblogs.com/Howlet/p/12685072.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

不知不觉就遇到了线程同步器问题,查了资料写下了总结

1. CountDownLatch

日常中会有开启多个线程去并发执行任务,而 主线程要等所有子线程执行完之后才能运行的需求 。之前我们是使用Thread.join方法来实现的,过程如下:

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

    Thread t1 = new Thread( () -> {
        try {
            Thread.sleep(1000);
            System.out.println("t1 over");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    Thread t2 = new Thread( () -> {
        try {
            Thread.sleep(2000);
            System.out.println("t2 over");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });

    t1.start();
    t2.start();
    
    t1.join();
    t2.join();

    System.out.println("mian over");
}
t1 over
t2 over
mian over

join()方法不够灵活,现在JDK提供了 CountDownLatch 这个类来实现所需功能

private static CountDownLatch countDownLatch = new CountDownLatch(2);

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

    ExecutorService t = Executors.newCachedThreadPool();

    Runnable r1 = () -> {
        try {
            System.out.println("r1 sleep");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            countDownLatch.countDown();
        }
    };

    Runnable r2 = () -> {
        try {
            System.out.println("r2 sleep");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            countDownLatch.countDown();
        }
    };

    t.submit(r1);
    t.submit(r2);

    System.out.println("main wait");
    countDownLatch.await();
    System.out.println("main over");
}
main wait
r1 sleep
r2 sleep
main over

CountDownLatch流程:

  • 新建CountDownLatch实例,传入计数器次数
  • 主线程调用CountDownLatch.await()方法后会被阻塞
  • 子线程中在某处调用CountDownLatch.countDown()方法可使内部计数器减1
  • 当计数器变成0时,主线程的await()方法才会返回

CountDownLatch优点:

  • 调用Thread.join()调用线程会被阻塞至子线程运行完毕,而CountDownLatch.countDown()可在线程运行中执行
  • 使用线程池时是提交任务的,而没有接触到线程无法使用线程方法,那么countDown()可加在Runnable中执行

CountDownLatch原理:

内部维护了一个计数器,当计数器为0就放行,源码就不放了,熟悉AQS的同学想想就知道怎么回事

  • 继承了AQS,其实就是用AQS的state来表示计数器
  • await()方法内部有acquireSharedInterruptibly(),后者调用了重写tryaquireShared()其实就是判断计数器是否为0,不为0则阻塞进AQS队列
  • countDown()方法内部有releaseShared(),后者调用了重写tryReleaseShared()计数器减一,若为0,则唤醒阻塞线程

2. CyclicBarrier

满足多个线程都到达同一个位置后才全部开始运行的需求。CountDownLatch是一次性使用的,计数器为0后再次调用会直接返回,此时升级版的CyclicBarrier来了,其一可以满足计数器重置功能,且二还可以让一组线程达到一个状态后再全部同时执行

场景要求:假设一个任务分为3个阶段,每个线程要串行地从低阶段执行到高阶段

private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, 
                        () -> System.out.println("一个阶段完成"));

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

    ExecutorService service = Executors.newCachedThreadPool();

    Runnable r1 = () -> {
        try {
            System.out.println(Thread.currentThread() + "Step1");
            cyclicBarrier.await();

            System.out.println(Thread.currentThread() + "Step2");
            cyclicBarrier.await();

            System.out.println(Thread.currentThread() + "Step3");

        } catch (Exception e) {
            e.printStackTrace();
        }
    };

    Runnable r2 = () -> {
        try {
            System.out.println(Thread.currentThread() + "Step1");
            cyclicBarrier.await();

            System.out.println(Thread.currentThread() + "Step2");
            cyclicBarrier.await();

            System.out.println(Thread.currentThread() + "Step3");

        } catch (Exception e) {
            e.printStackTrace();
        }
    };

    service.submit(r1);
    service.submit(r2);

    service.shutdown();
}
Thread[pool-1-thread-1,5,main]Step1
Thread[pool-1-thread-2,5,main]Step1
一个阶段完成
Thread[pool-1-thread-1,5,main]Step2
Thread[pool-1-thread-2,5,main]Step2
一个阶段完成
Thread[pool-1-thread-1,5,main]Step3
Thread[pool-1-thread-2,5,main]Step3

CyclicBarrier的流程

  • 和上面差不多就不一一解释了
  • CyclicBarrier的构造方法中,第一个参数为计数器次数,第二个为阶段结束后要执行的方法

CyclicBarrier的原理

  • 基于独占锁,底层是AQS实现,独占锁可以原子性改变计数器,以及条件队列阻塞线程来实现线程同步
  • 内部有parties和count变量,实现重置功能
  • await()方法内调用dowait()方法
    • 获取锁更新次数减一
    • 没有为0,阻塞当前线程加入条件队列
    • 为0执行屏蔽点任务,然后唤醒条件队列的全部线程

3. Semaphore

不同与前两者,Semaphore信号量内部计数器是递增的,在需要同步的地方调用acquire指定需要同步的个数即可

private static Semaphore semaphore = new Semaphore(0);

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

    ExecutorService service = Executors.newCachedThreadPool();

    Runnable r1 = () -> {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread() + "over");
        semaphore.release();;
    };

    Runnable r2 = () -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread() + "over");
        semaphore.release();
    };

    service.submit(r1);
    service.submit(r2);

    semaphore.acquire(2);
    System.out.println("All child thread over");

    service.shutdown();
}

Semaphore的流程

  • Semaphore的构造函数传参复制当前计数器的值
  • 每个线程内部调用release()即计数器加1
  • 主线程调用acquire()方法传参为2 ,会被阻塞至计数器到达2

Semaphore的原理

  • 底层还是使用AQS,提供了公平与非公平,也是用state表示次数
  • acquire()方法获取一个信号量,并且state减一
    • 若为0,直接返回
    • 不为0当前线程会被加入AQS阻塞队列
  • release()方法,把当前Semaphore的信号量加1,然后会选择一个信号量满足的线程进行激活
  • 内部还实现了公平与非公平策略

Recommend

  • 99
    • jesuslove.github.io 5 years ago
    • Cache

    iOS开发进阶:线程同步技术-锁

    在iOS多线程中,经常会出现资源竞争和死锁的问题。本节将学习iOS中不同的锁。 线程同步方案 常见的两个问题:多线程买票...

  • 68

    乐观锁、悲观锁、公平锁、自旋锁、偏向锁、轻量级锁、重量级锁、锁膨胀…难理解?不存的!来,话不多说,带你飙车。 上一篇介绍了线程池的使用,在享受线程池带给我们的性能优势之外,似乎也带来了另一个问题:线程安全的问题。

  • 47

  • 40

    一、概述 本文手写两个线程同步机制应用的实例,分别是负载均衡和单例模式,这样在了解线程的同时也能了解负载均衡和单例模式,一...

  • 42

    引言:线程之间经常需要协同工作,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直到该线程完成对数据的操作。这些技术包括临界区(CriticalSection),互斥量(Mutex),信号量(Semaphore),事件Event等。Eventthreading库中的event对象通过使...

  • 47
    • www.cocoachina.com 5 years ago
    • Cache

    iOS源码解析:多线程 线程同步

    多线程的安全隐患 在使用多线程的过程中,一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,同一个变量,同一个对象,同一个文件。试想一下,三个线程同时向一个文件写东西,那势必会造成混乱。 ...

  • 53
    • www.tuicool.com 5 years ago
    • Cache

    python多线程同步实例分析

    进程之间通信与线程同步是一个历久弥新的话题,对编程稍有了解应该都知道,但是细说又说不清。一方面除了工作中可能用的比较少,另一方面就是这些概念牵涉到的东西比较多,而且相对较深。网络编程,服务端编程,并发应用等都会涉及到。其...

  • 31

    前几天一位朋友去面试,面试官问了他同步,异步,多线程之间是什么关系,异步比同步高效在哪?多线程比单线程高效在哪?由于回答的不好,让我帮他捋一下,其实回答这个问题不难,难就难在只对别人说理论,而没有现杀的例子。 一...

  • 30
    • segmentfault.com 3 years ago
    • Cache

    Linux系统编程 —线程同步概念

    同步概念 同步,指对在一个系统中所发生的事件之间进行协调,在时间上出现一致性与统一化的现象。 但是,对于不同行业,对于同步的理解略有不同。比如:设备同步,是指在两个设备之间规定一个共同的时间参考;数据库同...

  • 8
    • liujiacai.net 3 years ago
    • Cache

    Java 线程同步原理探析

    Java 线程同步原理探析 2018-12-29 编程语言 约 5225 字 预计...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK