Java - JUC(java.util.concurrent)包详解,其下的锁、安全集合类、线程池相关、线程创建相关和线程辅助类、阻塞队列

本文主要是介绍Java - JUC(java.util.concurrent)包详解,其下的锁、安全集合类、线程池相关、线程创建相关和线程辅助类、阻塞队列,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

JUC是什么?

JUC是java.util.concurrent包的简称,在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题


Java中线程有六个状态

java.lang.Thread.State

public enum State {// 新生NEW,// 运行RUNNABLE,// 阻塞BLOCKED,// 等待WAITING,//超时等待TIMED_WAITING,//终止TERMINATED;}


JUC的结构 

tools(工具类):又叫信号量三组工具类,包含有

CountDownLatch(闭锁) 是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待

CyclicBarrier(栅栏) 之所以叫barrier,是因为是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 ,并且在释放等待线程后可以重用。

Semaphore(信号量) 是一个计数信号量,它的本质是一个“共享锁“。信号量维护了一个信号量许可集。线程可以通过调用 acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

executor(执行者):是Java里面线程池的顶级接口,但它只是一个执行线程的工具,真正的线程池接口是ExecutorService,里面包含的类有:

ScheduledExecutorService 解决那些需要任务重复执行的问题

ScheduledThreadPoolExecutor 周期性任务调度的类实现

atomic(原子性包):是JDK提供的一组原子操作类

包含有AtomicBoolean、AtomicInteger、AtomicIntegerArray等原子变量类,他们的实现原理大多是持有它们各自的对应的类型变量value,而且被volatile关键字修饰了。这样来保证每次一个线程要使用它都会拿到最新的值。

locks(锁包):是JDK提供的锁机制,相比synchronized关键字来进行同步锁,功能更加强大,它为锁提供了一个框架,该框架允许更灵活地使用锁包含的实现类有:

ReentrantLock 它是独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。

ReentrantReadWriteLock 它包括子类ReadLock和WriteLock。ReadLock是共享锁,而WriteLock是独占锁。

LockSupport 它具备阻塞线程和解除阻塞线程的功能,并且不会引发死锁。

collections(集合类):主要是提供线程安全的集合, 比如:

ArrayList对应的高并发类是CopyOnWriteArrayList,

HashSet对应的高并发类是 CopyOnWriteArraySet,

HashMap对应的高并发类是ConcurrentHashMap等等


synchronized和JUC的Lock

synchronized和lock锁的区别

synchronized内置的java关键字,Lock是一个java类
synchronized无法判断获取锁的状态, Lock可以判断是否获取到了锁
synchronized会自动释放锁,Lock必须要手动释放锁!如果不是释放锁,会产生死锁
synchronized 线程1(获得锁,阻塞),线程2(等待); Lock锁就不一定会等待下去
synchronized 可重入锁,不可以中断的,非公平的; Lock锁,可重入的,可以判断锁,非公平(可自己设置);
synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码


reentrantLock

ReentrantLock 和 synchronized 的区别:

🍃1.synchronized 使用时不需要手动释放锁. ReentrantLock 使用时需要手动释放. 使用起来更灵活, 但是也容易遗漏 unlock。

🍃2.synchronized只是非公平锁,ReentranLock 提供了公平锁和非公平锁两种实现,可以通过构造方法来切换。

🍃3.ReentrantLock 还提供了一个特殊的加锁操作-- tryLock()。默认的 lock() 加锁失败就阻塞,而 tryLock() 加锁失败,则不阻塞,继续往下执行,并且返回 false。除了立即失败之外,tryLock() 还能设定一定的等待时间。

🍃4.ReentrantLock 提供了更强大的等待/唤醒 机制。 synchronized 搭配的是 Object 类的 wait,notify,只能随机唤醒其中一个线程;ReentrantLock 搭配了 Condition 类来实现等待唤醒,可以做到能随机唤醒一个,也能指定线程唤醒。

构造方法:

  	public ReentrantLock() {sync = new NonfairSync(); //无参默认非公平锁}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();//传参为true为公平锁}
public class SaleTicketDemo {public static void main(String[] args) {Ticket ticket = new Ticket();new Thread(()->{for(int i = 0; i < 40; i++) ticket.sale();}, "a").start();new Thread(()->{for(int i = 0; i < 40; i++) ticket.sale();}, "b").start();new Thread(()->{for(int i = 0; i < 40; i++) ticket.sale();}, "c").start();}
}class Ticket {private int ticketNum = 30;private Lock lock = new ReentrantLock();public void sale() {lock.lock();try {if (this.ticketNum > 0) {System.out.println(Thread.currentThread().getName() + "购得第" + ticketNum-- + "张票, 剩余" + ticketNum + "张票");}//增加错误的发生几率Thread.sleep(10);} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}}


Condition

精准的通知和唤醒线程
Condition是个接口,基本的方法就是await()和signal()方法;
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
Conditon中的await()对应Object的wait();

Condition中的signal()对应Object的notify();

Condition中的signalAll()对应Object的notifyAll()。

对比项Object Monitor MethodsCondition
前置条件获取对象的锁调用Lock.lock()获取锁
调用Lock.newCondition() 获取 Condition 对象
调用方式直接调用
如:object.wait0
直接调用
如:condition.await()
等待队列个数一个多个
当前线程释放锁并进入等待状态支持支持
当前线程释放锁并进入等待状态,在
等待状态中不响应中断
不支持支持
当前线程释放锁并进入超时等待状态支持支持
当前线程释放锁并进入等待状态到将
来的某个时间
不支持支持
唤醒等待队列中的一个线程支持支持
唤醒等持队列中的全部线程支持

 Condition常见例子(生产者消费者模式)

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class PC {public static void main(String[] args) {a a = new a();new Thread(()->{for (int i =0;i<10;i++){a.increment();}},"A").start();new Thread(()->{for (int i =0;i<10;i++){a.decrease();}},"B").start();}}
class  a{public int nummber=0;Lock lock = new ReentrantLock();Condition condition = lock.newCondition();public   void  increment(){lock.lock();try {while(nummber!=0){condition.await();}nummber++;System.out.println(Thread.currentThread().getName()+">>"+nummber);condition.signalAll();}catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public  void decrease(){lock.lock();try {while(nummber!=1){condition.await();}nummber--;System.out.println(Thread.currentThread().getName()+">>"+nummber);condition.signalAll();}catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
}


JUC下的读写锁ReentrantReadWriteLock

ReadWriteLock

package com.czp.lock;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;/*** 独占锁(写锁) 一次只能由一个线程占有* 共享锁(读锁) 一次可以有多个线程占有* readWriteLock* 读-读 可以共存* 读-写  不能共存* 写-写 不能共存*/
public class ReadWriteLock {public static void main(String[] args) {MyCacheLock myCache = new MyCacheLock();//写入操作for (int i = 0; i < 6; i++) {int temp = i;new Thread(() -> {myCache.put(temp + "", temp + "");}, String.valueOf(i)).start();}//读取操作for (int i = 0; i < 6; i++) {int temp = i;new Thread(() -> {myCache.get(temp + "");}, String.valueOf(i)).start();}}
}class MyCacheLock {private volatile Map<String, Object> map = new HashMap<>();//读写锁private java.util.concurrent.locks.ReadWriteLock lock = new ReentrantReadWriteLock();// 存,写入的时候只有一个人操作public Object get(String key) {lock.readLock().lock();Object o = null;try {System.out.println(Thread.currentThread().getName() + "读取");try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}o = map.get(key);System.out.println(Thread.currentThread().getName() + "读取ok" + o);} catch (Exception e) {e.printStackTrace();} finally {lock.readLock().unlock();}return o;}public void put(String key, Object value) {lock.writeLock().lock();try {System.out.println(Thread.currentThread().getName() + "写入" + key);map.put(key, value);System.out.println(Thread.currentThread().getName() + "写入完毕");} catch (Exception e) {e.printStackTrace();} finally {lock.writeLock().unlock();}}}class MyCache {private volatile Map<String, Object> map = new HashMap<>();public Object get(String key) {System.out.println(Thread.currentThread().getName() + "读取");Object o = map.get(key);System.out.println(Thread.currentThread().getName() + "读取ok" + o);return o;}public void put(String key, Object value) {System.out.println(Thread.currentThread().getName() + "写入" + key);map.put(key, value);System.out.println(Thread.currentThread().getName() + "写入完毕");}}


不安全的集合类和JUC的collections

list 不安全-CpoyOnWriteArrayList

CpoyOnWriteArrayList(写时复制)。当多个线程读的时候,是线程安全的,不需要加锁;当多个线程涉及到修改的时候,先将当前容器进行Copy, 复制出一个新的容器,然后新的容器里添加元素, 添加完元素之后,再将原容器的引用指向新的容器。

//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {public static void main(String[] args) {//并发下 arrayList 是不安全的/*** 解决方案* 1. 使用vector解决* 2. List<String> arrayList = Collections.synchronizedList(new ArrayList<>());* 3. List<String> arrayList = new CopyOnWriteArrayList<>();*///copyOnWrite 写入时复制  COW 计算机程序设计领域的一种优化策略//多个线程调用的时候, list, 读取的时候固定的,写入的时候,可能会覆盖//在写入的时候避免覆盖造成数据问题//CopyOnWriteArrayList 比 vector牛逼在哪里//读写分离List<String> arrayList = new CopyOnWriteArrayList<>();for (int i = 0; i < 100; i++) {new Thread(()->{arrayList.add(UUID.randomUUID().toString().substring(0,5));System.out.println(arrayList);},String.valueOf(i)).start();}}
}


set 不安全-CopyOnWriteArraySet

/*** 同理可证*/
public class SetTest {public static void main(String[] args) {//        Set<String> set = new HashSet<>();//如何解决hashSet线程安全问题//1. Set<String> set = Collections.synchronizedSet(new HashSet<>());Set<String> set = new CopyOnWriteArraySet<>();for (int i = 0; i < 100; i++) {new Thread(() -> {set.add(UUID.randomUUID().toString().substring(0, 5));System.out.println(set);}, String.valueOf(i)).start();}}
}

hashSet底层是hashMap

public HashSet() {map = new HashMap<>();
}// add 的本质就是 map 的 key key是无法重复的
public boolean add(E e) {return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//这是一个不变的值


HashMap不安全-ConcurrentHashMap

Hashtable 是线程安全的,但是不推荐使用,因为它的做法是直接给 Hashtable 对象本身加锁。

如果是 Hashtable 这种加锁方式,两线程访问不同链表中的元素(hash运算不等会放在不同链表中),不会产生线程安全的情况,它也给加锁了,这就导致严重的竞争关系,大大的拖慢了程序的效率!!

size 属性也是通过 synchronized 来控制同步 , 也是比较慢的。
一旦触发扩容 , 就由该线程完成整个扩容过程 . 这个过程会涉及到大量的元素拷贝 , 效率会非常低。

ConcurrentHashMap

ConcurrentHashMap 相比于 Hashtable 做出了重大改进,它把锁的粒度细化了。(下图的改进是基于 Java 8 的)

此时每个桶一把锁,访问不同链表的元素,就不会产生锁竞争了;并且一个哈希表上面的桶的个数可能会非常多,这就进一步使锁冲突的概率大大降低了(稀释了)。

总结 ConcurrentHashMap 的优化策略 :

把锁的粒度细化了,给每个哈希桶都加上一把锁(链表的头结点),降低了锁冲突的概率。

读不加锁,写才加锁。

读操作没有加锁,目的是为了进一步降低锁冲突的概率。为了保证读到刚修改的数据, 搭配了

volatile 关键字。 

在维护 size 的时候,使用 CAS 机制,又进一步降低了锁冲突。

针对扩容场景做出了优化:化整为零

发现需要扩容的线程,只需要创建一个新的数组,同时只搬几个元素过去。扩容期间,新旧数组同时存在。后续每个来操作 ConcurrentHashMap 的线程, 都会参与搬家的过程,每个操作负责搬运一小部分元素。搬完最后一个元素再把旧数组删掉。这个期间,插入只往新数组加增加,查找需要同时查新数组和旧数组。
而 Hashtable,HashMap 它们在某次 put 的时候,触发扩容,由这个 put 完成整个扩容操作,就巨慢无比,非常低效!!

在 Java 8 之前,此处的 ConcurrentHashMap 并不是每个桶加锁,而是"分段锁",若干个桶一把锁,类似下图:

HashMap 中 key 允许为 null ,Hashtable 和 ConcurrentHashMap 不允许


JUC的Callable()创建线程

普通的线程代码, 之前都是用的thread或者runnable接口

public class demo01 {public static void main(String[] args) {ThreadDemo threadDemo = new ThreadDemo();threadDemo.start();new Thread(new ThreadDemo2()).start();}
}class ThreadDemo extends Thread{@Overridepublic void run() {System.out.println("普通线程已开启(继承Thread)");}
}
class ThreadDemo2 implements Runnable{@Overridepublic void run() {System.out.println("普通线程已开启(实现Runnable接口)");}
}

使用FutureTask+Callable创建多线程的方式

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不同, run() => call()

public class CallableTest {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());new Thread(futureTask,"a").start();System.out.println(futureTask.get());}
}class MyThread implements Callable<Integer> {@Overridepublic Integer call() throws Exception {System.out.println("call()方法被调用了");return 1024;}
}


JUC中的tools(工具类/辅助类)

CountDownLatch

countDownLatch.countDown(); //数量减1

countDownLatch.await();// 等待计数器归零,然后再向下执行

每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await();就会被唤醒,继续执行

//计数器
public class demo02 {public static void main(String[] args) throws InterruptedException {//相当于计数器CountDownLatch countDownLatch = new CountDownLatch(5);//计数器总数是5,当减少为0,任务才继续向下执行for (int i = 1; i <6 ; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+"==>start");countDownLatch.countDown();}).start();}countDownLatch.await();System.out.println("main线程继续向下执行");}
}


cyclicBarrier

public class CyclicBarrierDemo {public static void main(String[] args) {/*** 集齐77个龙珠召唤神龙*/// 召唤龙珠的线程CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{System.out.println("召唤神龙成功! ");});for (int i = 0; i < 7; i++) {int temp = i;//lambda 能拿到i吗new Thread(()->{System.out.println(Thread.currentThread().getName() + "收集" + temp + "个龙珠");try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}).start();}}
}


Semaphore

semaphore.acquire(); //获取信号量,假设如果已经满了,等待信号量可用时被唤醒

semaphore.release(); //释放信号量

作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数

public class SemaphoreTest {public static void main(String[] args) {Semaphore semaphore = new Semaphore(3);for (int i = 0; i < 6; i++) {int temp = i;new Thread(()->{try {semaphore.acquire(); //获取System.out.println(temp + "号车抢到车位");TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release(); //释放System.out.println(temp + "号车离开车位");}}).start();}}
}


JUC下的阻塞队列

什么情况下我们会使用阻塞队列,多线程并发处理,线程池!
如何使用队列?
添加 移除
四组API,核心put()、take()

方式抛出异常不会抛出异常,有返回值阻塞等待超时等待
添加操作add()offer()供应put()offer(obj,int,timeunit.status)
移除操作remove()poll()获得take()poll(int,timeunit.status)
判断队列首部element()peek()偷看,偷窥
/*** 抛出异常*/public static void test1() {//队列的大小ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);System.out.println(queue.add("a"));System.out.println(queue.add("b"));System.out.println(queue.add("c"));//java.lang.IllegalStateException: Queue full//System.out.println(queue.add("d"));System.out.println("----------------------");System.out.println(queue.remove());System.out.println(queue.remove());System.out.println(queue.remove());//java.util.NoSuchElementExceptionSystem.out.println(queue.remove());//抛出异常}
   /*** 有返回值没有异常*/public static void test2(){ArrayBlockingQueue queue = new ArrayBlockingQueue(3);System.out.println(queue.offer("a"));System.out.println(queue.offer("b"));System.out.println(queue.offer("c"));
//        System.out.println(queue.offer("d"));       //offer 不抛出异常System.out.println(queue.poll());System.out.println(queue.poll());System.out.println(queue.poll());
//        System.out.println(queue.poll());   //null 不抛出异常}
    /*** 等待阻塞*/public static void test3() throws InterruptedException {ArrayBlockingQueue queue = new ArrayBlockingQueue(3);queue.put("a");queue.put("b");queue.put("c");
//        queue.put("c");  队列没有位置就会阻塞System.out.println(queue.take());System.out.println(queue.take());System.out.println(queue.take());}

SynchronousQueue同步队列
没有容量,
进去一个元素,必须等待取出来之后,才能再往里面放一个元素
put take


/*** 同步队列* 和其他的lockQueue 不一样, SynchronousQueue 不存储元素*/
public class SyncQueue {public static void main(String[] args) {SynchronousQueue<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(3);System.out.println(Thread.currentThread().getName() + "=>" + synchronousQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "=>" + synchronousQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "=>" + synchronousQueue.take());} catch (InterruptedException e) {e.printStackTrace();} finally {}},"T2").start();}
}

运行结果

T1put 1
T2=>1
T1put 2
T2=>2
T1put 3
T2=>3


JUC下的类构建线程池

Executors

静态静态工厂方法,最终都是通过ThreadPoolExecutor类来完成的

 

这篇关于Java - JUC(java.util.concurrent)包详解,其下的锁、安全集合类、线程池相关、线程创建相关和线程辅助类、阻塞队列的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/476263

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

hdu1180(广搜+优先队列)

此题要求最少到达目标点T的最短时间,所以我选择了广度优先搜索,并且要用到优先队列。 另外此题注意点较多,比如说可以在某个点停留,我wa了好多两次,就是因为忽略了这一点,然后参考了大神的思想,然后经过反复修改才AC的 这是我的代码 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof