JUC并发编程

JUC并发编程

1、什么是JUC

在Java中,线程部分是一个重点,本篇文章说的JUC也是关于线程的. JUC就是java.util.concurrent工具包的简称.。这是一个处理线程的工具包, JDK1.5开始出现的。

2、线程和进程

  • 进程(process) 是计算机中的程序关于某数据集合三的一次运行或动, 是系统进行资源分配和调度的基本单位, 是操作系统接口的基础. 在当代面向线程设计的计算机结构中, 进程是线程的容器. 程序是指令, 数据及其组织形式的描述, 进程是程序的实体. 是计算机中的程序关于某数据集合上的一次运行活动, 是系统进行资源分配和调度的基本单位, 是操作系统结构的基础. 程序是指令, 数据及其组织形式的描述, 进程是程序的实体.
  • 线程(thread) 是操作系统能够进行运算的最小单位. 他被包含在进程之中, 是进程中的实际运作单位. 一条线程指的是进程中一个单一顺序的控制流, 一个进程中可以并发多个线程,每个线程并行执行不同的任务.

总结来说:

  • 进程: 指在系统中正在运行的一个应用程序; 程序一单运行就是进程; 程序一旦进行就是进程; 进程-----资源分配的最小单位.
  • 线程: 系统分配处理器时间资源的基本单元, 或者说进程之内独立执行的一个单元执行流. 线程-----程序执行的最小单位

线程的枚举状态

public enum State {
        // 新生
        NEW,

        // 执行
        RUNNABLE,

        // 阻塞
        BLOCKED,

        // 等待,死等
        WAITING,

        // 超时等待,过时不候
        TIMED_WAITING,

        // 终止
        TERMINATED;
    }

wait和sleep的区别

(1) 来自不同的类

wait -> Object类

sleep -> Thread类

(2) 关于锁的释放

wait会释放锁,sleep不会释放,抱着锁睡觉了

(3) 适用范围

wait 只能用在同步代码块中

sleep 可以在任何地方使用

(4) 是否需要捕获异常

wait 不需要捕获异常

sleep 需要捕获异常

3、Lock(锁)

传统Synchronized锁

本质就是一个排队拿锁的机制

Lock锁
Synchronized 和 Lock 区别

1、Synchronized 是内置的Java关键字; Lock 是一个Java类

2、Synchronized 无法判断获取锁的状态;Lock 可以判断是否获取到锁

3、Synchronized 会自动释放锁;Lock 必须要手动释放,若不释放则会造成死锁

4、Synchronized 线程1(获得锁,阻塞),线程2(等待,死等);Lock锁就不一定会一直等待下去

5、Synchronized 可重入锁,不可以中断,非公平锁;Lock,可重入锁,可以判断锁,非公平锁(可以自己设置)

6、Synchronized 适合锁少量的代码同步问题;Lock适合锁大量的同步代码

锁是什么?如何判断锁的是谁?

4、生产者和消费者问题

生产者消费者问题,Synchronized版

package com.shan.test;

/*
* 生产者消费者模式
* */
public class TestPC {
    public static void main(String[] args) {

        final Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"D").start();

    }
}

//资源类
class Data {
    // 属性
    private Integer number = 0;

    //方法
    public synchronized void increment() throws InterruptedException {
        if (number != 0) {
            //等待
            this.wait();
        }
        number ++;
        System.out.println(Thread.currentThread().getName() + "-->" + number);
        //加完之后通知其他线程开启
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {
            //等待
            this.wait();
        }
        number --;
        System.out.println(Thread.currentThread().getName() + "-->" + number);
        //减完之后通知其他线程开启
        this.notifyAll();
    }
}

问题:存在A B C D 四个线程!虚假唤醒

if 改为 while

package com.shan.test;

/*
* 生产者消费者模式
* */
public class TestPC {
    public static void main(String[] args) {

        final Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"D").start();

    }
}

//资源类
class Data {
    // 属性
    private Integer number = 0;

    //方法
    public synchronized void increment() throws InterruptedException {
        while (number != 0) {
            //等待
            this.wait();
        }
        number ++;
        System.out.println(Thread.currentThread().getName() + "-->" + number);
        //加完之后通知其他线程开启
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            //等待
            this.wait();
        }
        number --;
        System.out.println(Thread.currentThread().getName() + "-->" + number);
        //减完之后通知其他线程开启
        this.notifyAll();
    }

}

JUC 版的生产者消费者模式

package com.shan.test;

/*
* 生产者消费者模式
* */
public class TestPC {
    public static void main(String[] args) {

        final Data data = new Data();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"D").start();

    }
}

//资源类
class Data {
    // 属性
    private Integer number = 0;
    
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //方法
    public synchronized void increment() throws InterruptedException {
        while (number != 0) {
            //等待
            condition.await();
        }
        number ++;
        System.out.println(Thread.currentThread().getName() + "-->" + number);
        //加完之后通知其他线程开启
        condition.signalAll();
    }

    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {
            //等待
            condition.await();
        }
        number --;
        System.out.println(Thread.currentThread().getName() + "-->" + number);
        //减完之后通知其他线程开启
        condition.signalAll();
    }

}

Condition能精准的唤醒特定线程

package com.shan.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
* JUC版生产者消费者模式
* */
public class demo2 {
    public static void main(String[] args) {
        Data2 data2 = new Data2();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data2.printA();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data2.printB();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data2.printC();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}

class Data2{ //资源类->属性,方法
    //1、new一个Lock对象
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition(); //number = 1 -> A启动
    Condition condition2 = lock.newCondition(); //number = 2 -> B启动
    Condition condition3 = lock.newCondition(); //number = 3 -> C启动
    private Integer number = 1;

    //方法
    public void printA() {
        lock.lock();//首先加个锁
        try {
            //业务代码,判断 -> 执行 -> 通知
            while (number != 1) { //首先根据条件判断
                //等待
                condition1.await(); //JUC版使用 Lock的等待,对应 Synchronized的 wait
            }
            //通知下一个进程启动
            System.out.println(Thread.currentThread().getName() + "----> AAAAAA");
            number = 2;
            //唤醒指定的 B线程
            condition2.signal(); //精准控制线程的启动顺序
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();//执行完成后解锁
        }
    }

    public void printB() {
        lock.lock();//首先加个锁
        try {
            //业务代码,判断 -> 执行 -> 通知
            while (number != 2) { //首先根据条件判断
                //等待
                condition2.await(); //JUC版使用 Lock的等待,对应 Synchronized的 wait
            }
            //通知下一个进程启动
            System.out.println(Thread.currentThread().getName() + "----> BBBBBB");
            number = 3;
            //唤醒指定的 C线程
            condition3.signal(); //精准控制线程的启动顺序
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();//执行完成后解锁
        }
    }

    public void printC() {
        lock.lock();//首先加个锁
        try {
            //业务代码,判断 -> 执行 -> 通知
            while (number != 3) { //首先根据条件判断
                //等待
                condition3.await(); //JUC版使用 Lock的等待,对应 Synchronized的 wait
            }
            //通知下一个进程启动
            System.out.println(Thread.currentThread().getName() + "----> CCCCCC");
            number = 1;
            //唤醒指定的 A线程
            condition1.signal(); //精准控制线程的启动顺序
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();//执行完成后解锁
        }
    }

}

5、8锁机制

package com.shan.Lock_8q;

import java.util.concurrent.TimeUnit;

/**
 * 8锁问题,实际上就是关于锁的 8个问题
 * 1、在标准情况下,先打印 发短信?还是 打电话?  --> 先打印发短信
 * 2、sendSms延迟4s先打印 发短信?还是 打电话? --> 先打印发短信
 * */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"B").start();

    }
}

class Phone {
    public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call() {
        System.out.println("打电话");
    }
}
package com.shan.Lock_8q;

import java.util.concurrent.TimeUnit;

/*
* 3、增添一个普通方法,先打印 发短信?还是 hello?  --> 先打印 hello
* 4、两个对象,两个同步方法,先打印 发短信?还是 打电话?  --> 先打印打电话
 * */
public class Test2 {
    public static void main(String[] args) {

        // 两个不同的对象,两个调用者,两把不同的锁
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();

        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();

    }
}

class Phone2 {
    //synchronized 锁的对象是方法的调用者
    //两个方法拿到的是同一个锁,谁先拿到谁先执行
    public synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call() {
        System.out.println("打电话");
    }

    // 普通方法没有加锁,不是同步方法,不受锁的影响
    public void hello() {
        System.out.println("hello");
    }
}
package com.shan.Lock_8q;

import java.util.concurrent.TimeUnit;

/*
* 5、增加两个静态的同步方法,先打印 发短信?还是 打电话?  --> 发短信
* 6、两个对象,两个静态的同步方法,先打印 发短信?还是 打电话?  --> 发短信
* */
public class Test3 {
    public static void main(String[] args) {

        // 两个对象的 class类模板只有一个,static锁的是 class
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();

        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();

    }
}

//Phone3 唯一的 class对象
class Phone3 {
    //synchronized 锁的对象是方法的调用者
    //两个方法拿到的是同一个锁,谁先拿到谁先执行
    //static 静态方法
    //类一加载就有了,锁的是 class
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public static synchronized void call() {
        System.out.println("打电话");
    }

}
package com.shan.Lock_8q;

import java.util.concurrent.TimeUnit;
/*
* 7、一个普通的Synchronized方法,和一个静态类的方法,先打印 发短信?还是 打电话? --> 打电话
* 8、两个对象,一个普通的Synchronized方法,和一个静态类的方法,先打印 发短信?还是 打电话? --> 打电话
* */
public class Test4 {
    public static void main(String[] args) {

        // 两个对象的
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        //锁的存在
        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();

    }
}

//Phone3 唯一的 class对象
class Phone4 {
    //静态类的方法,锁的是 class类模板
    public static synchronized void sendSms() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    //普通的 static方法,锁的是调用者
    public synchronized void call() {
        System.out.println("打电话");
    }
}

小结

new this 锁的是一个具体的对象

static class 锁的是一个类模板

6、集合类不安全

List不安全

package com.shan.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

//java.util.ConcurrentModificationException 并发修改异常
public class Test1 {
    public static void main(String[] args) {
        //单线程
        //List<String> list = Arrays.asList("1","2","3");
        //list.forEach(System.out::println);

        //并发状态下,ArrayList不安全
        /*
        * 解决方案:
        * 1、List<String> list = new Vector<>();
        * 2、List<String> list = Collections.synchronizedList(new ArrayList<>());
        * 3、List<String> list = new CopyOnWriteArrayList<>();
        * */
        //CopyOnWrite 写入时复制  COW 计算机程序设计领域中一种优化策略
        //多个线程操作,list,在读取的时候是固定的,写入的时候可能会存覆盖操作
        //在写入的时候避免覆盖,造成数据问题
        //读写分离
        //CopyOnWrite 比 Vector 好在哪里?
        /*
        * 源码分析:
        * ----------------------------------------------------------------------------------
        public boolean add(E e) {
            final ReentrantLock lock = this.lock;
            lock.lock();
            try {
                Object[] elements = getArray();
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len + 1);
                newElements[len] = e;
                setArray(newElements);
                return true;
            } finally {
                lock.unlock();
            }
        }
        * ----------------------------------------------------------------------------------
        public synchronized void addElement(E obj) {
            modCount++;
            ensureCapacityHelper(elementCount + 1);
            elementData[elementCount++] = obj;
        }
        * ----------------------------------------------------------------------------------
        * 可以看出vector中的add方法加了一个Synchronized锁,而CopyOnWrite方法用的是一个 Lock锁
        * */
        List<String> list1 = new Vector<>();
        List<String> list2 = Collections.synchronizedList(new ArrayList<>());
        List<String> list3 = new CopyOnWriteArrayList<>();

        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list3.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list3);
            }, String.valueOf(i)).start();
        }
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Set不安全

package com.shan.unsafe;

import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

// ConcurrentModificationException
public class TestSet {

    public static void main(String[] args) {
        //Set<String> set = new HashSet<>();

        Set<String> set = Collections.synchronizedSet(new HashSet<>());

        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

Map不安全

package com.shan.unsafe;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

//ConcurrentModificationException
public class TestMap {
    public static void main(String[] args) {
        //Map是这样用的吗? --> 不是,工作中不适用 HashMap
        //Map<String, Object> map = new HashMap<>();
        //并发安全 ConcurrentHashMap(),支持检索的完全并发性和更新的高预期并发性的哈希表。
        Map<String, Object> map = new ConcurrentHashMap(); 
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7、callable

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 可以返回结果
  2. 可以抛出异常
  3. 实现方法不同 callable的实现方法为call()
package com.shan.callable;

import sun.util.resources.cldr.ar.CalendarData_ar_LB;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestCallable {
    public static void main(String[] args) {
        //new Thread(new Runnable() -> FutureTask() -> FutureTask(Callable<V> callable)).start();
        //new Runnable() -> FutureTask() -> FutureTask(Callable<V> callable)
        // 因为有这样一段包含关系,所以Runnable可以调用 call()方法
        new Thread().start();
        Test test = new Test();
        FutureTask futureTask = new FutureTask(test); //适配类
        new Thread(futureTask, "A").start();
        try {
            Object o = futureTask.get(); //获取callable的返回结果
            System.out.println(o);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

    }
}

class Test implements Callable<String> {
    @Override
    public String call() {
        System.out.println("call()方法");
        return "hello";
    }
}

8、常用的辅助类

(1)CountDownLatch

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

简单来说就是一个计数器,当必须要执行的线程走完,计数器归零之后,再向下执行。

测试代码

package com.shan.Utils;

import java.util.concurrent.CountDownLatch;

public class CountDownLaunchDemo {
    public static void main(String[] args) throws InterruptedException {
        //CountDownLatch常常适用于 必须要执行的任务
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " --> Go Out");
                countDownLatch.countDown(); //调用一次 countDown(),计数器减一
            }, String.valueOf(i)).start();
        }
        //等待计数器归零才能向下执行
        countDownLatch.await();//若不添加等待,则会出现线程还没跑完就继续向下执行的问题
        System.out.println("Door close!");
    }
}

(2)CyclicBarrier

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

简单来说,就是一个加法器,当线程数量完成到指定数量时,才能执行下一步

测试代码

package com.shan.Utils;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("成功召唤神龙!");
        });
        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            new Thread(() -> {
                System.out.println("拿到了第" + temp + "个龙珠");
                try {
                    cyclicBarrier.await();//等待七个线程执行完
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

(3)Semaphore

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码测试

package com.shan.Utils;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        //假设有 3个停车位
        Semaphore semaphore = new Semaphore(3);
        //6辆车
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "-->抢到了停车位");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName() + "-->离开了停车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

9、ReadWriteLock(读写锁)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

代码测试

package com.shan.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 独占锁(写锁):一次只能被一个线程占有
 * 共享锁(读锁):多个线程可以同时占有
 * ReadWriteLock:
 * 读-读,可以共存
 * 读-写,不能共存
 * 写-写,不能共存
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {

        MyCache myCache = new MyCache();
        MyCacheLock myCacheLock = new MyCacheLock();

        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(()->{
                //只执行写操作
                myCacheLock.set(temp+"", temp+"");
            },String.valueOf(i)).start();
        }
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(()->{
                //只执行读操作
                myCacheLock.get(temp+"");
            },String.valueOf(i)).start();
        }

    }
}

//加锁后
class MyCacheLock {

    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //插入,写操作
    public void set(String key, Object value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "-->正在写入");
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "-->写入完成");
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }

    }

    //读取,读操作
    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "-->正在读取");
            map.get(key);
            System.out.println(Thread.currentThread().getName() + "-->读取完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }

    }
}

//自定义缓存
class MyCache {

    private volatile Map<String, Object> map = new HashMap<>();

    //插入,写操作
    public void set(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "-->正在写入");
        map.put(key,value);
        System.out.println(Thread.currentThread().getName() + "-->写入完成");
    }

    //读取,读操作
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "-->正在读取");
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "-->读取完成");
    }
}

10、阻塞队列

什么情况下我们会使用阻塞队列? --> 多线程并发处理;线程池

学会使用队列 (添加 / 移除)

四组API

方法抛出异常返回结果阻塞等待超时等待
写入add()offer()put()offer( E e, long timeout, TimeUnit unit)
读出remove()poll()take()poll(long timeout, TimeUnit unit)
判断队首元素element()peek()--
/**
 * 抛出异常
 */
public static void test1() {
    BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    //存入队列
    System.out.println(blockingQueue.add("a"));
    System.out.println(blockingQueue.add("b"));
    System.out.println(blockingQueue.add("c"));
    // 若多添加一个元素,则会报错 IllegalStateException: Queue full
    //System.out.println(blockingQueue.add("d"));

    System.out.println(blockingQueue.element());//判断队首元素
    System.out.println("=================");

    //取出元素 (FIFO)
    System.out.println(blockingQueue.remove());
    System.out.println(blockingQueue.remove());
    System.out.println(blockingQueue.remove());
    // 若多添加一个元素,则会报错 NoSuchElementException
    //System.out.println(blockingQueue.remove());
}
/**
 * 不抛出异常,返回结果
 */
public static void test2() {
    BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    //存入队列
    System.out.println(blockingQueue.offer("a"));
    System.out.println(blockingQueue.offer("b"));
    System.out.println(blockingQueue.offer("c"));
    // 若多添加一个元素,会返回一个 false
    //System.out.println(blockingQueue.offer("d"));

    System.out.println(blockingQueue.peek()); //检索队首元素
    System.out.println("=================");

    //取出元素 (FIFO)
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    // 若多添加一个元素,则返回一个 null
    //System.out.println(blockingQueue.poll());
}
/**
 * 阻塞等待(死等)
 */
public static void test3() throws InterruptedException {
    BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    //存入队列 --> (blockingQueue.put是一个void函数,没返回值)
    blockingQueue.put("a");
    blockingQueue.put("b");
    blockingQueue.put("c"); //程序正常结束,代表执行成功
    // 若多添加一个元素,队列已经满了,则会造成阻塞等待,一直等,直到手动结束运行
    //blockingQueue.put("d");

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

    //每过三秒,取出元素 (FIFO)
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    // 若多取出一个元素,也会一直等,直到手动结束程序运行
    //System.out.println(blockingQueue.take());
}
/**
 * 超时等待(超时就拜拜)
 */
public static void test4() {
    BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

    //存入队列 --> (blockingQueue.put是一个void函数,没返回值)
    System.out.println(blockingQueue.offer("a"));
    System.out.println(blockingQueue.offer("b"));
    System.out.println(blockingQueue.offer("c"));
    // 若多添加一个元素,可以手动设置一个超时时间,超时自动退出
    //System.out.println(blockingQueue.offer("d", 3, TimeUnit.SECONDS));

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


    //每过三秒,取出元素 (FIFO)
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    // 若多取出一个元素,超时直接退出
    // 且返回 Disconnected from the target VM, address: '127.0.0.1:52824', transport: 'socket'
    //System.out.println(blockingQueue.poll(3, TimeUnit.SECONDS));
}
SynchronousQueue同步队列

没有容量,进一个出一个, put, take

官方文档:其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。

package com.shan.bq;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " -->put a");
                blockingQueue.put("a");
                System.out.println(Thread.currentThread().getName() + " -->put b");
                blockingQueue.put("b");
                System.out.println(Thread.currentThread().getName() + " -->put c");
                blockingQueue.put("c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程1").start();

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " -->取出" + blockingQueue.take());
                System.out.println(Thread.currentThread().getName() + " -->取出" + blockingQueue.take());
                System.out.println(Thread.currentThread().getName() + " -->取出" + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"线程2").start();

    }
}

11、线程池(重点)

线程池:三大方法,七大参数

池化技术

程序的运行本质就是:占用系统资源,为了优化资源的使用 --> 池化技术

池化技术简单来说就是,我创建了一个池子,里面有你需要的东西,用的时候你来拿,用完之后再还给我

线程池的好处:

  1. 减低资源的消耗

  2. 提高响应速度

  3. 方便管理

线程复用,可以控制最大并发数量,管理线程

线程池:三大方法

package com.shan.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//Executors 工具类 三大方法
public class Demo1 {
    public static void main(String[] args) {
        //ExecutorService threadPool = Executors.newSingleThreadExecutor(); //创建单个线程
        //ExecutorService threadPool = Executors.newFixedThreadPool(10); //创建一个固定大小的线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个可伸缩性的线程池,遇强则强,遇弱则弱

        try {
            for (int i = 1; i <= 100; i++) {
                //使用了线程池之后,应使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "--> ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池使用完,程序结束,需要关闭
            threadPool.shutdown();
        }

    }
}

七大参数

源码分析:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

// 其本质都是 ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, // 核心线程池大小
                          int maximumPoolSize,  // 最大线程池大小
                          long keepAliveTime,  // 超时等待
                          TimeUnit unit,  // 超时单位
                          BlockingQueue<Runnable> workQueue,  // 阻塞队列
                          ThreadFactory threadFactory  // 线程工厂,负责创建线程,一般不动
                          RejectedExecutionHandler handler  // 拒绝策略
                         ) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         threadFactory, defaultHandler);
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

四种拒绝策略:

new ThreadPoolExecutor.AbortPolicy()  // 银行满了,还有人进来,不处理这个人,抛出异常 RejectedExecutionException
new ThreadPoolExecutor.CallerRunsPolicy()  // 哪来的哪里去
new ThreadPoolExecutor.DiscardOldestPolicy()  // 队列满了,尝试去和最早的任务竞争,也不会抛出异常
new ThreadPoolExecutor.DiscardPolicy()  // 队列满了,丢掉任务,不会抛出异常

小结与拓展

了解什么是 CPU密集型,IO密集型:涉及到调优策略

package com.shan.pool;

import java.util.concurrent.*;

public class Demo1 {
    public static void main(String[] args) {
        //自定义线程池,ThreadPoolExecutor
        /**
         * 最大线程池到底该如何定义?
         * 1、CPU密集型:几核就定义为几,可以保持 CPU的效率最高
         * 2、IO密集型:判断程序中十分消耗 IO的线程
         */

        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                Runtime.getRuntime().availableProcessors(), //获取cpu核数(8/12个线程并发执行)
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );

        try {
            //最大承载 = Deque + max
            for (int i = 1; i <= 10; i++) {
                //使用了线程池之后,应使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "--> ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池使用完,程序结束,需要关闭
            threadPool.shutdown();
        }

    }
}

12、四大函数式接口(必须掌握)

lambda表达式,链式编程,函数式接口,Stream流式计算

函数式接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
// 框架中存在大量的 @FunctionalInterface
// 函数式接口可以简化编程模型,在新版本的框架中大量应用
package com.shan.function;

import java.util.function.Function;

/**
 * Function 函数型接口,有一个输入参数,有一个输出
 * 只要是函数型接口,可以用 lambda表达式简化
 */
public class Demo01 {
    public static void main(String[] args) {
//        Function function = new Function<String, String>() {
//            @Override
//            public String apply(String s) {
//                return s;
//            }
//        };

        Function function = (str)-> {return str;};
        System.out.println(function.apply("asd"));
    }
}

断定型接口:有一个参数输入,返回值只能是 布尔值

package com.shan.function;

import java.util.function.Predicate;

/**
 * 断定型接口:有一个参数输入,返回值只能是 布尔值
 */
public class Demo02 {
    public static void main(String[] args) {
        //判断字符串是否为空
//        Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String s) {
//                return s.isEmpty();
//            }
//        };
        //简化为 lambda表达式
        //Predicate<String> predicate = (str)->{return str.isEmpty();};

        //更加简化
        Predicate<String> predicate = String::isEmpty;
        System.out.println(predicate.test("abc"));
    }
}

Consumer 消费型接口

package com.shan.function;

import java.util.function.Consumer;

/**
 * consumer 消费型接口,只有输入,没有输出
 */
public class Demo03 {
    public static void main(String[] args) {
//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String s) {
//                //底层代码为: void accept(T t); 无返回值
//                System.out.println(s);
//            }
//        };

        //Consumer<String> consumer = (s) -> { System.out.println(s); };
        Consumer<String> consumer = System.out::println;
        consumer.accept("anc");
    }
}

Supplier 提供型接口

package com.shan.function;

import java.util.function.Supplier;

/**
 * Supplier 供给者接口,只输出,且没有参数
 */
public class Demo04 {
    public static void main(String[] args) {
//        Supplier<String> stringSupplier = new Supplier<String>() {
//            @Override
//            public String get() {
//                return "hello";
//            }
//        };
        
        Supplier<String> stringSupplier = () -> "hello";
        System.out.println(stringSupplier.get());
    }
}

13、stream流计算

package com.shan.Stream;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

/**
 * 题目要求:一分钟内完成此题,只能用一行代码实现!
 * 现在有 5个用户!按要求进行筛选:
 * 1、ID必须为偶数
 * 2、年龄必须大于 35岁
 * 3、用户名转化为大写字母
 * 4、用户名字母倒序
 * 5、只输出一个用户
 */
public class Demo1 {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",24);
        User u3 = new User(3,"c",28);
        User u4 = new User(4,"d",36);
        User u5 = new User(5,"e",37);
        User u6 = new User(6,"f",41);
        // 集合就是存储
        List<User> users = Arrays.asList(u1,u2,u3,u4,u5,u6);

        //计算交给 Stream流
        users.stream()
                .filter(u -> u.getId() % 2 == 0)
                .filter(u -> u.getAge() > 35)
                .map(u -> u.getName().toUpperCase())
                .sorted(Comparator.reverseOrder())
                .limit(1)
                .forEach(System.out::println);
    }
}

14、ForkJoin

简单来说就是把一个大任务拆解成一个个小任务,然后合并结果

代码

package com.shan.ForkJoin;

import java.util.concurrent.RecursiveTask;

/**
 * 求和计算的任务
 * 1、
 * 2、ForkJoin使用步骤
 *  1.使用 ForkJoinPool来执行
 *  2.计算任务 forkjoinpool.execute(ForkJoinTask task)
 * 3、Stream流
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
    private long start;
    private long end;

    private long temp = 10000L; //临界值

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    //计算方法
    @Override
    protected Long compute() {
        if ((end - start) < temp) { //小于临界值,就正常计算
            long sum = 0L;
            for (long i = start; i <= end ; i++) {
                sum += i;
            }
            return sum;
        } else { //若大于临界值,就采用分枝运算
            long middle = (start + end) / 2;
            ForkJoinDemo forkJoinDemo1 = new ForkJoinDemo(start, middle);
            forkJoinDemo1.fork(); //把任务拆分,放入线程队列
            ForkJoinDemo forkJoinDemo2 = new ForkJoinDemo(middle + 1, end);
            forkJoinDemo2.fork();//把任务拆分,放入线程队列
            //返回结果
            return forkJoinDemo1.join() + forkJoinDemo2.join();
        }
    }
}

测试

package com.shan.ForkJoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //test1(); //运行时间:8066
        //test2(); //运行时间:265
        test3(); //运行时间:228
    }

    //普通求和
    public static void test1(){
        long start = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 1L; i <= 10_0000_0000L ; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + " 运行时间:" + (end -start));
    }
    //会使用 ForkJoin
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + " 运行时间:" + (end -start));
    }
    //会使用 Stream并行流
    public static void test3() {
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum = "+ sum + " 运行时间:" + (end -start));
    }
}

15、异步任务

package com.shan.future;

import java.sql.Time;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 异步调用:CompletableFuture
 * 异步执行
 * 成功回调
 * 失败回调
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //无返回值的 runAsync 异步回调
//        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
//            try {
//                TimeUnit.SECONDS.sleep(3);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName() + "-runAsync()-->Void");
//        });
//        System.out.println("1111");
//        completableFuture.get();// 获取阻塞执行的结果

        // 有返回值的 supplyAsync调用
        // Ajax,成功和失败的回调
        // 返回的是错误信息
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "-supplyAsync()-->Integer");
            int i = 10 / 0;
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t-->" + t); //返回正常信息
            System.out.println("u-->" + u); //错误信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        }).exceptionally((e) -> {
            System.out.println(e.getMessage()); //可以获取到错误信息的返回结果
            return 1023;
        }).get());
    }
}

16、JMM

什么是 JMM

JMM:Java内存模型,不是一个物理上存在的东西,是一个概念。

关于JMM的一些同步约定:

1、线程解锁前,必须把共享变量立刻返回主存

2、线程加锁前,必须读取主存中的最新值到工作内存中

3、加锁和解锁必须操作的是同一把锁

线程 工作内存,主内存

8种操作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为了支持 JMM,Java 定义了8种原子操作,用来控制主存与工作内存之间的交互:

  • read 读取:作用于主内存,将共享变量从主内存传送到线程的工作内存中。
  • load 载入:作用于工作内存,把 read 读取的值放到工作内存中的副本变量中。
  • store 存储:作用于工作内存,把工作内存中的变量传送到主内存中。
  • write 写入:作用于主内存,把从工作内存中 store 传送过来的值写到主内存的变量中。
  • use 使用:作用于工作内存,把工作内存的值传递给执行引擎,当虚拟机遇到一个需要使用这个变量的指令时,就会执行这个动作。
  • assign 赋值:作用于工作内存,把执行引擎获取到的值赋值给工作内存中的变量,当虚拟机栈遇到给变量赋值的指令时,就执行此操作。
  • lock锁定: 作用于主内存,把变量标记为线程独占状态。
  • unlock解锁: 作用于主内存,它将释放独占状态。

17、Volatile

Volatile的理解

Volatile 是 Java 虚拟机提供的 轻量级同步机制

  1. 保证可见性

    package com.shan.TestVol;
    
    import java.util.concurrent.TimeUnit;
    
    //能保证可见性
    public class JMMTest {
    
        // 加上 volatile 能保证可见性
        // 即在 B线程修改了内存中的数据之后,A线程能知道内存中的元素已经被修改,从而保证了可见性
        // 不加 volatile就会陷入死循环
        private volatile static int num = 0;
    
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                while (num == 0) {
                    //System.out.println("我这的num=" + num); //sout底层包含 Synchronized,同步代码块
                }
            }, "A").start();
            TimeUnit.SECONDS.sleep(1);
    
            new Thread(()->{
                num = 1;
                System.out.println(num);
            },"B").start();
    
        }
    }
    
  2. 不保证原子性

    package com.shan.TestVol;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    //不保证原子性
    public class Demo02 {
    
        //Volatile不保证原子性,所以我们要使用原子类的Integer
        private static num = 0;
    
        public static void add() {
            num ++; 
        }
    
        public static void test() {
            for (int i = 1; i <= 20 ; i++) { //单线程必不可能出错 num = 400
                for (int j = 1; j <= 20; j++) {
                        add();
                }
            }
            System.out.println("num = " + num);
        }
    
        public static void main(String[] args) {
            for (int i = 1; i <= 20 ; i++) {
                new Thread(()->{ //多线程运行, 正常来说应该等于 400
                    for (int j = 1; j <= 20; j++) {
                        add();
                    }
                }).start();
            }
            while (Thread.activeCount() > 2) { //main  GC
                Thread.yield();
            }
            System.out.println("num = " + num);
        }
    }
    

    如果不使用Lock 和 Synchronized 如何保证原子性

    package com.shan.TestVol;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    //不保证原子性
    public class Demo02 {
    
        //Volatile不保证原子性,所以我们要使用原子类的Integer
        private volatile static AtomicInteger num = new AtomicInteger();
    
        public static void add() {
            //num ++; //使用 AtomicInteger之后,num++方法就不可以在使用了
            num.getAndIncrement(); //AtomicInteger的 +1方法
        }
    
        public static void test() {
            for (int i = 1; i <= 20 ; i++) { //单线程必不可能出错 num = 400
                for (int j = 1; j <= 20; j++) {
                        add();
                }
            }
            System.out.println("num = " + num);
        }
    
        public static void main(String[] args) {
            for (int i = 1; i <= 20 ; i++) {
                new Thread(()->{ //多线程运行, 正常来说应该等于 400
                    for (int j = 1; j <= 20; j++) {
                        add();
                    }
                }).start();
            }
            while (Thread.activeCount() > 2) { //main  GC
                Thread.yield();
            }
            System.out.println("num = " + num);
        }
    }
    
  3. 禁止指令重排

    什么是指令重排?

    你写的程序,计算机不一定会按照排好的顺序去执行

    源代码 --> 计算机优化之后的重排 --> 并行执行也会造成重排 --> 内存系统也会重排 --> 执行

    处理器在执行指令重排的时候,会考虑数据之间的依赖性

    int a = 0;//1
    int b = 1;//2
    a = a + 2;//3
    b = a * a;//4
    // 正常来说我们希望代码按照 1234 的顺序去执行
    // 但是在真正执行的时候,可能会变为 1324 或者 1243
    // 也有可能变成 2341
    

    可能会影响代码运行的结果

    Volatile 可以在写操作中添加一个内存屏障,有效防止指令重排

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

18、单例模式

单例模式分为 饿汉式,懒汉式,

饿汉式

package com.shan.single;

/**
 * 饿汉式,线程安全的
 */
public class Hungry {

    //线程启东时就会创建对象,可能会浪费空间资源
    private final byte[] data1 = new byte[1024*1024];
    private final byte[] data2 = new byte[1024*1024];
    private final byte[] data3 = new byte[1024*1024];
    private final byte[] data4 = new byte[1024*1024];
    private final byte[] data5 = new byte[1024*1024];

    private Hungry() {

    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry genInstance() {
        return HUNGRY;
    }
}

懒汉式

package com.shan.single;

/**
 * 懒汉式,线程不安全的
 */
public class LazyManDemo {
    private LazyManDemo() {
        System.out.println(Thread.currentThread().getName() + " --> ok");
    }

    private static LazyManDemo lazyManDemo;

    public static LazyManDemo getInstance() {
        if (lazyManDemo == null) { //使用的时候再去创建,节省资源
            lazyManDemo = new LazyManDemo(); 
        }
        return lazyManDemo;
    }

    //多线程并发
    public static void test() {
        for (int i = 0; i < 10; i++) {
            new Thread(LazyManDemo::getInstance).start();
        }
    }

    public static void main(String[] args) {
        test();
    }
}

运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由上图结果可见, 当在多线程中,如果多个线程同时进行,就会实例化多个对象,所以要对懒汉式进行优化。

1、加锁 Synchronized + volatile

package com.shan.single;

/**
 * DCL懒汉式,线程安全
 */
public class LazyManDemo {
    private LazyManDemo() {
        System.out.println(Thread.currentThread().getName() + " --> ok");
    }

    //这里加 volatile是为了防止CPU执行过程中的指令重排
    private volatile static LazyManDemo lazyManDemo;

    public static LazyManDemo getInstance() {
        //加锁,双重检测锁模式的懒汉式单例,简称DCL(Double Check Lock)
        if (lazyManDemo == null) {
            synchronized (LazyManDemo.class) {
                if (lazyManDemo == null) {
                    lazyManDemo = new LazyManDemo(); //不是一个原子性操作
                    /**
                     * new一个对象需要三步操作:
                     * 1、分配一个内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、将这个对象放到这个内存空间
                     *
                     * 正常情况下,我们希望的执行顺序是123
                     * 但是由于指令可能重排,它的执行顺序可能会变为132
                     * 这样就会造成一个问题
                     * 当线程 A 根据 132重排指令执行时
                     *   线程 B需要创建对象,但是由于 3已经执行完成
                     *   这时线程 B就会判断 lazyManDemo不为空,从而返回一个未初始化完成的对象,导致代码出问题
                     */
                }
            }
        }
        return lazyManDemo;
    }

    //多线程并发
    public static void test() {
        for (int i = 0; i < 10; i++) {
            new Thread(LazyManDemo::getInstance).start();
        }
    }

    public static void main(String[] args) {
        test();
    }
}

这个时候我们的代码就安全了,但是真的安全吗?答案是不安全的,因为在Java中存在反射的机制,我们可以通过反射来修改私有属性,让它变得不私有,然后通过反射对象来创建一个新的线程

代码测试

package com.shan.single;

import com.sun.corba.se.impl.resolver.SplitLocalResolverImpl;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class LazyMan {

    //4、可以设置一个红绿灯,防止两个对象都用反射创建
    // guoqing甚至可以换成 一些经过加密的变量,这样安全性更高
    private static boolean guoqqing = false; //这里设置false为通行证


    private LazyMan() {
        //2、加锁判断,防止反射二次创建对象
        synchronized (LazyMan.class) {
            if (guoqqing == false) {
                guoqqing = true;
            } else {
                throw new RuntimeException("请不要使用反射来搞破坏");
            }
//            if (lazyMan != null) {
//                throw new RuntimeException("请不要使用反射来搞破坏");
//            }
        }
        System.out.println(Thread.currentThread().getName() + " --> ok");
    }

    //这里加 volatile是为了防止CPU执行过程中的指令重排
    private volatile static com.shan.single.LazyMan lazyMan;

    public static LazyMan getInstance() {
        //加锁,双重检测锁模式的懒汉式单例,简称 DCL(Double Check Lock)
        if (lazyMan == null) {
            synchronized (com.shan.single.LazyManDemo.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan();
                    }
                }
            }
            return lazyMan;
        }

        //多线程并发
        public static void test() {
            for (int i = 0; i < 10; i++) {
                new Thread(com.shan.single.LazyManDemo::getInstance).start();
            }
        }

        public static void main(String[] args) throws Exception {
            //1、通过反射可以破坏单例模式
            //LazyMan instance1 = LazyMan.getInstance();

            //5、面对红绿灯,我们依旧可以破坏单例,通过反编译,找到这个私有变量
            Field guoqqing = LazyMan.class.getDeclaredField("guoqqing");
            guoqqing.setAccessible(true);

            Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
            constructor.setAccessible(true);

            //3、可以将两个对象都是用反射来创建,就可以做到穿透锁的防线
            LazyMan instance1 = constructor.newInstance();
            guoqqing.set(instance1, false); //false为通行证
            LazyMan instance2 = constructor.newInstance();

            if (instance1 == instance2) {
                System.out.println("相等");
            }else {
                System.out.println("不相等");
                System.out.println(instance1.hashCode());
                System.out.println(instance2.hashCode());
            }
        }
    }


对上面的代码做出解释如下:

进攻方(反射):

  1. 可以通过反射来破坏单例,使用反射来一个newInstance(),也能创建一个新的对象,破坏单例
  2. 加锁之后,我们可以采取另一种策略,把所有对象的创建都使用 newInstance(),这样就可以绕过锁,破坏单例
  3. 面对红绿灯,我们依旧可以破坏单例,通过反编译(win+R,然后输入 javap -s EnumSingle.class )找到红绿灯的变量名,破坏其私有性质,即可破坏单例.

防守方:

  1. 通过加锁判断,来防止反射创建对象
  2. 可以设置一个红绿灯,防止两个对象都用反射创建,guoqing甚至可以换成 一些经过加密的变量,这样安全性更高
  3. 这样就没什么办法了…

那么真的没有办法可以阻止反射了吗?答案是有的,我们可以通过枚举类来防止反射的入侵

代码测试

package com.shan.single;

import java.lang.reflect.Constructor;

//enum是一个什么? 它的本事也是一个 class类
public enum EnumSingle {
    INSTANCE;

    public EnumSingle enumSingle() {
        return INSTANCE;

    }
}

class Test {
    public static void main(String[] args) throws Exception {

        EnumSingle single1 = EnumSingle.INSTANCE;
        //EnumSingle single2 = EnumSingle.INSTANCE;

        /* 对于上述方式,我们就想破坏一下,嘿嘿
         * 首先在 target文件中查看源码
         * -------------------------------------------------------------------
            public enum EnumSingle {
                INSTANCE;
                private EnumSingle() {}
                public EnumSingle enumSingle() { return INSTANCE; }
            }
          * -------------------------------------------------------------------
          * private EnumSingle() {}可以看到这里存在一个无参构造方法,嘿嘿,可以操作了
         */
        //Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle single2 = declaredConstructor.newInstance();
        /*
        这时则会抛出异常:NoSuchMethodException: com.shan.single.EnumSingle.<init>()-->没有空参构造方法???
        参数有问题?这是怎么回事呢?很明显IDEA骗了我们
         */

        /*
        这时我们可以使用 jad反编译一下源码
        private EnumSingle(String s, int i) {
            super(s, i);
        }
        这下终于可以看到,源码里面竟然存在 两个参数!!
        EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        增加两个参数之后得到的返回结果:IllegalArgumentException: Cannot reflectively create enum objects
        符合源码:throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ----------------------------------------------------------------------------------------------
        @CallerSensitive
        public T newInstance(Object ... initargs)
            throws InstantiationException, IllegalAccessException,
                   IllegalArgumentException, InvocationTargetException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, null, modifiers);
                }
            }
            if ((clazz.getModifiers() & Modifier.ENUM) != 0)
                throw new IllegalArgumentException("Cannot reflectively create enum objects");
            ConstructorAccessor ca = constructorAccessor;   // read volatile
            if (ca == null) {
                ca = acquireConstructorAccessor();
            }
            @SuppressWarnings("unchecked")
            T inst = (T) ca.newInstance(initargs);
            return inst;
        }
        ----------------------------------------------------------------------------------------------
         */

        if (single1 == single2) {
            System.out.println("相等");
        }else {
            System.out.println("不相等");
            System.out.println(single1.hashCode());
            System.out.println(single2.hashCode());
        }

    }
}

19、深入理解CAS

什么是CAS

CAS是Compare-And-Swap(比较并交换)的缩写,也是一种==轻量级的同步机制==,主要用于实现多线程环境下的无锁算法和数据结构,保证了并发安全性。它可以在不使用锁(如synchronized、Lock)的情况下,对共享数据进行线程安全的操作。

CAS操作主要有三个参数:要更新的内存位置、期望的值、新值。CAS操作的执行过程如下:

  1. 首先,获取要更新的内存位置的值,记为var。
  2. 然后,将期望值expected与var进行比较,如果两者相等,则将内存位置的值var更新为新值new。
  3. 如果两者不相等,则说明有其他线程修改了内存位置的值var,此时CAS操作失败,需要重新尝试,一直循环。

代码测试

package com.shan.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class Demo01 {
    //CAS compareAndSet 比较并交换
    public static void main(String[] args) {
        //AtomicInteger()
        //创建一个新的AtomicInteger,初始值为 0 。
        //AtomicInteger(int initialValue)
        //用给定的初始值创建一个新的AtomicInteger。
        AtomicInteger atomicInteger = new AtomicInteger(2024);
        //达到期望值就更新
        System.out.println(atomicInteger.compareAndSet(2024, 2025));
        System.out.println(atomicInteger.get());

        atomicInteger.getAndIncrement();
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2025, 2024));
        System.out.println(atomicInteger.get());
    }
}

Unsafe类

前面提到,CAS是一种原子操作。那么Java是怎样来使用CAS的呢?我们知道,在Java中,如果一个方法是native的,那Java就不负责具体实现它,而是交给底层的JVM使用c或者c++去实现。

Unsafe类是JDK提供的一个不安全的类,它提供了一些底层的操作,包括内存操作、线程调度、对象实例化等。它的作用是让Java可以在底层直接操作内存,从而提高程序的效率。但是,由于Unsafe类是不安全的,所以只有JDK开发人员才能使用它,普通开发者不建议使用。它里面大多是一些native方法,其中就有几个关于CAS的:例如getAndIncrement

//源码分析
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

//在这边我们可以看到 getAndIncrement()方法中调用了一个unsafe类中的getAndAddInt方法,再往底层探究我们可以看到这方法存在里面三个参数(Object var1, long var2, int var4)分别对应了(this, valueOffset, 1),通过var5获取内存地址中的值(即当前var1复制过来的内存地址偏移量),如果当前对象内存地址偏移值=var5,就执行var5 + var4(在上层方法调用中,var4默认=1),由此实现了+1操作。

具体的实现过程如下:

  1. 调用compareAndSwapInt、compareAndSwapLong或compareAndSwapObject方法时,会传入三个参数,分别是需要修改的变量V、期望的值A和新值B。
  2. 方法会先读取变量V的当前值,如果当前值等于期望的值A,则使用新值B来更新变量V,否则不做任何操作。
  3. 方法会返回更新操作是否成功的标志,如果更新成功,则返回true,否则返回false。

由于CAS操作是基于底层硬件支持的原子性指令来实现的,所以它可以保证操作的原子性和线程安全性,同时也可以避免使用锁带来的性能开销。因此,CAS操作广泛应用于并发编程中,比如实现无锁数据结构、实现线程安全的计数器等。

CAS中的ABA问题

ABA问题指在CAS操作过程中,如果变量的值被改为了 A、B、再改回 A,而CAS操作是能够成功的,这时候就可能导致程序出现意外的结果。(简单理解就是 狸猫换太子 --> 太子换狸猫,但是没有人知道这个太子被换过

在高并发场景下,使用CAS操作可能存在ABA问题,也就是在一个值被修改之前,先被其他线程修改为另外的值,然后再被修改回原值,此时CAS操作会认为这个值没有被修改过,导致数据不一致。

package com.shan.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class Demo01 {
    //CAS compareAndSet 比较并交换
    public static void main(String[] args) {
        //AtomicInteger()
        //创建一个新的AtomicInteger,初始值为 0 。
        //AtomicInteger(int initialValue)
        //用给定的初始值创建一个新的AtomicInteger。
        AtomicInteger atomicInteger = new AtomicInteger(2024);
        //达到期望值就更新
        /* ================================捣乱的线程======================================= */
        System.out.println(atomicInteger.compareAndSet(2024, 2025));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2025, 2024));
        System.out.println(atomicInteger.get());

        /* ================================期望的线程======================================= */
        System.out.println(atomicInteger.compareAndSet(2025, 6666));
        System.out.println(atomicInteger.get());
    }
}

CAS的一些应用场景

CAS在多线程并发编程中被广泛应用,它通常用于实现乐观锁无锁算法。以下是CAS的一些应用场景:

  1. **线程安全计数器:**由于CAS操作是原子性的,因此CAS可以用来实现一个线程安全的计数器;
  2. **队列:**在并发编程中,队列经常用于多线程之间的数据交换。使用CAS可以实现无锁的非阻塞队列(Lock-Free Queue);
  3. **数据库并发控制:**乐观锁就是通过CAS实现的,它可以在数据库并发控制中保证多个事务同时访问同一数据时的一致性;
  4. **自旋锁:**自旋锁是一种非阻塞锁,当线程尝试获取锁时,如果锁已经被其他线程占用,则线程不会进入休眠,而是一直在自旋等待锁的释放。自旋锁的实现可以使用CAS操作;
  5. **线程池:**在多线程编程中,线程池可以提高线程的使用效率。使用CAS操作可以避免对线程池的加锁,从而提高线程池的并发性能。

CAS真的完全没加锁吗?(拓展知识)

上面说过,CAS是一个无锁机制的操作,底层是通过Unsafe类使用native本地方法进行的CAS操作,但是大家有没有想过这样的问题:硬件层面CAS又是如何保证原子性的呢?真的完全没加锁吗?

拿比较常见的x86架构的CPU来说,其实 CAS 操作通常使用 cmpxchg 指令实现的。

可是为啥 cmpxchg 指令能保证原子性呢?主要是有以下几个方面的保障:

  • cmpxchg 指令是一条原子指令。在 CPU 执行 cmpxchg 指令时,处理器会自动锁定总线,防止其他 CPU 访问共享变量,然后执行比较和交换操作,最后释放总线。
  • cmpxchg 指令在执行期间,CPU 会自动禁止中断。这样可以确保 CAS 操作的原子性,避免中断或其他干扰对操作的影响。
  • cmpxchg 指令是硬件实现的,可以保证其原子性和正确性。CPU 中的硬件电路确保了 cmpxchg 指令的正确执行,以及对共享变量的访问是原子的。

所以,在操作系统层面,CAS还是会加锁的,通过加锁的方式锁定总线,避免其他CPU访问共享变量。

20、原子引用(解决ABA问题)

解决ABA问题,引入原子引用

Java中提供了AtomicStampedReference类,该类通过使用版本号的方式来解决ABA问题。每个共享变量都会关联一个版本号,CAS操作时需要同时检查值和版本号是否匹配。因此,如果共享变量的值被改变了,版本号也会发生变化,即使共享变量被改回原来的值,版本号也不同,因此CAS操作会失败。

代码测试

package com.shan.cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class Demo02 {
    public static void main(String[] args) {
        /**
         * 这里使用的是String类型进行比较,但是使用 Integer时会出现一些问题
         * 首先我们要知道,Integer使用了对象缓存机制,默认范围为 -128~127,
         * 只要超过了这个值,就一定回去创建一个新的对象分配新的内存空间
         * AtomicStampedReference,如果引用的泛型是包装类,要注意对象的引用问题
         */
        AtomicStampedReference<String > atomicStampedReference = new AtomicStampedReference<String>("hello",1);

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//获得版本号
            System.out.println("a1 --> " + stamp);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            }
            //达到期望就更新
            atomicStampedReference.compareAndSet("hello","world",
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
            System.out.println("a2 --> " + atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet("world","hello",
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
            System.out.println("a3 --> " + atomicStampedReference.getStamp());

        },"A").start();

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1 --> " + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet("hello","world",
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);
            System.out.println("b2 --> " + atomicStampedReference.getStamp());
        },"B").start();
    }
}

CAS中的 CPU空转 问题

除了ABA问题,CAS操作还可能会受到自旋时间过长的影响,因为如果某个线程一直在自旋等待,会浪费CPU资源。

为了解决上述问题,可以采用自适应自旋锁的方式,即在前几次重试时采用忙等待的方式,后面则使用阻塞等待的方式,避免浪费CPU资源。

代码测试

package com.shan.cas;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

public class Demo03 {

    private AtomicReference<Thread> owner = new AtomicReference<>();
    private int count;

    public void lock() {
        Thread currentThread = Thread.currentThread();
        // 已经获取了锁
        if (owner.get() == currentThread) {
            count++;
            return;
        }
        // 自旋等待获取锁
        while (!owner.compareAndSet(null, currentThread)) {
            // 自适应自旋
            if (count < 10) {
                count++;
            } else {
                // 阻塞等待
                LockSupport.park(currentThread);
            }
        }
    }

    public void unlock() {
        Thread currentThread = Thread.currentThread();
        // 当前线程持有锁
        if (owner.get() == currentThread) {
            if (count > 0) {
                count--;
            } else {
                // 释放锁
                owner.compareAndSet(currentThread, null);
                // 唤醒其他线程
                LockSupport.unpark(currentThread);
            }
        }
    }
}

这里解释一下上面代码:

在设计时使用了AtomicReference来保存当前持有锁的线程对象,这样可以保证线程安全。

当一个线程请求获取锁时,如果当前线程已经持有锁,则将计数器加1,否则使用CAS操作来获取锁。这样可以避免了使用synchronized关键字或者ReentrantLock等锁的实现机制。

当线程获取锁失败时,使用自旋等待的方式,这样可以避免线程进入阻塞状态,避免了线程上下文切换的开销。当重试次数小于10时,使用自旋等待的方式,当重试次数大于10时,则使用阻塞等待的方式。这样可以在多线程环境下保证线程的公平性和效率。

在释放锁时,如果计数器大于0,则将计数器减1,否则将锁的拥有者设为null,唤醒其他线程。这样可以确保在有多个线程持有锁的情况下,正确释放锁资源,并唤醒其他等待线程,保证线程的正确性和公平性。

21、各种锁的理解

1、公平锁

非常公平,不能插队,3h --> 3s,必须等 3h 执行完,才能执行 3s

2、非公平锁

不公平,可以插队,3h --> 3s,可以优先执行 3s,再执行 3h

3、可重入锁
package com.shan.lock;

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            phone.sendSms();
        },"A").start();
        new Thread(()->{
            phone.sendSms();
        },"B").start();
    }
}

class Phone {
    public synchronized void sendSms() {
        System.out.println(Thread.currentThread().getName() + " --> sendSms");
        call(); //这里也有一把锁
    }

    public synchronized void call() {
        System.out.println(Thread.currentThread().getName() + " --> call");
    }
}
4、自旋锁
//自定义自旋锁

package com.shan.lock;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 */
public class SpinlockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    //加锁
    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "--> myLock");

        //自旋锁
        while (!atomicReference.compareAndSet(null,thread)){

        }
    }

    //解锁
    public void myUnLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "--> myUnLock");
        atomicReference.compareAndSet(thread, null);
    }

}

测试代码:

package com.shan.lock;

import java.util.concurrent.TimeUnit;

public class TestSpinlock {
    public static void main(String[] args) {
        SpinlockDemo lock = new SpinlockDemo();

        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T1").start();

        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        },"T2").start();
    }
}

运行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5、死锁

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值