一.Volatile

0.基础知识

volatile关键字是java虚拟机提供的最轻量级的同步机制

JMM:Java Memory Model

  • 可见性 Visiblity
  • 原子性 Atomicity
  • 有序性 Ordering

可见性:

JMM内存模型中,工作内存和主内存同步延迟现象造成了可见性问题。

为什么要使用缓存?

  • 处理器CPU和存储设备的速度存在数量级上的差距,使用缓存Cache来解决这个问题

    使用缓存造成的问题?

  • 每个处理器(线程)都有自己的缓存,但是却需要共享一个主内存(线程之间的通信传值必须通过主内存来完成),也就是缓存一致性问题

原子性:

​ 不可分割,完整性,只能同时成功(失败)

  1. ```java

n++;

// 1.执行getfield(getstatic)获得原始值

// 2.执行iadd 进行加1操作

// 3.执行putfield写 把累加后的值写回

```

​ 保证原子性:

  1. - synchronized
  2. - JUC(atomic)

有序性:

​ 简单来说有序性就是禁止指令重排序优化

​ 处理器计算和Java虚拟机都会对指令重排进行优化

  1. ```java

1.源代码

2.编译器优化重排

3.指令并行重排

4.内存系统重排

5.最终执行的指令

```

  1. - 单线程环境确保程序最终执行结果和代码顺序执行的结果一致
  2. - 处理器进行重排序必须考虑**指令之间的依赖性**
  3. - 多线程中线程交替运行,编译器优化重排,无法保证多个线程中使用变量保持一致性

内存屏障:Memory Barrier,是一个CPU指令

  • 保证特定操作的执行顺序
  • 保证某些变量的内存可见性(实现volatile)
  • 插入内存屏障,禁止内存屏障前后的指令执行重排序优化
1. volatile的解释

当一个变量定义为Volatile后:

  • 可见性,对所有线程的可见性
  • 有序性,禁止对指令进行重排序优化
  • 非原子性,不能保证原子性操作
  1. class MyData{
  2. volatile int number = 0;
  3. void setNumber(){
  4. this.number = 60;
  5. }
  6. void addNumber(){
  7. number ++;
  8. }
  9. // juc 来保证原子性
  10. AtomicInteger atomicInteger=new AtomicInteger(0);
  11. void addAtomic(){
  12. atomicInteger.getAndIncrement();
  13. }
  14. }
  15. public class VolatileVisible {
  16. public static void main(String[] args) {
  17. //VisibleByVolatile();
  18. AtomicNotByVolatile();
  19. }
  20. /**
  21. * volatile 不保证原子性
  22. */
  23. public static void AtomicNotByVolatile() {
  24. MyData myData=new MyData();
  25. for(int i=1;i<=20;i++){
  26. new Thread(()->{
  27. for(int j=0;j<1000;j++){
  28. myData.addNumber();
  29. myData.addAtomic();
  30. }
  31. },String.valueOf(i)).start();
  32. }
  33. // 等待线程完全运行
  34. while(Thread.activeCount()>2){
  35. Thread.yield();
  36. }
  37. System.out.println(Thread.currentThread().getName()
  38. +"\t finally number value "+myData.number);
  39. System.out.println(Thread.currentThread().getName()
  40. +"\t atomic number "+myData.atomicInteger);
  41. }
  42. /**
  43. * volatile 保证可见性
  44. */
  45. public static void VisibleByVolatile() {
  46. MyData myData=new MyData();
  47. new Thread(() -> {
  48. System.out.println(Thread.currentThread().getName()+"\t come in");
  49. try{
  50. TimeUnit.SECONDS.sleep(3);
  51. }catch (InterruptedException e){
  52. e.printStackTrace();
  53. }
  54. myData.setNumber();
  55. System.out.println(Thread.currentThread().getName()
  56. +"\t updated number value "+ myData.number);
  57. },"aaa").start();
  58. // 等待循环
  59. while(myData.number==0){
  60. }
  61. System.out.println(Thread.currentThread().getName()
  62. +"\t mission is over "+myData.number);
  63. }
  64. }
3.volatile的应用

​ 在单例模式(Singleton)的双重检测(DCL)

  • DCL双端检测机制不一定安全,存在指令重排序的可能(多次运行构造方法)
  • Volatile ,将变量voaltile定义后可以保证单例模式的正确
  1. public class SingletonTest {
  2. private volatile static SingletonTest instance=null;
  3. private SingletonTest(){
  4. System.out.println(Thread.currentThread().getName()+"\t 构造方法");
  5. }
  6. // 懒加载模式不能保证在并发情况下的单例
  7. private static SingletonTest getInstance(){
  8. if(instance==null){
  9. instance=new SingletonTest();
  10. }
  11. return instance;
  12. }
  13. // DCL模式 双端检锁模式 instance需要表示为volatile
  14. // 防止指令进行重排
  15. private static SingletonTest DCLGetInstance(){
  16. if(instance==null){
  17. synchronized (SingletonTest.class){
  18. if(instance==null){
  19. instance=new SingletonTest();
  20. }
  21. }
  22. }
  23. return instance;
  24. }
  25. public static void main(String[] args) {
  26. for(int i=1;i<=10;i++){
  27. new Thread(()->{
  28. SingletonTest.DCLGetInstance();
  29. }).start();
  30. }
  31. }
  32. }

二.CAS

0.CAS的定义

CompareAndSwap 比较并交换

CompareAndSwap 是一条并发原语

  • 判断内存某个位置的值是否为预期值,如果是则更改为新的值
  • 原语的执行必须是连续的,执行过程中不允许被打断
  • CAS是CPU的原子指令,不会造成数据不一致性问题
  1. // java.util.concurrent.atomic
  2. // AtomicInteger.compareAndSet()
  3. public final boolean compareAndSet(int expectedValue, int newValue) {
  4. return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
  5. }
  6. // Unsafe 类下的native方法
  7. public final native boolean compareAndSetInt(Object o, long offset,
  8. int expected,int x);

第一参数拿到的期望值,如果期望值一致,进行newValue赋值;期望值不一致,数据被修改,返回false

java.util.concurrent 包中的许多类都提供比synchronized机制更高的性能和可伸缩性

原子变量+非阻塞的同步机制

  • 原子变量: 一种更好的volatile类型变量,解决数据在并发访问中的一致性问题
  • 非阻塞算法: 应用在OS,JVM线程/进程调度,垃圾回收机制
1.CAS底层原理
  • 自旋锁
  • Unsafe类

Unsafe类直接调用操作系统底层资源执行相关任务

  1. // AtomicInteger.java
  2. private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
  3. private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");
  4. public final int getAndIncrement() {
  5. return U.getAndAddInt(this, VALUE, 1);
  6. }
  7. // Unsafe.java
  8. public final int getAndAddInt(Object o, long offset, int delta) {
  9. int v;
  10. do {
  11. // 先根据内存偏移地值 来取到值
  12. v = getIntVolatile(o, offset);
  13. // 如果这个值在运行期间被其他值修改,重新进行读取这个值,
  14. // 保证这个值相等,才会进行增加操作
  15. } while (!weakCompareAndSetInt(o, offset, v, v + delta));
  16. return v;
  17. }
2.CAS的缺点
  • 循环时间长,CAS不成功,会给CPU带来很大的开销
  • 只能保证一个共享变量的原子操作
  • ABA问题
3.ABA问题
  • 什么是ABA问题?

一个变量初次读取的时候是A值,它的值被修改成B,后来又被修改成A,CAS操作会认为其重来没有被修改过

CAS算法实现一个重要的前提需要去除内存中某个时刻的数据并在当下时刻进行比较并替换,在这两个操作的时间间隔会导致数据的变化。

  • 如何解决ABA问题

    1)原子引用

    2)传统的互斥同步

  • 原子引用

    1. AtomicReference<>(); // 引用类
    2. AtomicStampedReference<>() // 带时间戳的原子引用

三.集合类并发安全

0.问题描述

直接使用ArrayList / HashSet/ HashMap 在多线程并发的环境下

  1. public class ContainerNotSafeTest {
  2. public static void main(String[] args) {
  3. listNotSafe();
  4. mapSafe();
  5. }
  6. public static void mapSafe() {
  7. Map<String,String> map=new ConcurrentHashMap<>();//new HashMap<>();
  8. for(int i=1;i<=30;i++) {
  9. new Thread(() -> {
  10. map.put(Thread.currentThread().toString(), UUID.randomUUID().toString().substring(0, 8));
  11. System.out.println(map);
  12. }, String.valueOf(i)).start();
  13. }
  14. }
  15. public static void listNotSafe() {
  16. //List<String> list= Arrays.asList("a","b","c");
  17. //list.forEach(System.out::println);
  18. List<String> list=new CopyOnWriteArrayList<>();
  19. for(int i=1;i<=30;i++){
  20. new Thread(()->{
  21. list.add(UUID.randomUUID().toString().substring(0,8));
  22. System.out.println(list);
  23. },String.valueOf(i)).start();
  24. }
  25. }
  26. }
  • Java.util.ConcurrentModificationException
1.故障现象
  • 并发情况下常出现的 并发修改异常
  1. private void checkForComodification(final int expectedModCount) {
  2. if (modCount != expectedModCount) {
  3. throw new ConcurrentModificationException();
  4. }
  5. }
2.导致原因
  • 并发环境下,线程对数据进行修改导致
3.解决方法
  • 1.使用线程安全的集合类 Vector
  1. List<String> list=new Vector<>();
  2. // 加锁synchronized来实现并发安全
  3. public synchronized boolean add(E e) {
  4. modCount++;
  5. add(e, elementData, elementCount);
  6. return true;
  7. }

  • 拓展知识 1:同步容器类

    • Vector
    • Hashtable

这是早期JDK的一部分(jdk 1.0),这类同步的封装器由 Collections.synchronizedXxx等工厂方法创建的,实现线程安全的方式:将他们的状态封装起来,对每个共有方法进行同步,是得每次只有一个线程能访问容器的状态

  1. static class SynchronizedCollection<E> implements Collection<E>, Serializable {
  2. private static final long serialVersionUID = 3053995032091335093L;
  3. final Collection<E> c; // Backing Collection
  4. final Object mutex; // Object on which to synchronize
  5. SynchronizedCollection(Collection<E> c) {
  6. this.c = Objects.requireNonNull(c);
  7. mutex = this;
  8. }
  9. SynchronizedCollection(Collection<E> c, Object mutex) {
  10. this.c = Objects.requireNonNull(c);
  11. this.mutex = Objects.requireNonNull(mutex);
  12. }
  13. // 实现互斥同步
  14. public int size() {
  15. synchronized (mutex) {return c.size();}
  16. }
  17. public boolean isEmpty() {
  18. synchronized (mutex) {return c.isEmpty();}
  19. }
  20. public boolean contains(Object o) {
  21. synchronized (mutex) {return c.contains(o);}
  22. }
  23. public Object[] toArray() {
  24. synchronized (mutex) {return c.toArray();}
  25. }
  26. }
  • 拓展知识 2 : 工厂模式

    • 简单工厂
    • 抽象工厂
    • 工厂方法

工厂:封装创建对象(new)的代码

工厂方法: 处理对象的创建,并将这样的行为封装在子类中

  • 定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法将类的实例化推迟到子类
  1. abstract Product factoryMethod(String type)

  • 2.使用集合同步方法封装

    1. List<String> list= Collections.synchronizedList(new ArrayList<>());
  • 3.并发容器类

    1. // java.util.concurrent.CopyOnWriteArrayList;
    2. List<String> list= new CopyOnWriteArrayList<>();
    3. // 源码
    4. final transient Object lock = new Object();
    5. // 在写的时候进行加锁
    6. public boolean add(E e) {
    7. synchronized (lock) {
    8. Object[] es = getArray();
    9. int len = es.length;
    10. es = Arrays.copyOf(es, len + 1);
    11. es[len] = e;
    12. setArray(es);
    13. return true;
    14. }
    15. }

四.Java锁

0.公平锁、非公平锁
  • 公平锁:申请锁的顺序(先来后到)
  • 非公平锁: 多个线程获取锁的顺序不按照申请锁的顺序,高并发下会造成优先级反转或者饥饿的现象

ReentrantLock 默认就是非公平锁

Synchronized 也是非公平锁

  1. // 默认的无参构造函数就是非公平锁
  2. public ReentrantLock() {
  3. sync = new NonfairSync();
  4. }
  5. // 传入True就是公平锁
  6. public ReentrantLock(boolean fair) {
  7. sync = fair ? new FairSync() : new NonfairSync();
  8. }

非公平锁的优先在于吞吐量比公平锁要大

1.可重入锁(递归锁)

可重入锁也叫递归锁,最大的作用就是避免死锁

  • 同一线程外层函数获得锁之后,内层递归函数仍获取该锁代码
  • 线程可以进入任何一个它已经拥有的锁所同步着的代码块

ReetrantLock/Synchronized 就是典型的可重入锁

  1. // synchronized
  2. class Phone{
  3. // 同一线程外层函数获得锁之后,内层递归函数仍获取该锁代码
  4. public synchronized void sendSMS() throws Exception{
  5. System.out.println(Thread.currentThread().getId()+"\t invoked sendSMS");
  6. // 内层函数
  7. sendEmail();
  8. }
  9. public synchronized void sendEmail() throws Exception{
  10. System.out.println(Thread.currentThread().getId()+"\t invoked sendEmail");
  11. }
  12. }
  13. // ReentrantLock
  14. class Person implements Runnable{
  15. @Override
  16. public void run() {
  17. getPerson();
  18. }
  19. Lock lock=new ReentrantLock();
  20. private void getPerson(){
  21. lock.lock();
  22. try{
  23. System.out.println(Thread.currentThread().getName()+"\t invoked get person");
  24. setPerson();
  25. }finally {
  26. lock.unlock();
  27. }
  28. }
  29. private void setPerson(){
  30. lock.lock();
  31. try{
  32. System.out.println(Thread.currentThread().getName()+"\t invoked set person");
  33. }finally {
  34. lock.unlock();
  35. }
  36. }
  37. }
  38. /**
  39. * 可重入锁
  40. */
  41. public class ReentrantLockTest {
  42. public static void main(String[] args) {
  43. Phone phone=new Phone();
  44. Person person=new Person();
  45. new Thread(()->{
  46. try {
  47. phone.sendSMS();
  48. } catch (Exception e) {
  49. e.printStackTrace();
  50. }
  51. },"thread1").start();
  52. new Thread(()->{
  53. try {
  54. phone.sendSMS();
  55. } catch (Exception e) {
  56. e.printStackTrace();
  57. }
  58. },"thread2").start();
  59. System.out.println("==========================");
  60. Thread thread3=new Thread(person);
  61. Thread thread4=new Thread(person);
  62. thread3.start();
  63. thread4.start();
  64. }
  65. }
  66. // 运行结果
  67. /**
  68. Thread-0 invoked get person
  69. Thread-0 invoked set person
  70. Thread-1 invoked get person
  71. Thread-1 invoked set person
  72. 12 invoked sendSMS
  73. 12 invoked sendEmail
  74. 13 invoked sendSMS
  75. 13 invoked sendEmail
  76. /
2.自旋锁

尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁

  • 减少线程上下文切换的消耗
  • 循环会消耗CPU机能
  1. /**
  2. * 自旋锁 spinLock
  3. */
  4. public class SpinLockTest {
  5. private AtomicReference<Thread> atomicReference=new AtomicReference<>();
  6. private void myLock(){
  7. Thread thread=Thread.currentThread();
  8. System.out.println(Thread.currentThread().getName()+"\t thread come in myLock");
  9. // 旋转
  10. // A 线程调用后 B线程一直执行循环等待
  11. while(!atomicReference.compareAndSet(null,thread)){
  12. }
  13. }
  14. private void myUnLock(){
  15. Thread thread=Thread.currentThread();
  16. atomicReference.compareAndSet(thread,null);
  17. System.out.println(Thread.currentThread().getName()+"\t invoked the myUnLock");
  18. }
  19. public static void main(String[] args) {
  20. SpinLockTest spinLockTest=new SpinLockTest();
  21. new Thread(()->{
  22. spinLockTest.myLock();
  23. // 暂停一会线程 让后面的线程无法取到锁 实现自旋
  24. try{ TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
  25. spinLockTest.myUnLock();
  26. },"A").start();
  27. // 暂停一会 A线程先于B线程运行
  28. try{ TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
  29. new Thread(()->{
  30. spinLockTest.myLock();
  31. spinLockTest.myUnLock();
  32. },"B").start();
  33. }
  34. }
3.读写锁/互斥锁
  • 读锁(共享锁),该锁可以被多个线程所持有
  • 写锁(独占锁),该锁一次只能被一个线程所持有
  • 互斥锁,读写,写写都是互斥的
  1. // 线程操作的资源类
  2. class MyCache{
  3. private volatile Map<String,Object> map=new HashMap<>();
  4. // 重入锁
  5. private Lock lock=new ReentrantLock();
  6. // 读写锁
  7. private ReentrantReadWriteLock rwLock=new ReentrantReadWriteLock();
  8. void put(String key, Object value) {
  9. // 写锁
  10. rwLock.writeLock().lock();
  11. try{
  12. System.out.println(Thread.currentThread().getName() + "\t 正在写入" + key);
  13. // 暂停线程模拟 资源
  14. try {
  15. TimeUnit.MILLISECONDS.sleep(300);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. map.put(key, value);
  20. System.out.println(Thread.currentThread().getName() + "\t 写入完成");
  21. }catch (Exception e){
  22. e.printStackTrace();
  23. }finally {
  24. rwLock.writeLock().unlock();
  25. }
  26. }
  27. void get(String key){
  28. rwLock.readLock().lock();
  29. try{
  30. System.out.println(Thread.currentThread().getName()+"\t 正在读取"+key);
  31. // 暂停线程模拟资源
  32. try {
  33. TimeUnit.MILLISECONDS.sleep(300);
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. Object result=map.get(key);
  38. System.out.println(Thread.currentThread().getName() + "\t 读取完成"+result);
  39. }catch (Exception e){
  40. e.printStackTrace();
  41. }finally {
  42. rwLock.readLock().unlock();
  43. }
  44. }
  45. }
  46. /**
  47. * 读写锁
  48. * 读取共享资源可以同时并发的进行
  49. * 写资源 就需要一个线程独占
  50. *
  51. * 写操作:原子+独占
  52. */
  53. public class ReadWriteLockTest {
  54. public static void main(String[] args) {
  55. MyCache myCache=new MyCache();
  56. // 写线程
  57. for(int i=0;i<5;i++){
  58. int finalI = i;
  59. new Thread(()->{
  60. myCache.put(finalI +"", finalI +"");
  61. },String.valueOf(i)).start();
  62. }
  63. // 保证写入完全后,再进行读取
  64. try{TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e){e.printStackTrace();}
  65. // 读线程
  66. for(int i=0;i<5;i++){
  67. int finalI = i;
  68. new Thread(()->{
  69. myCache.get(finalI+"");
  70. },String.valueOf(i)).start();
  71. }
  72. }
  73. }

五.J.U.C-AQS

0.AQS- Abstract Queued Synchronizer

AQS 是一个用于构建锁和同步器的框架,AQS的实现

  • ReentrantLock
  • Semaphore
  • CountDownLatch
  • ReentrantReadWriteLock
  • FutureTask
  1. // AOS 实现类的体现
  2. public class ReentrantLock implements Lock, java.io.Serializable {
  3. /** Synchronizer providing all implementation mechanics */
  4. private final Sync sync;
  5. abstract static class Sync extends AbstractQueuedSynchronizer {
  6. }
  7. //
  8. public class Semaphore implements java.io.Serializable {
  9. /** All mechanics via AbstractQueuedSynchronizer subclass */
  10. private final Sync sync;
  11. /**
  12. * Synchronization implementation for semaphore. Uses AQS state
  13. * to represent permits. Subclassed into fair and nonfair
  14. * versions.
  15. */
  16. abstract static class Sync extends AbstractQueuedSynchronizer {
  17. }
1.Semaphore信号量

信号量可以代替Synchronized和Lock

  • 用于多个共享资源的的互斥作用
  • 并发线程数的控制
  1. public class SemaphoreTest {
  2. public static void main(String[] args) {
  3. Semaphore semaphore=new Semaphore(3);
  4. for(int i=0;i<6;i++){
  5. new Thread(()->{
  6. try{
  7. semaphore.acquire();
  8. System.out.println(Thread.currentThread().getName()+"\t 抢到车位");
  9. try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException ie){ie.printStackTrace();}
  10. System.out.println(Thread.currentThread().getName()+"\t 停车3s后离开车位");
  11. }catch (InterruptedException e){
  12. e.printStackTrace();
  13. }finally {
  14. semaphore.release();
  15. }
  16. },String.valueOf(i)).start();
  17. }
  18. }
  19. }
  20. /**
  21. * 0 抢到车位
  22. * 1 抢到车位
  23. * 2 抢到车位
  24. * 2 停车3s后离开车位
  25. * 1 停车3s后离开车位
  26. * 0 停车3s后离开车位
  27. * 4 抢到车位
  28. * 5 抢到车位
  29. * 3 抢到车位
  30. * 3 停车3s后离开车位
  31. * 5 停车3s后离开车位
  32. * 4 停车3s后离开车位
  33. */

六.阻塞队列

0.BlockingQueue阻塞队列

层级结构图如下图所示,List 和 BlockingQueue是高度相似的层级接口

常见的实现类

  • ArrayBlockingQueue ,基于数组实现的有界阻塞队列

  • LinkedBlockingQueue , 基于链表结构的阻塞队列,吞吐量较ArrayBlockingQueue 要高

    1. //一个很重要的特性,默认的队列长度是 Integer.MAX_VALUE
    2. public LinkedBlockingQueue() {
    3. this(2147483647);
    4. }
    5. public LinkedBlockingQueue(int capacity) {
    6. this.count = new AtomicInteger();
    7. this.takeLock = new ReentrantLock();
    8. this.notEmpty = this.takeLock.newCondition();
    9. this.putLock = new ReentrantLock();
    10. this.notFull = this.putLock.newCondition();
    11. if (capacity <= 0) {
    12. throw new IllegalArgumentException();
    13. } else {
    14. this.capacity = capacity;
    15. this.last = this.head = new LinkedBlockingQueue.Node((Object)null);
    16. }
    17. }
  • SynchronousQueue ,不存储元素的阻塞队列

  • PriorityBlockingQueue

  • LinkedBlockingDeque

  • LinkedTransferQueue

1.阻塞队列的方法

常见的错误类型

2.SynchronousQueue 不存储元素的队列

A blocking queue in which each insert operation must wait for a corresponding remove operation by another thread, and vice versa. A synchronous queue does not have any internal capacity, not even a capacity of one

每一个put操作都必须等待一个take操作,否则不能继续添加元素。

  1. /**
  2. * 不存储元素的队列
  3. */
  4. public class SynchronousQueueTest {
  5. public static void main(String[] args) {
  6. BlockingQueue<String> blockingQueue=new SynchronousQueue<>();
  7. new Thread(()->{
  8. try {
  9. System.out.println(Thread.currentThread().getName()+"\t"+"put 1");
  10. blockingQueue.put("1");
  11. System.out.println(Thread.currentThread().getName()+"\t"+"put 2");
  12. blockingQueue.put("2");
  13. System.out.println(Thread.currentThread().getName()+"\t"+"put 3");
  14. blockingQueue.put("3");
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. },"A").start();
  19. new Thread(()->{
  20. try{
  21. TimeUnit.SECONDS.sleep(5);
  22. System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take());
  23. TimeUnit.SECONDS.sleep(5);
  24. System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take());
  25. TimeUnit.SECONDS.sleep(5);
  26. System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take());
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. },"B").start();
  31. }
  32. }
3.阻塞队列的应用
  • 1.生产消费这模型
  • 2.线程池
  • 3.消息中间件

生产者消费者模型

  • 传统的加锁版本

    sync+wait+notify 是一组

    lock+await+singalAll 是一组

    1. class ShareData{
    2. private int number=0;
    3. private Lock lock=new ReentrantLock();
    4. private Condition condition=lock.newCondition();
    5. void increment() throws InterruptedException {
    6. // 加锁
    7. lock.lock();
    8. try {
    9. // 判断
    10. // 防止虚假唤醒
    11. while(number!=0){
    12. // 等待
    13. condition.await();
    14. }
    15. // 操作
    16. number++;
    17. System.out.println(Thread.currentThread().getName()+"\t"+number);
    18. // 通知唤醒
    19. condition.signalAll();
    20. }catch (Exception e){
    21. e.printStackTrace();
    22. }finally {
    23. lock.unlock();
    24. }
    25. }
    26. void decrement() throws Exception{
    27. // 加锁
    28. lock.lock();
    29. try {
    30. // 判断
    31. // 防止虚假唤醒
    32. while(number == 0){
    33. // 等待
    34. condition.await();
    35. }
    36. // 操作
    37. number --;
    38. System.out.println(Thread.currentThread().getName()+"\t"+number);
    39. // 通知唤醒
    40. condition.signalAll();
    41. }catch (Exception e){
    42. e.printStackTrace();
    43. }finally {
    44. lock.unlock();
    45. }
    46. }
    47. }
    48. /**
    49. * 传统的生产者消费者模式
    50. *
    51. * 两个线程交替操作,一个加1,一个减1
    52. */
    53. public class ConsumerProducerTest {
    54. public static void main(String[] args) {
    55. ShareData shareData=new ShareData();
    56. // 两个线程表示 生产者和消费者
    57. new Thread(()->{
    58. for(int i=0;i<5;i++){
    59. try {
    60. shareData.increment();
    61. } catch (InterruptedException e) {
    62. e.printStackTrace();
    63. }
    64. }
    65. },"A").start();
    66. new Thread(()->{
    67. for(int i=0;i<5;i++){
    68. try {
    69. shareData.decrement();
    70. } catch (Exception e) {
    71. e.printStackTrace();
    72. }
    73. }
    74. },"B").start();
    75. }
    76. }

while()循环

  • wait操作,中断或者虚假唤醒可能会出现,必须放在循环的语句
  • 只能使用while()循环,不能使用if()进行循环
4.synchronized 和lock的区别
  • 原始构成

    synchronized属于JVM,底层使用 monitorenter 和 monitorexit

    lock是具体的类,是api层面的锁 java.util.concurrent.lock

  • 使用的方法

    synchronized ,monitorexit实现自动退出,不需要用户手动的释放锁,代码块执行结束自动让线程释放对锁的占用

    ReentrantLock 需要用户手动的释放锁

  • 等待可中断

    synchronized 不可中断,除非抛出异常否则正常运行到结束

    ReentrantLock 可中断,设置超时方法tryLock(long timeout,TimeUnit unit) ,调用interrupt()方法可以进行中断

  • 加锁否公平

    synchronized 非公平锁

    ReentrantLock两者都可以,默认是公平锁,根据构造方法传入的boolean值来实现

  • 锁绑定多个条件Condition

    Synchronized 没有

    ReentrantLock可以实现精准唤醒

七.线程池

1.为什么使用线程池

线程池主要是控制运行线程的数量,处理过程将任务放入队列,线程执行完毕后,再从队列中取出任务来执行

  • 线程复用,降低资源消耗,重复利用创建的线程降低线程创建和销毁时的资源消耗
  • 控制最大并发数,提高响应的速度,任务到达后不需要等待线程的创建就能立即执行
  • 管理线程,线程池可以对系统资源进行分配、调优和监控
2.线程池

Java中的线程池是通过Executor框架实现的主要有ExecutorExecutorsExecutorServiceThreadPoolExecutor

常见实现的线程池

  • Executors.newFixedThreadPool() ,一个定长的线程池,执行长期任务
  • Executors.newSingleThreadExecutorl() ,一个任务一个任务执行的场景
  • Executors.newCachedThreadPool() ,执行很多短期异步的小程序或负载较轻的服务器,一个可缓存线程池
  • Executors.newScheduledThreadPool() ,带时间调度的
  • Executors.newWorkStealingPool(int) ,机器上可用的处理器个数作为其并行级别
  1. public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
  2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  3. 60L, TimeUnit.SECONDS,
  4. new SynchronousQueue<Runnable>(),
  5. threadFactory);
  6. public static ExecutorService newFixedThreadPool(int nThreads) {
  7. return new ThreadPoolExecutor(nThreads, nThreads,
  8. 0L, TimeUnit.MILLISECONDS,
  9. new LinkedBlockingQueue<Runnable>());
  10. }
3.线程池的创建

线程池依托于ThreadPoolExecutor来进行创建

  1. public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
  2. TimeUnit unit,BlockingQueue<Runnable> workQueue,
  3. ThreadFactory threadFactory,
  4. RejectedExecutionHandler handler) {
  5. if (corePoolSize < 0 ||
  6. maximumPoolSize <= 0 ||
  7. maximumPoolSize < corePoolSize ||
  8. keepAliveTime < 0)
  9. throw new IllegalArgumentException();
  10. if (workQueue == null || threadFactory == null || handler == null)
  11. throw new NullPointerException();
  12. this.corePoolSize = corePoolSize;
  13. this.maximumPoolSize = maximumPoolSize;
  14. this.workQueue = workQueue;
  15. this.keepAliveTime = unit.toNanos(keepAliveTime);
  16. this.threadFactory = threadFactory;
  17. this.handler = handler;
  18. }

线程池的重要参数:

  • corePoolSize, 线程池中常驻的核心线程数
  • maximumPoolsize, 线程池中能容纳的最大线程数
  • keepAliveTime ,多余空闲线程的存活时间
  • Unit ,存活时间的单位
  • workQueue ,任务队列,被提交但是尚未被执行的任务
  • threadFactory ,生成线程池中工作线程的线程工厂
  • handler ,饱和拒绝策略,当队列满且工作线程大雨等于线程池的最大线程数,如何拒绝请求执行的runnable策略

线程池的工作原理(流程):

线程池的拒绝策略

​ 等待队列已经排满,无法容纳新任务,同时线程池的max线程数也到达,无法继续为新任务服务,这个时候就需要拒绝策略机制合理的处理这个问题。

RejectedExecutionHandler

  • AbortPolicy 默认,直觉抛出异常阻止其正常运行

  • CallerRunsPolicy , 将某些任务回退到调用者

  • DiscardOldestPolicy ,抛弃队列中等待最久的任务,当前任务加入队列

  • DiscardPolicy , 直接丢弃任务

4.实际生产的线程池的配置
  • 不用Executors中创建的线程池方法
  • 只能使用自定义的ThreadPoolExecutor

Executors返回的线程池对象的弊端

  • FixedThreadPool、SingledThreadPool 允许请求队列的长度为 Integer.MAX_VALUE ,可能会堆积大量请求,导致OOM
  1. public static ExecutorService newFixedThreadPool(int nThreads) {
  2. return new ThreadPoolExecutor(nThreads, nThreads,
  3. 0L, TimeUnit.MILLISECONDS,
  4. new LinkedBlockingQueue<Runnable>());
  5. // 这个队列的长度问题
  6. public LinkedBlockingQueue() {
  7. this(Integer.MAX_VALUE);
  8. }
  9. public LinkedBlockingQueue(int capacity) {
  10. if (capacity <= 0) throw new IllegalArgumentException();
  11. this.capacity = capacity;
  12. last = head = new Node<E>(null);
  13. }
  • CachedThreadPool、ScheduledThreadPool 允许创建线程数量为Integer.MAX_VALUE ,会创建大量线程,导致OOM
  1. public static ExecutorService newCachedThreadPool() {
  2. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  3. 60L, TimeUnit.SECONDS,
  4. new SynchronousQueue<Runnable>());
  • CPU密集型,cpu核数+1个线程的线程池
  • IO密集型, cpu核数/(1- 阻塞系数) ,阻塞系数在0.8-0.9之间

八.死锁及定位分析

死锁:

  • 系统资源不足
  • 进程运行推进的顺序不合适
  • 资源分配不当

两个及两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象

  1. class holdLockThread implements Runnable{
  2. private String lockA;
  3. private String lockB;
  4. public holdLockThread(String lockA, String lockB) {
  5. this.lockA = lockA;
  6. this.lockB = lockB;
  7. }
  8. @Override
  9. public void run() {
  10. synchronized (lockA){
  11. System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockA+"\t 尝试获取"+lockB);
  12. // 模拟线程操作
  13. try{ TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
  14. synchronized (lockB){
  15. System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockB+"\t 尝试获取"+lockA);
  16. }
  17. }
  18. }
  19. }
  20. /**
  21. * 死锁
  22. * 如何定位死锁的位置
  23. * ps -ef|grep ***
  24. * jps java ps
  25. * jps -l 定位进程号
  26. * jstack 进程号
  27. * 找到死锁查看
  28. */
  29. public class DeadLockTest {
  30. public static void main(String[] args) {
  31. String lockA="lockA";
  32. String lockB="lockB";
  33. new Thread(new holdLockThread(lockA,lockB),"thread AAA").start();
  34. new Thread(new holdLockThread(lockB,lockA),"thread BBB").start();
  35. }
  36. }

解决死锁问题:

除了查看日志还可以进行定位

  1. dengshuodengshuo@dengMac ~/Code/Project/JVM/src/DeadLock jps -l
  2. 9094
  3. 12250 DeadLock.DeadLockTest
  4. 12251 org.jetbrains.jps.cmdline.Launcher
  5. 12252 jdk.jcmd/sun.tools.jps.Jps
  1. dengshuodengshuo@dengMac ~/Code/Project/JVM/src/DeadLock jstack 12250
  2. 2020-03-04 09:28:05
  3. Found one Java-level deadlock:
  4. =============================
  5. "thread AAA":
  6. waiting to lock monitor 0x000000010a140a00 (object 0x0000000787e71cb8, a java.lang.String),
  7. which is held by "thread BBB"
  8. "thread BBB":
  9. waiting to lock monitor 0x0000000100b1ae00 (object 0x0000000787e71c88, a java.lang.String),
  10. which is held by "thread AAA"
  11. Java stack information for the threads listed above:
  12. ===================================================
  13. "thread AAA":
  14. at DeadLock.holdLockThread.run(DeadLockTest.java:22)
  15. - waiting to lock <0x0000000787e71cb8> (a java.lang.String)
  16. - locked <0x0000000787e71c88> (a java.lang.String)
  17. at java.lang.Thread.run(java.base@13/Thread.java:830)
  18. "thread BBB":
  19. at DeadLock.holdLockThread.run(DeadLockTest.java:22)
  20. - waiting to lock <0x0000000787e71c88> (a java.lang.String)
  21. - locked <0x0000000787e71cb8> (a java.lang.String)
  22. at java.lang.Thread.run(java.base@13/Thread.java:830)
  23. Found 1 deadlock.

JUC并发基础的更多相关文章

  1. Java多线程与并发基础

    CS-LogN思维导图:记录专业基础 面试题 开源地址:https://github.com/FISHers6/CS-LogN 多线程与并发基础 实现多线程 面试题1:有几种实现线程的方法,分别是什么 ...

  2. JAVA多线程和并发基础面试问答(转载)

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

  3. [转] JAVA多线程和并发基础面试问答

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

  4. JAVA多线程和并发基础面试问答

    转载: JAVA多线程和并发基础面试问答 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一.在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对 ...

  5. 【多线程】JAVA多线程和并发基础面试问答(转载)

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

  6. Java 并发基础

    Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...

  7. (转)JAVA多线程和并发基础面试问答

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

  8. Java笔记(十四) 并发基础知识

    并发基础知识 一.线程的基本概念 线程表示一条单独的执行流,它有自己的程序计数器,有自己的栈. 1.创建线程 1)继承Thread Java中java.lang.Thread这个类表示线程,一个类可以 ...

  9. java并发基础(五)--- 线程池的使用

    第8章介绍的是线程池的使用,直接进入正题. 一.线程饥饿死锁和饱和策略 1.线程饥饿死锁 在线程池中,如果任务依赖其他任务,那么可能产生死锁.举个极端的例子,在单线程的Executor中,如果一个任务 ...

随机推荐

  1. iview使用之怎样通过render函数在tabs组件中添加标签

    在实际项目开发中我们通常会遇到一些比较'新颖'的需求,而这时iview库里往往没有现成可用的组件示例,所以我们就需要自己动手翻阅IviewAPI进行自定义一些组件,也可以说是将iview库里的多种组件 ...

  2. Linux网络编程(2)

    Preview 基于上一篇博客,本文将继续展开TCP面向连接的,客户端以及服务端各自需要进行的操作,我们按照真实TCP连接的顺序,分别阐述客户端socket(), connect()以及服务端sock ...

  3. Centos8安装docker-compose

    一.首先检查是否有pip 执行命令:piv -V 二.更新pip 执行命令:pip install --upgrade pip 三.下载  setuptools 执行命令 :pip install - ...

  4. tp5--路由的使用(初级)

    在配置文件夹下的route.php文件配置路由: 控制器: 运行结果:

  5. (转)如何学好C语言

    原文:http://coolshell.cn/articles/4102.html    作者:陈皓 有人在酷壳的留言版上询问下面的问题 keep_walker : 今天晚上我看到这篇文章. http ...

  6. js 函数的多图片预加载(preload) 带插件版完整解析

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         本篇文章为您分析一下原生JS实现图片预加载效果 本篇文章写的 ...

  7. js之用IndexOf返回指定字符串的次数

    代码 var Str = "strs,strs,stras,str,strs,strs"; var subStr ="strs" ; var count = 0 ...

  8. JavaScript面向对象的作用域链(转载)

    JavaScript的作用域一直以来是前端开发中比较难以理解的知识点,对于JavaScript的作用域主要记住几句话,走遍天下都不怕... 一.“JavaScript中无块级作用域” 在Java或C# ...

  9. LinearLayout控件

    LinearLayout是线性布局控件,它包含的子控件将以横向或竖向的方式排列,按照相对位置来排列所有的widgets或者其他的containers,超过边界时,某些控件将缺失或消失.因此一个垂直列表 ...

  10. 详解如何使用gulp实现项目在浏览器中的自动刷新

    情况描述: 我们很容易遇到这样一种情况: 我们并不是一开始就规划好了整个项目,比如可能接手别人的项目或者工程已经手动创建好了,现在要想利用gulp来实现浏览器自动刷新,那么如何做呢? 其实非常简单,本 ...