AQS:

  是AbstractQueuedSynchronizer的简称,JUC的核心。

  底层是sync queue双向链表,还可能有condition queue单向链表,使用Node实现FIFO队列,可以用于构建同步队列或者其他同步装置的基础框架。

  使用了int类型表示状态,在AQS中有个state的成员变量,基于AQS的ReentrantLock,state表示获取锁的线程数,等于0,没有,1有,大于1表示重入锁的数量。

protected final int getState() {    //获取当前同步状态
return this.state;
} protected final void setState(int var1) { //设置当前同步状态
this.state = var1;
} //使用CAS设置当前状态,该方法能够保证状态设置的原子性
protected final boolean compareAndSetState(int var1, int var2) {
return unsafe.compareAndSwapInt(this, stateOffset, var1, var2);
}

基于模板方法,需要继承AQS,重写某些方法。可以实现排它锁和共享锁的模式(独占Reentrantlock、共享countdownlatch,同时实现一种)。

1、实现思路:

  首先AQS中维护了一个queue来管理锁,线程会尝试获取锁,如果失败,就将当前线程以及等待状态等信息封装成一个node节点,加入到sync queue的tail,head node的线程释放锁的时候,会唤醒队列中的后继线程,而后续节点在获取锁成功的时候把自己设置为首节点就是因为这些设计,jdk有很多基于AQS的设计,一些常用的组件:

  countdownlatch、semaphore、CyclicBarrier、Reentrantlock、Condition、Futuretask等。

2、同步组件:

2.1).CountDownLatch(闭锁):

  可以实现阻塞当前的线程,通过一个计数器进行初始化,这个计数器都是进行原子操作,只能同时有个线程操作这个计数器,调用CountDownLatch的await()会处于阻塞状态,其他线程调用Countdown(),每次减一,直到计数器变成零。

  这时候所有因为调用await()阻塞的线程才能继续往下执行,CountDownLatch只能执行一次,不能重置,想要使用重置的计数器,可以使用。

2.2).CyclicBarrier

  await()需要等到countdown()将计数器减到0,才会执行后续的代码。await()可以有时间参数,选择等待多长时间过后就会执行await后续的代码。

countDown()尽量卸载finally内部
countDownLatch.await();
countDownLatch.await(10, TimeUnit.MILLISECONDS);

使用场景:

2.3).Semaphore:信号量

  控制并发访问的个数,用于只能提供有限访问的资源。

semaphore.acquire(3); // 获取多个许可
test(threadNum);
semaphore.release(3); // 释放多个许可
Semaphore semaphore = new Semaphore(2);允许线程数一定要大于等于acquire和release的个数
semaphore.tryAcquire()尝试获取许可,没有获取许可的线程都会丢弃
semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS)在5000ms中尝试获取许可

 2.4).CyclicBarrier

  用于多线程计算数据,最后合并计算结果,例如Excel很多页流水,通过多线程计算每一页流水,最后计算总的。通过调用await()方法,线程进入等待状态,计数器进行加一操作,当值等于设置的初始值时,所有阻塞的线程继续执行。

private static CyclicBarrier barrier = new CyclicBarrier(5, () -> {
log.info("callback is running");
});

  通过使用lambda,当计数器满足条件优先执行lambda表达式里面的代码。

2.5).CyclicBarrier和CountDownLatch区别:

  1、CountDownLatch只能使用一次,而CyclicBarrier可以循环利用,使用reset进行重置。

  2、CountDownLatch描述:1或N个线程需要等待其他线程完成某个操作,才能继续往下执行。

  3、CyclicBarrier:多个线程之间相互等待,知道所有线程都满足某个条件才能继续执行后续操作,是各个线程直接相互等待的操作。

countdown表现:    CountDownLatch表现:

1 is ready        1 is ready
2 is ready     2 is ready
3 is ready     3 is ready
1 continue     1 continue
2 continue     2 continue
3 continue     3 continue
4 is ready     4 is ready
4 continue     5 is ready
5 is ready     6 is ready
5 continue     4 continue
6 is ready     5 continue
6 continue     6 continue

LOCK

1、ReadWriteLock:

2、ReentrantReadWriteLock:

  支持多线程读,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

  一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。

  一个线程要想同时持有写锁和读锁,必须先获取写锁再获取读锁;写锁可以“降级”为读锁;读锁不能“升级”为写锁。

  读读共享、其他都是互斥

3、ReentrantLock:

  注意不要把lock的实例化做成局部变量,每个线程执行该方法时都会保存一个副本,那么理所当然每个线程执行到lock.lock()处获取的是不同的锁,这样lock就不能起作用了。

1、synchronized:

  可重入性,jvm实现,在之前和ReentrantLock性能差别很大,但是引入了偏向锁、轻量级锁,效率已经相差不大,只能使用非公平锁,可以通过一些工具进行监控,jvm自动做加锁、解锁操作。

2、ReentrantLock:

  可重入性,jdk实现,粒度更小,可以指定公平锁(先等待的线程先获得锁)或非公平锁,提供一个condition类,可以实现分组唤醒需要唤醒的线程,而synchronized关键字要么唤醒一个线程,要么全部线程,可以通过lockInterruptibly()中断等待锁的线程机制,一定要记得在finally释放锁。

4、StampLock:

  对吞吐量有很大的改进,性能上有很大的提升,特别是适合读操作比较多的情况。ReentrantLock、ReentrantReadWriteLock、StampLock等lock都是对象层面的锁定

5、锁使用原则:

  1、当只有少量线程竞争的时候,可以使用synchronized,而且不会引发死锁。

  2、线程竞争不少,线程增长能够预估,可以选择ReentrantLock。

可重入锁:

  synchronized和ReentrantLock都是可重入锁。

  锁的分配机制是基于线程的分配,而不是基于方法的分配,在method1中已经获取了对象锁,在方法内部调用method2不用重新获取锁。

可中断锁:

  synchronized就不是可中断锁,而Lock是可中断锁。

  lockInterruptibly()的用法时已经体现了Lock的可中断性。

公平锁:

  公平锁是指当一个锁被释放的时候,等待时间最长的线程会获取该锁,非公平锁可能导致某些线程永远不会获取到锁。

  synchronized不是公平锁,ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。

  ReentrantLock在实例化的时候参数true表示公平锁,false表示非公平锁,而且有很多判断锁状态的方法。

读写锁:

  多线程读操作不会发生冲突。

condition:await()、signal()可以实现多路通知功能,但是通知部分线程要使用多个condition类,否则会全部唤醒。

J.U.C组件拓展:

Callable与Runnable、Thread接口对比:

Future接口:

  可以得到线程任务方法的返回值。

FutureTask类:

  实现了Runnable、Future,使用场景:线程A做一件事,线程B做别的事,在需要的时候可以的到线程A的返回值。

Fork/Join(jdk1.7):

  就是把大任务拆分成若干小任务,放到双端队列,每个队列分配一个线程,先做完的线程帮助其他线程,一个从下面,一个从上面,并行执行,最终汇总结果,但是某些情况下还是有线程竞争的情况

局限性:

  1、只能通过fork、join进行操作 2、不能有io操作 3、任务不能抛出检查异常。

Queue:

  除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的。

add(E e):

  将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则会抛出异常。

remove():

  移除队首元素,若移除成功,则返回true;如果移除失败(队列为空),则会抛出异常。

offer(E e):

  将元素e插入到队列末尾,如果插入成功,则返回true;如果插入失败(即队列已满),则返回false。

poll():

  移除并获取队首元素,若成功,则返回队首元素;否则返回null。

peek():

  获取队首元素,若成功,则返回队首元素;否则返回null。

注意点:
  1、对于非阻塞队列,一般情况下建议使用offer、poll和peek三个方法,不建议使用add和remove方法。因为使用offer、poll和peek三个方法可以通过返回值。

  2、判断操作成功与否,而使用add和remove方法却不能达到这样的效果。注意,非阻塞队列中的方法都没有进行同步措施。

  3、阻塞队列对于上面五个方法有做同步处理,而非阻塞队列没有同步。

put(E e)
take()
offer(E e,long timeout, TimeUnit unit)
poll(long timeout, TimeUnit unit) put方法用来向队尾存入元素,如果队列满,则等待;
take方法用来从队首取元素,如果队列为空,则等待;
offer方法用来向队尾存入元素,如果队列满,则等待一定的时间,当时间期限达到时,如果还没有插入成功,则返回false;否则返回true;
poll方法用来从队首取元素,如果队列空,则等待一定的时间,当时间期限达到时,如果取到,则返回null;否则返回取得的元素;

1、BlockingQueue:

  主要用在生产者消费者场景,不需要关注什么时候阻塞和唤醒。

2、ArrayBlockingQueue:

  有界的阻塞队列,就是容量是有限的,初始化指定容量大小,FIFO,内部是由数组实现。

3、DelayQueue:

  必须实现Delay接口,它的元素要进行排序,应用场景:定时关闭连接、缓存对象,超时处理等。

4、LinkedBlockingQueue:

  内部是链表,和ArrayBlockingQueue相似,FIFO。

5、priorityBlockingQueue:

  允许插入null。

6、SynchronousQueue:

  只能插入一个值,插入一个元素就会阻塞,也叫同步队列。

并发最佳实践:

  1、使用本地变量。

  2、使用不可变类。

  3、最小化锁的作用范围:S=1/(1-a+a/n)。

  4、宁可使用同步也不使用线程的wait和notify。

  5、使用BlockingQueue实现生产-消费模式。

  6、使用并发集合而不是加了锁的同步集合。

  7、使用Semaphore创建有界的访问。

  8、在使用synchronized时,宁可使用同步代码块,也不使用同步方法。

  9、避免使用静态变量。

并发和多线程(三)--并发容器J.U.C和lock简介的更多相关文章

  1. Java 并发和多线程(三) 多线程的代价 [转]

    原文链接:http://tutorials.jenkov.com/java-concurrency/costs.html 作者:Jakob Jenkov     翻译:古圣昌        校对:欧振 ...

  2. php-fpm和cgi,并发响应的理解以及高并发和多线程的关系

    首先搞清楚php-fpm与cgi的关系 cgi cgi是一个web server与cgi程序(这里可以理解为是php解释器)之间进行数据传输的协议,保证了传递的是标准数据. php-cgi php-c ...

  3. 多线程之并发容器ConcurrentHashMap(JDK1.6)

    简介 ConcurrentHashMap 是 util.concurrent 包的重要成员.本文将结合 Java 内存模型,分析 JDK 源代码,探索 ConcurrentHashMap 高并发的具体 ...

  4. 多线程之并发容器ConcurrentHashMap

    这部分内容转载自: http://www.haogongju.net/art/2350374 JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步 ...

  5. 多线程六 同步容器&并发容器

    同步容器(使用的是synchronized,并且不一定是百分百安全) 本篇续 -- 线程之间的通信 ,介绍java提供的并发集合,既然正确的使用wait和notify比较困难,java平台为我们提供了 ...

  6. Java高并发与多线程(三)-----线程的基本属性和主要方法

    今天,我们开始Java高并发与多线程的第三篇,线程的基本属性和主要方法. [属性] 编号(ID) 类型long 用于标识不同的线程,编号唯一,只存在java虚拟机的一次运行 名称(Name) 类型St ...

  7. JAVA 多线程和并发学习笔记(三)

    Java并发编程中使用Executors类创建和管理线程的用法 1.类 Executors Executors类可以看做一个“工具类”.援引JDK1.6 API中的介绍: 此包中所定义的 Execut ...

  8. 【Java并发编程二】同步容器和并发容器

    一.同步容器 在Java中,同步容器包括两个部分,一个是vector和HashTable,查看vector.HashTable的实现代码,可以看到这些容器实现线程安全的方式就是将它们的状态封装起来,并 ...

  9. JDK的多线程与并发库

    1.创建多线程 public class MultiThread { public static void main(String[] args) { // 通过继承Thread类 Thread th ...

随机推荐

  1. google免费DNSserver好用不?

    中国的网络实在不行,网速一直就是令人诟病. 比韩日那是差太多了,可是相比非洲还是不错.可是这根本无法满足国人的上网需求.于是大家都想破了脑袋想提高网速.这不方法来了么? 笔者在网上找了几种方法关于怎样 ...

  2. 【OI】简单的分块

    介绍下简单的分块: 当我们遇到区间类问题的时候,如何保证我们快速而高效地完成操作? 答案是线段树分块. 所谓分块,就是把一个序列分成许多块分别维护.是不是想起了树状数组 这样能大大提高效率: 例如,我 ...

  3. [翻译]NUnit---Equality Asserts&& Identity Asserts (四)

    Equality Asserts 这些方法测试两个参数是否相等.语言不自动装修的普通类型可以使用对应的重载的方法. Comparing Numerics of Different Types 比较两个 ...

  4. 计算属性 computed

    计算属性 computed 计算缓存 vs Methods <div id="example"> <p>Original message: "{{ ...

  5. uva10655

    Given the value of a+b and ab you will have to find the value of a n + b n Input The input file cont ...

  6. Book-MySQL-Operate

    创建数据库 CREATE DATABASE db_name 查看数据库 SHOW DATABASES 选择数据库 USE db_name 删除数据库 DROP DATABASE db_name 列主键 ...

  7. P4110 [HEOI2015]小L的白日梦

    传送门 题解 //minamoto #include<bits/stdc++.h> using namespace std; typedef long long ll; typedef l ...

  8. golang——log包学习

    log包实现了简单的日志服务. 1.func New(out io.Writer, prefix string, flag int) *Logger New创建一个Logger. 参数out设置日志信 ...

  9. GitHub安装使用教程

      由于重复率比较大,为了尊重他人的成果,先在此注明本文是在学习了以下博文之后的一些总结归纳,并且说明了一些本人实际使用GitHub遇到的问题,并给出了解决办法 .本人的操作系统是window10,所 ...

  10. [Qt Creator 快速入门] 第5章 应用程序主窗口

    对于日常见到的应用程序而言,许多都是基于主窗口的,主窗口中包含了菜单栏.工具栏.状态栏和中心区域等.这一章会详细介绍主窗口的每一个部分,还会涉及资源管理.富文本处理.拖放操作和文档打印等相关内容.重点 ...