初探:Java并发包中线程同步器

CountDownLatch

核心函数

	// 构造函数
    public CountDownLatch(int count) ;// count为内部计数
	public void await()

调用await后,该线程会被阻塞,直到count计数为0;或其他线程调用的该线程的interrupt方法,中断了该线程。

public boolean await(long timeout, TimeUnit unit)

和无差await()方法相识,但不会一直处于阻塞状态,当设置的时间到了之后,直接返回(false)。

public void countDown()

计数(count)减一,如果减一后count的值为0则,唤醒因为await方法阻塞的线程。

举例

public class CountDownLatchTest {

    private static ExecutorService executorService = Executors.newCachedThreadPool();
    public static void main(String[] args) throws Exception{

    }

    /**
     * CountDownLatch:一个线程等待其他线程运行都完后才执行
     * 生活中的例子:图书馆闭馆时,管理员需要等待所有的同学都离开后才关闭图书馆。
     */
    private static void countDownLatchTestDemo1() throws Exception {
        /* 假设图书馆今天有10个同学 */
        CountDownLatch downLatch = new CountDownLatch(10);
        for (int i = 1; i <= 10; i++) {
            String studentName = i + "同学";
            executorService.submit(() -> {
                System.out.println(studentName + "离开了图书馆");
                downLatch.countDown();
            });
        }
        /* (当前线程)管理员等待同学都离开图书馆*/
        downLatch.await();
        System.out.println("可以关门了");
    }
}

补充

CountDownLatch 与 join

  • CountDownLatch 先对于 join 而言,在使用上CountDownLatch 更加灵活,方便。
  • join 方法不好与线程池结合(线程池中我们直接是submit(Runnable接口))不好获取执行的线程,就不好使用join方法。
  • 对于多个线程同步问题,join的书写方式不太美观。例如我们需要等待3个子线程执行完后才结束main线程。
    public static void main(String[] args) throws InterruptedException {
       Thread thread1 = new Thread(() -> {
           try {
               Thread.sleep(3000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName() + "线程:执行完毕");
       });
    
       Thread thread2 = new Thread(() -> {
           System.out.println(Thread.currentThread().getName() + "线程:执行完毕");
       });
    
       Thread thread3 = new Thread(() -> {
           System.out.println(Thread.currentThread().getName() + "线程:执行完毕");
       });
    
       thread3.start();
       thread1.start();
       thread2.start();
    
       /*等待线程thread1执行完毕*/
       thread1.join();
       /*等待线程thread2执行完毕*/
       thread2.join();
       /*等待线程thread3执行完毕*/
       thread3.join();
    	// 当需要做同步处理的线程较多时,代码就会显得很‘丑’
       System.out.println("主线程执行完毕");
    }
    

CyclicBarrier

cyclicBarrier:回环屏障
由于CountDownLatch是”一次性“的,当计数器变为0后,由于CountDownLatch的await,countdown就会立即返回,失去的同步的作用。而CyclicBarrier可以理解位CountDownLatch的增强,可以多次使用。当一组线程达到一个状态全部执行后,会重置CyclicBarrier的状态(count=0)。

核心函数

public int await()

调用await后,该线程会被阻塞,直到count计数为0;或其他线程调用的该线程的interrupt方法,中断了该线程。

public boolean await(long timeout, TimeUnit unit)

和无差await()方法相识,但不会一直处于阻塞状态,当设置的时间到了之后,直接返回(false)。

举例

public class CyclicBarrierTest {

    private static ExecutorService executorService = Executors.newCachedThreadPool();

    public static void main(String[] args) {
        cyclicBarrierDemo1();
    }

    /**
     * <p>
     * 生活中的例子:公交车。
     * 记得原来有这样的公交车站:坐满就发车(没满就等),就有点符合CyclicBarrier。
     */
    public static void cyclicBarrierDemo1() {
        /* 假设公交车只有10座位 */
        final Integer num = 10;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
            System.out.println("发车啦,gogogo");
        });
        // 一共有230人 ==> 23次
        for (int i = 0; i < 230; i++) {
            executorService.submit(() -> {
                try {
                    /* 上车等待发车(座位-1) */
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

Semaphore

Semaphore( 信号量)与前面连个不太一样。Semaphore的内部的计数器是递增的(前两个都是递减的),
所以不需要知道需要同步的线程数。可以回忆一下,上面的两个举例中我们都有添加假设情况(假设图书馆今天有10个同学,假设公交车只有10座位),这也间接指明的需要同步的线程数

核心方法

  1. acquire
public void acquire()

通过 acquire() 获取一个许可(信号量资源),如果当前的许可的个数大于0,则为该线程颁发许可,且可用许可数-1。如果当前的许可的个数等于0,则阻塞(等待其他线程释放许可)。

public void acquire(int permits)

获取permits个个许可

  1. release
public void release()

释放一个许可,且可用许可数+1

public void release(int permits)

释放permits个许可,且可用许可数+permits

应用

按序打印: https://leetcode-cn.com/problems/print-in-order/
题目描述

三个不同的线程 A、B、C 将会共用一个 Foo 实例。
线程 A 将会调用 first() 方法
线程 B 将会调用 second() 方法
线程 C 将会调用 third() 方法
请设计修改程序,以确保 second() 方法在 first() 方法之后被执行,third() 方法在 second() 方法之后被执行。

方法一
volatile 方式

class Foo {
    static volatile Integer flag  = 1; 
    public Foo() {}

    public void first(Runnable printFirst) throws InterruptedException {
        while(flag!=1){}
        // printFirst.run() outputs "first". Do not change or remove this line.
        printFirst.run();
        flag=2;
    }

    public void second(Runnable printSecond) throws InterruptedException {
        while(flag!=2){ }
        // printSecond.run() outputs "second". Do not change or remove this line.
          printSecond.run();
         flag=3;
    }

    public void third(Runnable printThird) throws InterruptedException {
        while(flag!=3){}
        // printThird.run() outputs "third". Do not change or remove this line.
        printThird.run();
        flag=1;
    }
}

方式二
Semaphore方式


import java.util.concurrent.*;
class Foo {

    Semaphore firstSemaphore =  new  Semaphore(1);
    Semaphore secondSemaphore =  new  Semaphore(0);
    Semaphore thirdSemaphore =  new  Semaphore(0);
    
    public Foo() {}

    public void first(Runnable printFirst) throws InterruptedException {
       firstSemaphore.acquire(1);
        // printFirst.run() outputs "first". Do not change or remove this line.
        printFirst.run();
        secondSemaphore.release(1);
    }

    public void second(Runnable printSecond) throws InterruptedException {
         secondSemaphore.acquire(1);
        // printSecond.run() outputs "second". Do not change or remove this line.
          printSecond.run();
        thirdSemaphore.release(1);
    }

    public void third(Runnable printThird) throws InterruptedException {
        thirdSemaphore.acquire(1);
        // printThird.run() outputs "third". Do not change or remove this line.
        printThird.run();
        firstSemaphore.release(1);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值