并发包JUC学习

参考:https://www.cnblogs.com/linkworld/p/7819270.html
https://www.cnblogs.com/chenpi/p/5614290.html#_label3
https://blog.csdn.net/east123321/article/details/84771221
JUC:在java1.5提供了 java.util.concurrent包 (称JUC或者并发包),在并发编程中常用的工具类,

JUC大致包含:
1) 原子类(Atomic )
2) 锁框架( locks )
3) 同步器框架 (AbstractQueuedSynchronizer)
4) 执行器框架(Executor)
5) 并发集合类

原子类 参考:https://blog.csdn.net/qq_36771269/article/details/80889830
https://blog.csdn.net/mulinsen77/article/details/84579858
非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。

从多线程并行计算 乐观锁 和 悲观锁 来讲,JAVA中的synchronized 属于悲观锁,即是在操作某数据的时候总是会认为多线程之间会相互干扰,属于阻塞式的加锁;Atomic系列则属于乐观锁系列,即当操作某一段数据的时候,线程之间是不会相互影响,采用非阻塞的模式,直到更新数据的时候才会进行版本的判断是否值已经进行了修改。
sun.misc.Unsafe
这个类包含了大量的对C代码的操作,包括了很多直接内存分配和原子操作的调用,都存在安全隐患,所以标记为unsafe。
AtomicInteger是一个标准的乐观锁实现,sun.misc.Unsafe是JDK提供的乐观锁的支持。

根据修改的数据类型,可以将JUC包中的原子操作类可以分为4类。
基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
数组类型: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
引用类型: AtomicReference, AtomicStampedRerence, AtomicMarkableReference ;
对象的属性修改类型: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater 。

Atomic类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。

volatile关键字用于修饰变量, 当多个线程进行操作共享数据时,可以保证内存中的数据是可见的,线程在每次使用该变量时,都会读取变量修改后的最的值。(注意,如果只是使用volatile,也不足以保证数据操作的原子性。)

锁框架( locks )
先复习一下同步锁 synchronized,
synchronized java关键字, 可修饰代码块,方法,静态方法,类
synchronized 修饰变量会怎样? 嗯 会报错,编译都不会通过
在这里插入图片描述
synchronized 锁依赖于JVM层面,出现异常,锁会自动释放.

JUC中的锁 参考 https://blog.csdn.net/ZYC88888/article/details/80019202
JUC包中的锁,包括:Lock接口,ReadWriteLock接口,LockSupport阻塞原语,Condition条件,AbstractOwnableSynchronizer/AbstractQueuedSynchronizer/AbstractQueuedLongSynchronizer三个抽象类,ReentrantLock独占锁,ReentrantReadWriteLock读写锁。
这些锁的关系是这样的
在这里插入图片描述
ReentrantLock 和 ReentrantReadWriteLock 部分源码 (它们家关系真乱)

public class ReentrantLock implements Lock, java.io.Serializable {...}    
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
			 public static class ReadLock implements Lock, java.io.Serializable {...}
			 public static class WriteLock implements Lock, java.io.Serializable {...}
}

Lock接口和ReadWriteLock接口这些锁别忘了在finally中释放释放,

StampedLock它是java8在java.util.concurrent.locks新增的一个API。 http://www.importnew.com/14941.html
StampedLock控制锁有三种模式(写,读,乐观读),一个StampedLock状态是由版本和模式两个部分组成,锁获取方法返回一个数字作为票据stamp,它用相应的锁状态表示并控制访问,数字0表示没有写锁被授权访问。在读锁上分为悲观锁和乐观锁。速度比ReentrantReadWriteLock要快很多

总结
(1) synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定;
(2) ReentrantLock、ReentrantReadWriteLock,、StampedLock都是对象层面的锁定,要保证锁定一定会被释放,就必须将unLock()放到finally{}中;
(3)StampedLock 对吞吐量有巨大的改进,特别是在读线程越来越多的场景下;
(4)StampedLock有一个复杂的API,对于加锁操作,很容易误用其他方法;
(5)当只有少量竞争者的时候,synchronized是一个很好的通用的锁实现;
(6)当线程增长能够预估,ReentrantLock是一个很好的通用的锁实现;

注意不要死锁,死锁的四个条件
1 互斥
2 不可剥夺
3 请求与保持
4 循环等待
常用避免死锁的办法:

  1. 加锁顺序
    2)加锁时限

同步器框架 (AbstractQueuedSynchronizer)
同步器框架,也是locks包中的.主要实现类为 (ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。
在锁的实现中,没有直接继承AQS,而是定义了一个静态内部类去继承AQS,锁的实现聚合同步器,利用同步器实现锁的语义。(内部静态类new出来的对象,是每个对象独有的对象,不像静态变量多个对象共享一个变量)

private final Sync sync;
public ReentrantLock() {
        sync = new NonfairSync();
    }
    static final class NonfairSync extends Sync{...}
    abstract static class Sync extends AbstractQueuedSynchronizer {...}

ReenTrantLock:
可重入锁:线程获取一次数,计数器加1,释放一次,计数器减一 计数器为零时锁才被释放
锁的实现:JDK实现的 (synchronized是JVM实现的)
性能:synchronized优化后官方推荐 synchronized
功能: synchronized使用便捷,可以自动释放 ReenTrantLock需要手动释放 ReenTrantLock更灵活
ReenTrantLock独有功能:
(1)可指定是公平锁还是非公平锁

ReentrantLock lock = new ReentrantLock(false); ///非公平锁
ReentrantLock lock = new ReentrantLock(true);  //公平锁

(2)提供了一个Condition类可以分组唤醒需要的线程(synchronized随机唤醒,或者全部唤醒)
(3)提供能够中断等待锁的线程机制,

CountDownLatch同步辅助类
通过它可以完成类似阻塞当前线程的功能 ,
比如主线程中开了N个子线程,最后的计算需要等所有的子线程结束才进行,这个时候使用CountDownLatch.
CountDownLatch有一个给定的计数器进行初始化(该计数器时原子操作),调用该类await()方法,会让线程一直处于阻塞状态.直到其他线程调用countDown()将当前计数器的值变为0,这种操作只会出现一次,计数器是不能被重置的

private final static int threadCount = 200;  //计数器初始值
public static void main(String[] args) throws Exception {
    ExecutorService exec = Executors.newCachedThreadPool();
    final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
    for (int i = 0; i < threadCount; i++) {
        final int threadNum = i;
        exec.execute(() -> {
            try {
                test(threadNum);
            } catch (Exception e) {
                log.error("exception", e);
            } finally {
                countDownLatch.countDown();  //会让计数器中threadCount -1
            }
        });
    }
    countDownLatch.await();//当前线程(主线程)会被挂起,知道计数器中的数值为0才会被唤醒
  //  countDownLatch.await(10, TimeUnit.MILLISECONDS);//等待指定时间,到期自动唤醒
    log.info("finish");
    exec.shutdown(); //关闭线程池  当前已有的线程执行完
}
private static void test(int threadNum) throws Exception {
    Thread.sleep(100);        log.info("{}", threadNum);        Thread.sleep(100);
}

Semaphore(信号量 ):可以控制同一时间访问某个资源的个数
提供两个核心方法acquire(获取许可,没有就等待)和release(操作完成后释放许可)
Semaphore相关方法
void acquire() //获取一个许可
void acquire(int permits) //获取permits个许可
void release() //释放一个许可
void release(int permits) //释放permits个许可
boolean tryAcquire() ; //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
boolean tryAcquire(long timeout, TimeUnit unit) ; //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
boolean tryAcquire(int permits); //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
boolean tryAcquire(int permits, long timeout, TimeUnit unit) ; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
(Semaphore可以用来控制几个线程同时执行一段代码)

 private static Logger log = LoggerFactory.getLogger("AQSsemaphore");
    private final static int threadCount = 20;  //线程数
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(3);   //几个许可
        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    semaphore.acquire(); // 获取一个许可
                    //semaphore.acquire(3); // 获取多个许可
                    test(threadNum);
                    semaphore.release(); // 释放一个许可
                   // semaphore.release(3);// 释放多个许可
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }
    private static void test(int threadNum) throws Exception {
        log.info("{1111111111111}", threadNum);
        Thread.sleep(3000);
    }

CyclicBarrier: 回环栅栏 (同步辅助类) 也是可以控制同一时间访问某个资源的个数
可以完成多个线程之间相互等待(与CountDownLatch区别),只有当每个线程都准备就绪后才能往下执行 也是使用计数器实现(计数器初始值为零.计数器可以重复使用 与CountDownLatch区别)

通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
CyclicBarrier提供2个构造器:
CyclicBarrier(int parties, Runnable barrierAction)
public CyclicBarrier(int parties)
参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。

CyclicBarrier中最重要的方法就是await方法 ( 这个是线程不安全的,他只管计数,到了下面就放行)
用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;
await(long timeout, TimeUnit unit)方法:
让这些线程等待至一定的时间,如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。

 private static Logger log = LoggerFactory.getLogger("AQSsemaphore");
    private static CyclicBarrier barrier = new CyclicBarrier(3, () -> {
        log.info("callback is running"+ FulyUtil.getCurrentThreanName());   //计数器由0加至3时,会执行  然后回归0  
            });

    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
         Thread.sleep(1000);  //*******************注意*************
            final int threadNum = i;
            executor.execute(() -> {
                try {
                    race(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        executor.shutdown();
    }

    private static  void race(int threadNum) throws Exception {
        log.info("{} is ready "+ FulyUtil.getCurrentThreanName(), threadNum);
        Thread.sleep(3000);
        barrier.await(); //计数器加1   //直到计数器值为5时  
        log.info("{} continue  "+ FulyUtil.getCurrentThreanName(), threadNum);   //同时执行了
    }

//注意注意注意:  CyclicBarrier 虽然能保证多少线程之后才执行barrier.await()后面程序,但是没法保证有多少线程同时到达 barrier.await()

Executor框架 https://blog.csdn.net/tongdanping/article/details/79604637 这个写的比较详细,也很清晰 下面是节选这边文章的
什么是Executor框架:
线程池是线程的集合,线程池集中管理线程,线程用于执行异步任务,单个的线程既是工作单元也是执行机制,Executor框架是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。
插播一下 Executors https://blog.csdn.net/a355586533/article/details/78427871
一个功能非常强大的辅助类。
此包中所定义的 Executor、ExecutorService、ScheduledExecutorService、ThreadFactory 和 Callable 类的工厂和实用方法。
---------此类支持以下各种方法:
创建并返回设置有常用配置字符串的 ExecutorService 的方法。
创建并返回设置有常用配置字符串的 ScheduledExecutorService 的方法。
创建并返回“包装的”ExecutorService 方法,它通过使特定于实现的方法不可访问来禁用重新配置。
创建并返回 ThreadFactory 的方法,它可将新创建的线程设置为已知的状态。
创建并返回非闭包形式的 Callable 的方法,这样可将其用于需要 Callable 的执行方法中
(然而…阿里发布的 Java开发手册中强制线程池不允许使用 Executors 去创建 https://blog.csdn.net/fly910905/article/details/81584675)

Executor框架结构图解
在这里插入图片描述
Executor框架成员:ThreadPoolExecutor实现类、ScheduledThreadPoolExecutor实现类、Future接口、Runnable和Callable接口、Executors工厂类
在这里插入图片描述
1、 ThreadPoolExecutor实现类
2、ScheduledThreadPoolExecutor实现类
3、Future接口/FutureTask实现类
4、Runnable和Callable接口:用于实现线程要执行的工作单元。
5、Executors工厂类:提供了常见配置线程池的方法,因为ThreadPoolExecutor的参数众多且意义重大,为了避免配置出错,才有了Executors工厂类。

ThreadPoolExecutor实现类

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {   .......  }

coprePoolSize:核心线程数量 (有线程就放在里面执行,即便有线程是空闲的,也创建新的线程)
maximumPoolSize:最大线程数 (当workQueue满了才会创建新的线程执行)
workQueue:阻塞队列,存储等待执行的任务,线程池满的时候未执行的线程会放在workQueue中
keepAliveTime:线程没有任务执行时最多保持多久时间终止(核心线程中的线程空闲时间)
threadFactory:线程工厂,用来创建线程
rejectHandler:拒绝策略 workQueue满了.线程池满了,再有新线程提交(有四种策略1,直接抛出异常(默认);2,用调用者所在的线程执行任务;3,丢弃阻塞队列中靠最前的任务;4,直接丢弃)

线程池状态: https://blog.csdn.net/L_kanglin/article/details/57411851
在这里插入图片描述
RUNNING: 线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
SHUTDOWN: 线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
STOP: 线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
TIDYING: 当所有的任务已终止,线程任务数量为0,线程池会变为TIDYING状态.当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
TERMINATED: 线程池彻底终止,就变成TERMINATED状态。

ThreadPoolExecutor方法:
1 ) execute():提交任务,交给线程池执行
2 ) submit() : 提交任务能返回执行结果 execute+Future
3 ) shutdown():关闭线程池,等任务执行完
4 ) shutdownNow() :关闭线程池,不等任务执行完
5 ) getTaskCount() 线程池执行和未执行任务总数
6 )getCompletedTaskCount(): 已完成任务数量
7 )getPoolSize() :线程池当前线程数量
8 ) getActiveCount() 当前线程池中正在执行任务的线程数量

JUC—并发集合类
Java集合包
体内容包括Collection集合 和 Map类
Collection集合:List(LinkedList, ArrayList, Vector, Stack) 和 Set(HastSet , TreeSet)
在这里插入图片描述
Map的实现类主要有: HashMap,WeakHashMap, Hashtable和TreeMap

在这里插入图片描述
JUC中的集合类 https://www.cnblogs.com/skywang12345/p/3498454.html (大神写的很好)

JUC包中List和Set实现类
(1)CopyOnWriteArrayList相当于线程安全的ArrayList,它实现了List接口,内部实现了ReentrantLock。
(2)CopyOnWriteArraySet相当于线程安全的HashSet,它继承于AbstractSet类 内部是通过“动态数组(CopyOnWriteArrayList)”实现的。
(3)ConcurrentSkipListSet是线程安全的有序的集合(相当于线程安全的TreeSet);它继承于AbstractSet,并实现了NavigableSet接口。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,

JUC包中Map实现类
(1)ConcurrentHashMap是线程安全的哈希表(相当于线程安全的HashMap);它继承于AbstractMap类,并且实现ConcurrentMap接口。ConcurrentHashMap是通过“锁分段”来实现的

(2)ConcurrentSkipListMap是线程安全的有序的哈希表(相当于线程安全的TreeMap); 它继承于AbstractMap类,并且实现ConcurrentNavigableMap接口。ConcurrentSkipListMap是通过“跳表”来实现的

(3)ConcurrentSkipListSet是线程安全的有序的集合(相当于线程安全的TreeSet);它继承于AbstractSet,并实现了NavigableSet接口。ConcurrentSkipListSet是通过ConcurrentSkipListMap实现的,它也支持并发。

JUC集合包中Queue的实现类
(1) ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列。
(2) LinkedBlockingQueue是单向链表实现的(指定大小)阻塞队列,该队列按 FIFO(先进先出)排序元素。
(3) LinkedBlockingDeque是双向链表实现的(指定大小)双向并发阻塞队列,该阻塞队列同时支持FIFO和FILO两种操作方式。
(4) ConcurrentLinkedQueue是单向链表实现的无界队列,该队列按 FIFO(先进先出)排序元素。
(5) ConcurrentLinkedDeque是双向链表实现的无界队列,该队列同时支持FIFO和FILO两种操作方式。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值