JUC并发基础

一.Volatile

0.基础知识

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

JMM:Java Memory Model

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

可见性:

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

为什么要使用缓存?

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

    使用缓存造成的问题?

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

原子性:

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

```java

n++;
// 1.执行getfield(getstatic)获得原始值
// 2.执行iadd 进行加1操作
// 3.执行putfield写 把累加后的值写回
```

​ 保证原子性:

   - synchronized
   - JUC(atomic)

有序性:

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

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

```java

1.源代码
2.编译器优化重排
3.指令并行重排
4.内存系统重排
5.最终执行的指令
```

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

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

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

当一个变量定义为Volatile后:

  • 可见性,对所有线程的可见性
  • 有序性,禁止对指令进行重排序优化
  • 非原子性,不能保证原子性操作
class  MyData{
    volatile int number = 0;
    void setNumber(){
        this.number = 60;
    }
    void addNumber(){
        number ++;
    }
    // juc 来保证原子性
    AtomicInteger atomicInteger=new AtomicInteger(0);
    void addAtomic(){
        atomicInteger.getAndIncrement();
    }
}
public class VolatileVisible {
    public static void main(String[] args) {
        //VisibleByVolatile();
        AtomicNotByVolatile();
    }
    /**
     *  volatile 不保证原子性
     */
    public static void AtomicNotByVolatile() {
        MyData myData=new MyData();
        for(int i=1;i<=20;i++){
            new Thread(()->{
                for(int j=0;j<1000;j++){
                    myData.addNumber();
                    myData.addAtomic();
                }
            },String.valueOf(i)).start();
        }
        // 等待线程完全运行
        while(Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName()
                           +"\t finally number value "+myData.number);
        System.out.println(Thread.currentThread().getName()
                           +"\t atomic number "+myData.atomicInteger);
    }
    /**
     *   volatile 保证可见性
     */
    public static void VisibleByVolatile() {
        MyData myData=new MyData();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"\t come in");
            try{
                TimeUnit.SECONDS.sleep(3);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            myData.setNumber();
            System.out.println(Thread.currentThread().getName()
                               +"\t updated number value  "+ myData.number);
        },"aaa").start();

        // 等待循环
        while(myData.number==0){

        }
        System.out.println(Thread.currentThread().getName()
                           +"\t mission is over "+myData.number);
    }
}
3.volatile的应用

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

  • DCL双端检测机制不一定安全,存在指令重排序的可能(多次运行构造方法)
  • Volatile ,将变量voaltile定义后可以保证单例模式的正确
public class SingletonTest {

    private volatile static SingletonTest instance=null;

    private SingletonTest(){
        System.out.println(Thread.currentThread().getName()+"\t 构造方法");
    }
    // 懒加载模式不能保证在并发情况下的单例
    private static SingletonTest getInstance(){
        if(instance==null){
            instance=new SingletonTest();
        }
        return instance;
    }
    // DCL模式 双端检锁模式   instance需要表示为volatile
    // 防止指令进行重排
    private static SingletonTest DCLGetInstance(){
        if(instance==null){
            synchronized (SingletonTest.class){
                if(instance==null){
                    instance=new SingletonTest();
                }
            }
        }
        return instance;
    }
    public static void main(String[] args) {
        for(int i=1;i<=10;i++){
            new Thread(()->{
                SingletonTest.DCLGetInstance();
            }).start();
        }
    }
}

二.CAS

0.CAS的定义

CompareAndSwap 比较并交换

CompareAndSwap 是一条并发原语

  • 判断内存某个位置的值是否为预期值,如果是则更改为新的值
  • 原语的执行必须是连续的,执行过程中不允许被打断
  • CAS是CPU的原子指令,不会造成数据不一致性问题
// java.util.concurrent.atomic
// AtomicInteger.compareAndSet()
public final boolean compareAndSet(int expectedValue, int newValue) {
        return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
    }

// Unsafe 类下的native方法
public final native boolean compareAndSetInt(Object o, long offset,
                                                 int expected,int x);

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

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

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

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

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

// AtomicInteger.java
private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe();
private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value");

public final int getAndIncrement() {
        return U.getAndAddInt(this, VALUE, 1);
}

// Unsafe.java
public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            // 先根据内存偏移地值 来取到值
            v = getIntVolatile(o, offset);
        // 如果这个值在运行期间被其他值修改,重新进行读取这个值,
        // 保证这个值相等,才会进行增加操作
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
}
2.CAS的缺点
  • 循环时间长,CAS不成功,会给CPU带来很大的开销
  • 只能保证一个共享变量的原子操作
  • ABA问题
3.ABA问题
  • 什么是ABA问题?

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

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

  • 如何解决ABA问题

    1)原子引用

    2)传统的互斥同步

  • 原子引用

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

三.集合类并发安全

0.问题描述

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

public class ContainerNotSafeTest {
    public static void main(String[] args) {
        listNotSafe();

        mapSafe();
    }
    public static void mapSafe() {
        Map<String,String> map=new ConcurrentHashMap<>();//new HashMap<>();
        for(int i=1;i<=30;i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().toString(), UUID.randomUUID().toString().substring(0, 8));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
    public static void listNotSafe() {

        //List<String> list= Arrays.asList("a","b","c");
        //list.forEach(System.out::println);
        List<String> list=new CopyOnWriteArrayList<>();

        for(int i=1;i<=30;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,8));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}
  • Java.util.ConcurrentModificationException
1.故障现象
  • 并发情况下常出现的 并发修改异常
private void checkForComodification(final int expectedModCount) {
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
}
2.导致原因
  • 并发环境下,线程对数据进行修改导致
3.解决方法
  • 1.使用线程安全的集合类 Vector
List<String> list=new Vector<>();

// 加锁synchronized来实现并发安全
public synchronized boolean add(E e) {
        modCount++;
        add(e, elementData, elementCount);
        return true;
}

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

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

static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;
        final Collection<E> c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize
  
        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            mutex = this;
        }

        SynchronizedCollection(Collection<E> c, Object mutex) {
            this.c = Objects.requireNonNull(c);
            this.mutex = Objects.requireNonNull(mutex);
        }
        // 实现互斥同步
        public int size() {
            synchronized (mutex) {return c.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return c.isEmpty();}
        }
        public boolean contains(Object o) {
            synchronized (mutex) {return c.contains(o);}
        }
        public Object[] toArray() {
            synchronized (mutex) {return c.toArray();}
        }
 }
  • 拓展知识 2 : 工厂模式
    • 简单工厂
    • 抽象工厂
    • 工厂方法

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

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

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

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

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

    // java.util.concurrent.CopyOnWriteArrayList;
    List<String> list= new CopyOnWriteArrayList<>();
    
    // 源码
    final transient Object lock = new Object();
    // 在写的时候进行加锁 
    public boolean add(E e) {
            synchronized (lock) {
                Object[] es = getArray();
                int len = es.length;
                es = Arrays.copyOf(es, len + 1);
                es[len] = e;
                setArray(es);
                return true;
            }
     }
    

四.Java锁

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

ReentrantLock 默认就是非公平锁

Synchronized 也是非公平锁

// 默认的无参构造函数就是非公平锁
public ReentrantLock() {
        sync = new NonfairSync();
}
// 传入True就是公平锁
public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
}

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

1.可重入锁(递归锁)

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

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

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

// synchronized
class Phone{
    // 同一线程外层函数获得锁之后,内层递归函数仍获取该锁代码
    public synchronized  void sendSMS() throws  Exception{
        System.out.println(Thread.currentThread().getId()+"\t invoked sendSMS");
        // 内层函数
        sendEmail();
    }
    public synchronized void sendEmail() throws Exception{
        System.out.println(Thread.currentThread().getId()+"\t invoked sendEmail");
    }
}
// ReentrantLock
class Person implements Runnable{
    @Override
    public void run() {
        getPerson();
    }
    Lock lock=new ReentrantLock();
    private void getPerson(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"\t invoked get person");
            setPerson();
        }finally {
            lock.unlock();
        }
    }
    private void setPerson(){
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName()+"\t invoked set person");
        }finally {
            lock.unlock();
        }
    }
}
/**
 * 可重入锁
 */
public class ReentrantLockTest {
    public static void main(String[] args) {
        Phone phone=new Phone();
        Person person=new Person();
        new Thread(()->{
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"thread1").start();

        new Thread(()->{
            try {
                phone.sendSMS();
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"thread2").start();

        System.out.println("==========================");

        Thread thread3=new Thread(person);
        Thread thread4=new Thread(person);
        thread3.start();
        thread4.start();
    }
}
// 运行结果
/**
Thread-0     invoked get person
Thread-0     invoked set person
Thread-1     invoked get person
Thread-1     invoked set person
12   invoked sendSMS
12   invoked sendEmail
13   invoked sendSMS
13   invoked sendEmail
/
2.自旋锁

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

  • 减少线程上下文切换的消耗
  • 循环会消耗CPU机能
/**
 * 自旋锁 spinLock
 */
public class SpinLockTest {
    private AtomicReference<Thread> atomicReference=new AtomicReference<>();

    private void myLock(){
        Thread thread=Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t thread come in myLock");
        // 旋转
        // A 线程调用后   B线程一直执行循环等待
        while(!atomicReference.compareAndSet(null,thread)){

        }
    }
    private void myUnLock(){
        Thread thread=Thread.currentThread();
        atomicReference.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"\t invoked the myUnLock");
    }

    public static void main(String[] args) {

        SpinLockTest spinLockTest=new SpinLockTest();

        new Thread(()->{
            spinLockTest.myLock();
            // 暂停一会线程 让后面的线程无法取到锁 实现自旋
            try{ TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
            spinLockTest.myUnLock();
        },"A").start();

        // 暂停一会 A线程先于B线程运行
        try{ TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}

        new Thread(()->{
            spinLockTest.myLock();
            spinLockTest.myUnLock();
        },"B").start();
    }
}
3.读写锁/互斥锁
  • 读锁(共享锁),该锁可以被多个线程所持有
  • 写锁(独占锁),该锁一次只能被一个线程所持有
  • 互斥锁,读写,写写都是互斥的
// 线程操作的资源类
class MyCache{
    private volatile Map<String,Object> map=new HashMap<>();
    // 重入锁
    private Lock lock=new ReentrantLock();
    // 读写锁
    private ReentrantReadWriteLock rwLock=new ReentrantReadWriteLock();
    void put(String key, Object value) {
        // 写锁
        rwLock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t 正在写入" + key);
            //  暂停线程模拟 资源
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rwLock.writeLock().unlock();
        }
    }
    void get(String key){
        rwLock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"\t 正在读取"+key);
            //  暂停线程模拟资源
            try {
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result=map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成"+result);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rwLock.readLock().unlock();
        }
    }
}
/**
 * 读写锁
 * 读取共享资源可以同时并发的进行
 * 写资源 就需要一个线程独占
 *
 * 写操作:原子+独占
 */
public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCache myCache=new MyCache();
        // 写线程
        for(int i=0;i<5;i++){
            int finalI = i;
            new Thread(()->{
                myCache.put(finalI +"", finalI +"");
            },String.valueOf(i)).start();
        }
        // 保证写入完全后,再进行读取
        try{TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e){e.printStackTrace();}
        // 读线程
        for(int i=0;i<5;i++){
            int finalI = i;
            new Thread(()->{
                myCache.get(finalI+"");
            },String.valueOf(i)).start();
        }
    }
}

五.J.U.C-AQS

0.AQS- Abstract Queued Synchronizer

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

  • ReentrantLock
  • Semaphore
  • CountDownLatch
  • ReentrantReadWriteLock
  • FutureTask
// AOS 实现类的体现
public class ReentrantLock implements Lock, java.io.Serializable {
    /** Synchronizer providing all implementation mechanics */
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
    }
// 
public class Semaphore implements java.io.Serializable {
    /** All mechanics via AbstractQueuedSynchronizer subclass */
    private final Sync sync;
    /**
     * Synchronization implementation for semaphore.  Uses AQS state
     * to represent permits. Subclassed into fair and nonfair
     * versions.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
    }
1.Semaphore信号量

信号量可以代替Synchronized和Lock

  • 用于多个共享资源的的互斥作用
  • 并发线程数的控制
public class SemaphoreTest {
    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()+"\t 抢到车位");
                    try{ TimeUnit.SECONDS.sleep(3); }catch (InterruptedException ie){ie.printStackTrace();}
                    System.out.println(Thread.currentThread().getName()+"\t 停车3s后离开车位");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }

    }
}
/**
 * 0     抢到车位
 * 1     抢到车位
 * 2     抢到车位
 * 2     停车3s后离开车位
 * 1     停车3s后离开车位
 * 0     停车3s后离开车位
 * 4     抢到车位
 * 5     抢到车位
 * 3     抢到车位
 * 3     停车3s后离开车位
 * 5     停车3s后离开车位
 * 4     停车3s后离开车位
 */

六.阻塞队列

0.BlockingQueue阻塞队列

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

常见的实现类

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

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

    //一个很重要的特性,默认的队列长度是 Integer.MAX_VALUE
    public LinkedBlockingQueue() {
            this(2147483647);
    }
    public LinkedBlockingQueue(int capacity) {
            this.count = new AtomicInteger();
            this.takeLock = new ReentrantLock();
            this.notEmpty = this.takeLock.newCondition();
            this.putLock = new ReentrantLock();
            this.notFull = this.putLock.newCondition();
            if (capacity <= 0) {
                throw new IllegalArgumentException();
            } else {
                this.capacity = capacity;
                this.last = this.head = new LinkedBlockingQueue.Node((Object)null);
            }
     }
    
  • 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操作,否则不能继续添加元素。

/**
 * 不存储元素的队列
 */
public class SynchronousQueueTest {
    public static void main(String[] args)  {
        BlockingQueue<String> blockingQueue=new SynchronousQueue<>();
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"\t"+"put 1");
                blockingQueue.put("1");

                System.out.println(Thread.currentThread().getName()+"\t"+"put 2");
                blockingQueue.put("2");

                System.out.println(Thread.currentThread().getName()+"\t"+"put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        new Thread(()->{
            try{
                TimeUnit.SECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take());

                TimeUnit.SECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take());

                TimeUnit.SECONDS.sleep(5);
                System.out.println(Thread.currentThread().getName()+"\t"+blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"B").start();
    }
}
3.阻塞队列的应用
  • 1.生产消费这模型
  • 2.线程池
  • 3.消息中间件

生产者消费者模型

  • 传统的加锁版本

    sync+wait+notify 是一组

    lock+await+singalAll 是一组

    class ShareData{
        private int number=0;
        private Lock lock=new ReentrantLock();
        private Condition condition=lock.newCondition();
    
        void increment() throws InterruptedException {
            // 加锁
            lock.lock();
            try {
                // 判断
                // 防止虚假唤醒
                while(number!=0){
                    // 等待
                    condition.await();
                }
                // 操作
                number++;
                System.out.println(Thread.currentThread().getName()+"\t"+number);
    
                // 通知唤醒
                condition.signalAll();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
        void decrement() throws Exception{
            // 加锁
            lock.lock();
            try {
                // 判断
                // 防止虚假唤醒
                while(number == 0){
                    // 等待
                    condition.await();
                }
                // 操作
                number --;
                System.out.println(Thread.currentThread().getName()+"\t"+number);
    
                // 通知唤醒
                condition.signalAll();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
    
    /**
     * 传统的生产者消费者模式
     *
     * 两个线程交替操作,一个加1,一个减1
     */
    
    public class ConsumerProducerTest {
        public static void main(String[] args) {
            ShareData shareData=new ShareData();
    
            // 两个线程表示 生产者和消费者
            new Thread(()->{
                for(int i=0;i<5;i++){
                    try {
                        shareData.increment();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"A").start();
    
            new Thread(()->{
                for(int i=0;i<5;i++){
                    try {
                        shareData.decrement();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            },"B").start();
        }
    }
    

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) ,机器上可用的处理器个数作为其并行级别
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
  
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
3.线程池的创建

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

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
                              TimeUnit unit,BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

线程池的重要参数:

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

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

线程池流程图

线程池的拒绝策略

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

RejectedExecutionHandler

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

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

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

  • DiscardPolicy , 直接丢弃任务

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

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

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

八.死锁及定位分析

死锁:

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

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

class holdLockThread implements Runnable{
    private String lockA;
    private String lockB;

    public holdLockThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }
    @Override
    public void run() {

        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockA+"\t 尝试获取"+lockB);
            // 模拟线程操作
            try{ TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockB+"\t 尝试获取"+lockA);
            }
        }
    }
}
/**
 * 死锁
 * 如何定位死锁的位置
 *  ps -ef|grep ***
 * jps java ps
 * jps -l 定位进程号
 * jstack 进程号
 * 找到死锁查看
 */
public class DeadLockTest {
    public static void main(String[] args) {
        String lockA="lockA";
        String lockB="lockB";

        new Thread(new holdLockThread(lockA,lockB),"thread AAA").start();
        new Thread(new holdLockThread(lockB,lockA),"thread BBB").start();
    }
}

解决死锁问题:

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

dengshuodengshuo@dengMac  ~/Code/Project/JVM/src/DeadLock  jps -l
9094 
12250 DeadLock.DeadLockTest
12251 org.jetbrains.jps.cmdline.Launcher
12252 jdk.jcmd/sun.tools.jps.Jps

dengshuodengshuo@dengMac  ~/Code/Project/JVM/src/DeadLock  jstack 12250
2020-03-04 09:28:05
Found one Java-level deadlock:
=============================
"thread AAA":
  waiting to lock monitor 0x000000010a140a00 (object 0x0000000787e71cb8, a java.lang.String),
  which is held by "thread BBB"
"thread BBB":
  waiting to lock monitor 0x0000000100b1ae00 (object 0x0000000787e71c88, a java.lang.String),
  which is held by "thread AAA"

Java stack information for the threads listed above:
===================================================
"thread AAA":
        at DeadLock.holdLockThread.run(DeadLockTest.java:22)
        - waiting to lock <0x0000000787e71cb8> (a java.lang.String)
        - locked <0x0000000787e71c88> (a java.lang.String)
        at java.lang.Thread.run(java.base@13/Thread.java:830)
"thread BBB":
        at DeadLock.holdLockThread.run(DeadLockTest.java:22)
        - waiting to lock <0x0000000787e71c88> (a java.lang.String)
        - locked <0x0000000787e71cb8> (a java.lang.String)
        at java.lang.Thread.run(java.base@13/Thread.java:830)

Found 1 deadlock.
posted @ 2020-05-13 20:58  dengshuo7412  阅读(153)  评论(0编辑  收藏  举报