【Java多线程JUC入门详解02】:读写锁、阻塞队列、线程池、CountDownLatch、CyclicBarrier、Semaphore

CountDownLatch

减法计数器

package org.example.count;

import java.util.concurrent.CountDownLatch;

/**
 * @Author: sshdg
 * @Date: 2020/9/6 8:24
 */
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);//设置计数器
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"->go out");
                countDownLatch.countDown();//计数器减一
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();//等待计数器归0后再执行下面的代码
        System.out.println("close door");
    }
}

CyclicBarrier

从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。

它的作用就是会让所有线程都等待完成后才会继续下一步行动。

举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier。

构造方法

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

解析:

  • parties 是参与线程的个数
  • 第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务

重要方法

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

解析:

  • 线程调用 await() 表示自己已经到达栅栏
  • BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时
package org.example.count;

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

/**
 * @Author: sshdg
 * @Date: 2020/9/6 8:35
 */
public class CyclicBarrierTest {
    /**
     * 集齐七颗龙珠召唤神龙
     * @param args
     */
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
            System.out.println("召唤神龙成功");
        });
        for (int i = 0; i < 7; i++) {
            int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"->收集了"+temp+"颗");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

CyclicBarrier 使用场景

可以用于多线程计算数据,最后合并计算结果的场景。

CountDownLatch 区别

  • CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的
  • CountDownLatch 参与的线程的职责是不一样的,有的在倒计时,有的在等待倒计时结束。CyclicBarrier 参与的线程职责是一样的。

Semaphore

1. Semaphore 是什么?

Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。

类似抢车位一样,可以限定资源的数量来控制线程访问

2. 怎么使用 Semaphore?

2.1 构造方法
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)

解析:

  • permits 表示许可线程的数量
  • fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程
2.2 重要方法
public void acquire() throws InterruptedException
public void release()

解析:

  • acquire() 表示阻塞并获取许可
  • release() 表示释放许可
2.3 基本使用
package org.example.count;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @Author: sshdg
 * @Date: 2020/9/6 20:59
 */
public class SemaphoreTest {
    /**
     * 抢车位
     * @param args
     */
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println(Thread.currentThread().getName()+"释放车位");
                    semaphore.release();

                }
            }, String.valueOf(i)).start();
        }
    }
}

3. Semaphore 使用场景

可用于流量控制,限制最大的并发访问数。

读写锁ReadWriteLock

写入时,只允许一个线程进行写入。读取时,可以多个线程读取。

readWriteLock.writeLock().lock();

readWriteLock.readLock().lock();

使用方法和lock相同,业务代码try catch包裹起来,finally中unlock

package org.example.read_write_lock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Author: sshdg
 * @Date: 2020/9/6 21:46
 */
public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"", temp+"");
            }, String.valueOf(i)).start();
        }
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            }, String.valueOf(i)).start();
        }
    }
}
class MyCache{
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    Map<String, String> map = new HashMap<>();
    /**
     * 写入
     * @param key
     * @param value
     */
    public void put(String key, String value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName()+"写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

阻塞队列BlockingQueue

BlockingQueue的方法

BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:

抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll()take()poll(time, unit)
检查element()peek()不可用不可用

四组不同的行为方式解释:

1(异常)

如果试图的操作无法立即执行,抛一个异常。

2(特定值)

如果试图的操作无法立即执行,返回一个特定的值(常常是 true / false)。

3 (阻塞)

如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行。

4(超时)

如果试图的操作无法立即执行,该方法调用将会发生阻塞,直到能够执行,但等待时间不会超过给定值。返回一个特定值以告知该操作是否成功(典型的是 true / false)。

package org.example.blocking_queue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * @Author: sshdg
 * @Date: 2020/9/7 9:37
 */
public class BlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        System.out.println(blockingQueue.offer("1", 3L, TimeUnit.SECONDS));//true
        System.out.println(blockingQueue.offer("2", 3L, TimeUnit.SECONDS));//true
        System.out.println(blockingQueue.offer("3", 3L, TimeUnit.SECONDS));//true
        System.out.println(blockingQueue.offer("4", 3L, TimeUnit.SECONDS));//false
    }
}

同步队列SynchronousQueue

put进一个元素,必须先取出,否则无法再put

package org.example.blocking_queue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * @Author: sshdg
 * @Date: 2020/9/7 10:39
 */
public class SynchronousQueueTest {
    public static void main(String[] args) {
        BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+" put 1");
                synchronousQueue.put("1");

                System.out.println(Thread.currentThread().getName()+" put 2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName()+" put 3");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+" take "+synchronousQueue.take());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T2").start();

    }
}

线程池

阿里开发手册规定:

【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors返回的线程池对象的弊端如下:

1)FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

2)CachedThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

类似数据库连接池,池技术就是存储资源的,用的时候取、用完归还。

工具类创建的三种方法

Executors.newSingleThreadExecutor();//单个线程的线程池
Executors.newFixedThreadPool(5);//自定义大小的线程池
Executors.newCachedThreadPool();//可伸缩的,使用的多就多

使用

try {
    threadPool.execute(()->{
		//业务
    });
} catch (Exception e) {
    e.printStackTrace();
} finally {
    threadPool.shutdown();//关闭
}

但是,通过查看源码你会发现,这三种方法底层都是通过ThreadPoolExecutor创建的线程池。阿里巴巴开发手册中也强制规定了不允许使用工具类创建线程池。

接下来,我们就来看看,如何自定义一个线程池

自定义线程池

七大参数

以银行窗口为例:

设共有5个窗口(最大),日常开启2个(默认),等候区3个位置(阻塞队列),则如果两个窗口和三个等候区的位置全部满员时,开启5个窗口。若之后还有人进来,则拒绝(拒绝策略)。若超过一定时间,多出的窗口都没人使用了,则再将其关闭,恢复到默认窗口数2两个。

new ThreadPoolExecutor(2,//默认线程池大小
                       5,//最大 大小
                       5,//超时时间,就是说如果开到最大数量的线程,而超过这些时间,多出来的没人使用,则关闭多出来的。
                       TimeUnit.SECONDS,//时间单位
                       new ArrayBlockingQueue<>(3),//阻塞队列,等候区。如果需要的线程数超过等待区中的,则会开到最大的线程数。
                       Executors.defaultThreadFactory(),//线程工厂
                       new ThreadPoolExecutor.AbortPolicy()//拒绝策略:如果开到最大数量之后,阻塞队列还是满了,则之后的请求的拒绝的策略为此。
                      );

拒绝策略

//不处理任务,抛出异常
new ThreadPoolExecutor.AbortPolicy();
//哪里来的回哪去,如main线程请求的线程池,则交由main线程处理
new ThreadPoolExecutor.CallerRunsPolicy();
//不处理任务,不会抛出异常
new ThreadPoolExecutor.DiscardPolicy();
//尝试和最早的请求线程去竞争,也不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy();

最大线程数

cpu密集型

根据cpu的参数来确定。cpu是几线程,最大就设置几个

Runtime.getRuntime().availableProcessors()

通过以上代码获取cpu线程数

IO密集型

io十分占用资源,最大线程数大于你程序中十分占用资源的io的线程数即可

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值