10-JUC深层复习

JUC 深层学习记录

一、基础

1、juc是什么

Java Util concurrent 工具包。

2、线程和进程

线程、进程

进程:一个程序比如开启一个qq.exe, 进程和进程之间的内存资源是相互独立的。

线程:是进程的基本运行单位,一个进程可以包含多个线程,一个进程中之后有一个线程,进程中的所有线程共享进程中的资源。

  • java 真的可以开启线程么?

不可以

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

	// 调用本地方法
    private native void start0(); 

并发和并行

并发:

  • 一个cpu模拟出来的多条线程,由cpu分配时间片执行多个任务,快速交替,并不是真正意义上的并发。

并行:

  • 多盒cpu同时执行多条线程任务,cpu调度不会来回切换,真正意义上的并发。
public class Test1 {

    public static void main(String[] args) {
        // 获取cpu盒数
        // cpu密集型, io密集型
        System.out.println(Runtime.getRuntime().availableProcessors());

    }
}

并发编程的本质: 充分利用cpu资源。

线程有几个状态

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则不需要。

5、是否需要捕获异常:wait不需要捕获异常,sleep需要捕获异常。(可疑 wait需要中断异常)

3、lock 锁 (重点)

lock 接口

ReentrantLock 可重入锁
ReentrantReadWriteLock.ReadLock		读锁
ReentrantReadWriteLock.WriteLock 	写锁

ReentrantLock 可重入锁

	public ReentrantLock() {
        sync = new NonfairSync();  // 非公平锁
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync(); // 公平锁
    }

公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
  • 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

Synchronized 和 Lock 区别

1、Syuchronized 内置Java关键字,Lock 是一个类。

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

3、Synchronized 会自动释放锁,Lock 必须要手动释放锁,如果不释放锁,死锁

4、Synchronized 执行期间内会一直咱有锁,其他线程只能一直等,Lock 就不一定一直等(tryLock 尝试获取锁)

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

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

锁是什么,如果判断锁是谁?

  • 对象的引用
  • Class 对象

Condition (线程通信,替代传统wait/notify)

Condition因素出Object监视器方法( wait , notify和notifyAll )成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock个实现。 Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。

private int start = 0;
    Lock lock = new ReentrantLock();
	// 获取Condition实例
    Condition condition = lock.newCondition();

    public void incr() {
        lock.lock();
        try {
            while (start != 0 ){
                //等待
                condition.await();
            }
            start++;
            System.out.println(Thread.currentThread().getName() + "==>>" + start);
            // 唤醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void desc() {
        lock.lock();
        try {
            while (start == 0 ){
                //等待
                condition.await();
            }
            start--;
            System.out.println(Thread.currentThread().getName() + "==>>" + start);
            // 唤醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

虚假唤醒

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:

// 错误使用
synchronized (obj) {
         if (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     }   

// 正确使用
synchronized (obj) {
         while (<condition does not hold>)
             obj.wait(timeout);
         ... // Perform action appropriate to condition
     } 

Condition 精准通知和唤醒线程

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data.printA() ;
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data.printB();
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) data.printC();
        }, "C").start();
    }
}

// 精准唤醒
class Data {
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    private int number = 1;

    public void printA() {
        lock.lock();
        try {
            // 业务: 判断->执行->通知
            while (number != 1) {
                // 等待
                condition1.await();
            }
            // 唤醒指定的 B
            number = 2;
            System.out.println(Thread.currentThread().getName() + "->AAAA");
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        lock.lock();
        try {
            // 业务: 判断->执行->通知
            while (number != 2) {
                // 等待
                condition2.await();
            }
            // 唤醒指定的 C
            number = 3;
            System.out.println(Thread.currentThread().getName() + "->BBBB");
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        lock.lock();
        try {
            // 业务: 判断->执行->通知
            while (number != 3) {
                // 等待
                condition3.await();
            }
            // 唤醒指定的 A
            number = 1;
            System.out.println(Thread.currentThread().getName() + "->CCCC");
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

4、8锁现象 (对象锁、类锁)

锁是什么,如果判断锁是谁?

初始场景

  • Phone资源类有 void sendMessage() 、void call()两个方法
  • main方法开启 多线程场景 进行测试,8种情况可以提出8个问题,即8锁问题
  • JUC下线程sleep方法:TimeUnit.SECONDS.sleep(1);
package eight_locks;

public class Demo1 {


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


class Phone {

    public synchronized void sendMessage(){
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
}
1、创建一个Phone实例多线程调用两个方法,问哪一个先执行?
public class Demo1 {
    public static void main(String[] args) {
        Phone p = new Phone();
        // 发短信
        new Thread(()->{
            p.sendMessage();
        }).start();
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 打电话
        new Thread(()->{
            p.call();
        }).start();
    }
}

class Phone {
    public synchronized void sendMessage(){
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

输出:
发短信
打电话

结果分析

  • 因为synchronized关键字 是对 该资源类的对象 上锁,因此哪个线程先拿到对象锁,就先执行
  • 因此上面的线程先拿到锁,先执行,如果还不理解接着看问题2
2、创建一个Phone实例多线程调用两个方法,其中第一个线程调用的方法中加延迟,问哪一个先执行?
public class Demo1 {
    public static void main(String[] args) {
        Phone p = new Phone();
        // 发短信
        new Thread(()->{
            p.sendMessage();
        }).start();

        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 打电话
        new Thread(()->{
            p.call();
        }).start();
    }
}

class Phone {
    public synchronized void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}

输出:
发短信
打电话

结果分析

  • 原理同上。还是上面的线程先拿到 资源类 锁对象
  • 同一资源类下,synchronized修饰的方法 使用的是当前资源类 对象锁。谁先拿到谁执行。
3、创建一个Phone实例多线程调用两个方法,其中一个是普通方法,而且该线程位置靠后,问哪一个先执行?
public class Demo3 {
    public static void main(String[] args) {
        Phone3 p = new Phone3();
        // 发短信
        new Thread(()->{
            p.sendMessage();
        }).start();
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 打电话
        new Thread(()->{
            p.watchMovie();
        }).start();
    }
}


class Phone3 {
    public synchronized void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
    public void watchMovie(){
        System.out.println("看电影");
    }
}

输出:
看电影
发短信

结果分析

  • watchMovie()为普通方法,不受同步方法的影响,也就是不受 锁 的影响
  • 因为 sendMessage()方法体 中有延迟语句,因此会后输出,其实 抛去延迟来说,两个输出结果为不一定

4、创建两个Phone实例多线程调用两个方法,问哪一个先执行?

public class Demo4 {
    public static void main(String[] args) {
        Phone4 one = new Phone4();
        Phone5 two = new Phone4();
        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 打电话
        new Thread(()->{
            two.call();
        }).start();
    }
}


class Phone4 {
    public synchronized void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
输出:
打电话
发短信

结果分析

  • 区别于问题1,该情况是 两个资源类对象分别开启两个线程,因此锁对象 并无互相干扰,因为线程延时的原因,打电话 先输出
  • 由此可在次证明synchronized加载方法前面锁住的是 调用该方法的 实例对象,多个实例之间的锁 不干扰

5、创建一个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?

public class Demo5 {
    public static void main(String[] args) {
        Phone5 one = new Phone5();
        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 打电话
        new Thread(()->{
            one.call();
        }).start();
    }
}


class Phone5 {
    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized static void call(){
        System.out.println("打电话");
    }
}

输出:
发短信
打电话

问题分析

  • 加上static关键字之后,两个方法都变为静态方法。
  • 发短信 在前面的原因是 synchronized 加 静态方法 锁的是 Class ,Phone5.Class只有单个。因此第二个线程需要 等待第一个线程释放Class锁才能执行。

6、创建两个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?

public class Demo6 {
    public static void main(String[] args) {
        Phone5 one = new Phone6();
        Phone6 two = new Phone6();
        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 打电话
        new Thread(()->{
            two.call();
        }).start();
    }
}


class Phone6 {
    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized static void call(){
        System.out.println("打电话");
    }
}

输出:
发短信
打电话

结果分析

  • 两个对象的Class只有一个Phone6.Class
  • 原理同5,synchronized 加 静态方法 锁的是 Class
7、创建一个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
public class Demo7 {
    public static void main(String[] args) {
        Phone7 one = new Phone7();
        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 打电话
        new Thread(()->{
            one.call();
        }).start();
    }
}


class Phone7 {
    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized  void call(){
        System.out.println("打电话");
    }
}

输出:
打电话
发短信

结果分析

  • 打电话 先输出的原因是 Phone7 实例 和 Phone7.Class 分别被锁,两个线程之间并无影响,因为线程延迟的原因。
  • 再次 证明 synchronized 锁的是 类实例即对象 、synchronized 加 静态方法 锁的是 Class
8、创建两个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
public class Demo7 {
    public static void main(String[] args) {
        Phone7 one = new Phone8();
        Phone8 two = new Phone8();
        // 发短信
        new Thread(()->{
            one.sendMessage();
        }).start();
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 打电话
        new Thread(()->{
            two.call();
        }).start();
    }
}

class Phone7 {
    public synchronized static void sendMessage(){
        // JUC下的线程延时方法
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized  void call(){
        System.out.println("打电话");
    }
}
输出:
打电话
发短信

结果分析

  • 原理同7。两个线程分别锁的是 Phone8.Class 和 Phone8实例,因为线程延迟,打电话 才会优先输出。

5、集合类不安全

List 不安全 CopyOnWriteArrayList

//java.util.ConcurrentModificationException 并发修改异常
public class ListTest {
    public static void main(String[] args) {
        // 并发下ArrayList不安全的吗, synchronized;
        /***解决方案;
         * 1. List<string> list = new Vector<>();
         * 2. List<string> list = Collections.synchronizedList(new ArrayList());
         * 3. List<string> list = new CopyOnWriteArrayList<>();
         */
        // CopyOnWrite写入时复制 cow 计算机程序设计领域的一种优化策略;
        // 多个线程调用的时候, Tist,读取的时候,固定的,写入(覆盖)
        // 在写入的时候避免覆盖,造成数据问题读写分离
        // CopyOnWriteArrayList 比Vector Nb在哪里?
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i <= 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
            }, String.valueOf(i)).start();
        }
    }
}

CopyOnWriteArrayList 底层使用的读写分离,先复制数组,在使用Lock锁实现写操作。

set 不安全

/**
 * @author 任天杰
 * @date 2021-07-24 2:24
 *
 *  同理可证:ConcurrentModificationException
 *  1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
 *  2、Set<String> set = new CopyOnWriteArraySet<>();
 */
public class SetTest {

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

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

CopyOnWriteArraySet 与CopyOnWriteArrayList 同理

  • set 原理
public HashSet() {
    map = new HashMap<>(); // 实际就是一个hashmap
}

public boolean add(E e) {
    return map.put(e, PRESENT)==null;	// 只不过是利用map的key存储数据,所以重复
}

private static final Object PRESENT = new Object();// val值是一个固定值,没有什么意义

hashmap

callable

public class CallableTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //new Thread(new Runnable()).start();
        //new Thread(new FutureTask<>()).start();
        //new Thread(new FutureTask<>(Callable)).start();

        MyThread myThread = new MyThread();

        FutureTask futureTask = new FutureTask(myThread); // 适配类

        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start();// 结果会被缓存,效率高
        Integer get = (Integer) futureTask.get();// 获取返回值,该方法可能会产生阻塞,把它放到最后

        // 或者使用异步通信来处理
        System.out.println(get);
    }


}

class MyThread implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        // 耗时操作
        return 1024;
    }
}

细节:

1、有缓存

2、结果可能出现等待,会阻塞!

6、常用的辅助类

1)、CountDownLatch 减法计数器

允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。

// 计数器
public class CountDownLatchTest {

    public static void main(String[] args) throws InterruptedException {
        // 总数6,必须任务要执行完,在使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+" go out");
                countDownLatch.countDown();// 减1
            },String.valueOf(i)).start();
        }
        // 等待计数器归零,然后在往下执行
        countDownLatch.await();
        System.out.println("关门");
    }
}

原理:

countDownLatch.countDown() // 数量-1

countDownLatch.await(); //等待计数器归零,后在往下执行

每次有线程调用 countDown数量减1,假设计数器变为0,countDownLatch.await()就会被唤醒,继续执行!

2)、CyclicBarrier 加法计数器

允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。

public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
            System.out.println("召唤神龙!");
        });

        for(int i=1;  i<= 7 ; i++){
            int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "召唤了" + temp + "龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
3)、Semaphore 信号量

一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行

public class SemaphoreTest {

    public static void main(String[] args) {
        // 线程数量,限流
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                // acquire得到
                try {
                    semaphore.acquire();// acquire得到
                    System.out.println(Thread.currentThread().getName()+"抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();// release 释放信号量
                }
                // release 释放信号量

            },String.valueOf(i)).start();
        }
    }
}

原理:

semaphore.acquire(); 获得,假如已满,等待,等待被释放为止。

semaphore.release(); 释放,会将当前信号量释放+1,然后唤起等待线程。

作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!

7、读写锁

Interface ReadWriteLock

读可以被多个线程同时读取,写的时候只能有一个线程去写。

/**
 * 独占锁(写锁)一次只能被一个线程占有
 * 共享锁(读锁)多个线程可同时占有
 * ReadWriteLock
 * 读-读 可以共存!
 * 读-写 不能共存!
 * 写-写 不能共存!
 */
public class ReadWriteLockTest {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();
        // 写入
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp + "", temp);
            }, String.valueOf(i)).start();
        }
        // 写入
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp + "");
            }, String.valueOf(i)).start();
        }
    }
}

/* *
 *  加锁
 */
class MyCacheLock {
    private volatile Map<String, Object> map = new HashMap<>();
    // 读写锁,更加细粒度的控制
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    // 存 写 同时只有一个线程操作
    public void put(String key, Object val) {
        lock.writeLock().lock();// 写锁
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, val);
            System.out.println(Thread.currentThread().getName() + "写入ok");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    // 取 读 同时多个线程读取
    public void get(String key) {
        lock.readLock().lock(); // 读锁
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取ok");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
        lock.writeLock();
    }

}

/* *
 *  自定义缓存
 */
class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();

    // 存 写
    public void put(String key, Object val) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, val);
        System.out.println(Thread.currentThread().getName() + "写入ok");
    }

    // 取 读
    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取ok");
    }
}

8、阻塞队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TCyiriJ6-1628000760697)(.\imgs\01-JUC深层复习\image-20210724194754080.png)]

四组API

方式抛出异常有返回值阻塞等待超时等待
添加addoffer()put()offer(…)
移除removepoll()take()poll(…)
检测队首元素elementpeek()--
/**
 * 抛出异常
 */
public void test(){
    ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);

    System.out.println(arrayBlockingQueue.add("a"));
    System.out.println(arrayBlockingQueue.add("b"));
    System.out.println(arrayBlockingQueue.add("c"));

    // 抛出异常 IllegalStateException: Queue full
    // System.out.println(arrayBlockingQueue.add("d"));
    System.out.println("--------------------------");

    System.out.println(arrayBlockingQueue.remove());
    System.out.println(arrayBlockingQueue.remove());
    System.out.println(arrayBlockingQueue.remove());

    // NoSuchElementException
    System.out.println(arrayBlockingQueue.remove());
}
/**
     * 有返回值,没有异常
     */
public void test1(){
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

    System.out.println(blockingQueue.offer("a"));
    System.out.println(blockingQueue.offer("b"));
    System.out.println(blockingQueue.offer("c"));

    System.out.println(blockingQueue.offer("d"));// false 不抛出异常
    System.out.println("--------------------------");

    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());// 返回null
}

/**
 * 阻塞等待
 */
@org.junit.Test
    public void test2() throws InterruptedException {
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

    blockingQueue.put("a");
    blockingQueue.put("b");
    blockingQueue.put("c");
    //blockingQueue.put("d"); // 一直阻塞
    System.out.println("--------------------------");

    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    //System.out.println(blockingQueue.take());// 一直阻塞
}
/**
 *  阻塞超时
 */
public void test3() throws InterruptedException {
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

    blockingQueue.offer("a", 2, TimeUnit.SECONDS);
    blockingQueue.offer("a", 2, TimeUnit.SECONDS);
    blockingQueue.offer("a", 2, TimeUnit.SECONDS);

    blockingQueue.offer("a", 2, TimeUnit.SECONDS); // 阻塞超时
    System.out.println("--------------------------");

    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));// 阻塞超时
}

SynchronousQueue 同步队列

没有容量,进去一个元素,必须等待取出之后,才能再往里放一个元素!

put、take

同步队列:不存储元素,put一个元素,必须从里面take取出来,否则不能在put值!

9、线程池(重点)

池化技术

事先准备一些资源,使用从池中取,使用完归还到池中,以便后面在使用,重复利用。

线程池的好处

1、降低资源的消耗

2、提高响应速度

3、方便管理

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

三大方法

// 单例 一个线程
Executors.newSingleThreadExecutor();
// 缓存 根据cpu处理速度动态扩展
Executors.newCachedThreadPool();
// 固定 固定大小的线程数
Executors.newFixedThreadPool(10);

7大参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A4qEMYJ9-1628000760699)(.\imgs\01-JUC深层复习\image-20210724205607608.png)]

源码分析

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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

四种拒绝策略

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mtPpqab6-1628000760700)(.\imgs\01-JUC深层复习\image-20210724214100735.png)]

new ThreadPoolExecutor.AbortPolicy() // 阻塞队列满了,再来请求,抛出异常
new ThreadPoolExecutor.CallerRunsPolicy() // 那个线程请求的,由那个线程去处理
new ThreadPoolExecutor.DiscardPolicy() // 队列满了,丢弃任务,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy() //  队列满了,尝试去和最早的竞争,也不会抛出异常

小结和扩展

最大线程数该如何定义?

  • cpu密集型:

    几盒CPU,就用多少最大线程数,可以保持CPU的效率最高!

  • IO密集型:

    判断程序中有多少个任务十分消耗IO的线程,最大线程数 > 这个消耗IO线程的数量

// 获取cpu数量
int processors = Runtime.getRuntime().availableProcessors();

10、四大函数式接口(掌握)

新时代的程序员:lambda表达式、链式编程、函数型接口、Stream流式计算

函数接口:只有一个方法的接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

// 超级多@FunctionalInterface
// 简化编程模型,在新版本的框架底层大量应用
// foreach(消费者类的函数式接口)
1)Function 函数型接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d0xtrMhV-1628000760702)(.\imgs\01-JUC深层复习\image-20210724222840596.png)]

/**
 * Function 函数型接口,有一个输入型参数,有一个输出
 * 只要是 函数型接口 可以使用lambda表达式
 */
public class FunctionTest {
    public static void main(String[] args) {
//        Function function = new Function<String, String>() {
//            @Override
//            public String apply(String o) {
//                return o;
//            }
//        };
        Function function = (str) ->{ return str;};
        System.out.println(function.apply("abc"));
    }
}
2)Predicate 断定型接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-osITTyVC-1628000760703)(.\imgs\01-JUC深层复习\image-20210724223656113.png)].

/**
 * 断定型接口; 有一个输入参数,返回值只能是 布尔值!
 */ 
public class PredicateTest {
    public static void main(String[] args) {
//        Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String s) {
//                return s.isEmpty();
//            }
//        };
        Predicate<String> predicate = (str) -> {return str.isEmpty();};
        System.out.println(predicate.test(""));
    }
}
3)Consumer 消费型接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IsFR7dQ8-1628000760705)(.\imgs\01-JUC深层复习\image-20210724224334375.png)].

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

        Consumer<String> consumer = (str) -> { System.out.println(str); };
        consumer.accept("a");
    }
}
4)Supplier 供给型接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPQRXfBB-1628000760705)(.\imgs\01-JUC深层复习\image-20210724224643586.png)].

/**
 * Supplier  供给型接口: 只有输出,没有输入
 */
public class SupplierTest {
    public static void main(String[] args) {
//        Supplier<String> supplier = new Supplier<String>() {
//            @Override
//            public String get() {
//                return "Supplier 供给型接口";
//            }
//        };
        Supplier<String> supplier = () -> {return "Supplier 供给型接口";};
        System.out.println(supplier.get());
    }
}

11、Stream 流式计算

什么是 Stream 流式计算

计算

/**
 * 题目要求
 * 1、ID必须是偶数
 * 2、年龄大于23
 * 3、用户名转为大写
 * 4、用户名倒着排序
 * 5、只输出一个用户
 */
public class StreamTest {

    public static void main(String[] args) {
        User user1 = new User(1, "A", 21);
        User user2 = new User(2, "B", 22);
        User user3 = new User(3, "C", 23);
        User user4 = new User(4, "D", 24);
        User user5 = new User(5, "E", 25);
        List<User> list = Arrays.asList(user1,user2,user3,user4,user5);

        // Stream
        //lambda表达式、链式编程、函数型接口、Stream流式计算
        list.stream()
                .filter((u) ->{return u.getId() % 2== 0;})
                .filter(u -> u.getAge() > 23)
                .map(u->u.getName().toUpperCase())
                .sorted((u1, u2)->{return u1.compareTo(u2);})
                .limit(1)
                .forEach(System.out::println);
    }
}

@Data
@AllArgsConstructor // 有参构造器
@NoArgsConstructor // 无参构造器
class User{
    private int id;
    private String name;
    private int age;
}

12、ForkJoin

什么是 ForkJoin

ForkJoin 在JDK1.7就提供了,并行执行任务!提高效率。大数据量!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DpTRbGIW-1628000760706)(.\imgs\01-JUC深层复习\image-20210724231457397.png)]

ForkJoin 特点:工作窃取

这里维护的都是双端队列!所以可以从另一个放向执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vZ0RAGgy-1628000760707)(.\imgs\01-JUC深层复习\image-20210724231848038.png)]

Forkjoin 操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WtWWil3w-1628000760708)(.\imgs\01-JUC深层复习\image-20210724232624361.png)]

public class Test {

    // 普通算法
    @org.junit.Test
    public void test1() {
        long sum = 0;
        long start = System.currentTimeMillis();
        for (long i = 0; i < 1_0000_0000L; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + ", 耗时:" + (end - start));
    }

    // forkJoin算法
    @org.junit.Test
    public void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinTest forkJoinTest = new ForkJoinTest(0L, 1_0000_0000L);

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinTest);
        long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + ", 耗时:" + (end - start));
    }

    // stream 流算法
    @org.junit.Test
    public void test3() {
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0L, 1_0000_0000L).parallel().reduce(0,Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + ", 耗时:" + (end - start));
    }
}
/**
 * 计算和
 * 如何使用forkjoin
 * 1、forkjoinPool 通过他来执行
 * 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
 * 3、计算类要继承ForkJoinTask
 */
public class ForkJoinTest extends RecursiveTask<Long> {
    private Long start;
    private Long end;

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


    public ForkJoinTest(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;
            }
        }else {// forkjoin
            Long middle = (start + end) / 2;//中间值
            ForkJoinTest fork1 = new ForkJoinTest(start, middle);
            fork1.fork(); // 任务拆分,把任务压入队列
            ForkJoinTest fork2 = new ForkJoinTest(middle+1, end);
            fork2.fork(); // 任务拆分,把任务压入队列
            // 任务合并
            return fork1.join() + fork2.join();
        }
        return null;
    }
}

13、异步回调

/**
 *  异步调用
 *  //  异步执行
 *  //  失败回调
 *  //  成功回调
 */
public class Demo1 {
    // 没有返回值的 runAsync 异步调用
    public void test1() throws ExecutionException, InterruptedException {
        // 没有返回值的 runAsync 异步调用
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "=>Void");
        });
        System.out.println("main thread....");

        completableFuture.get(); // 获取执行阻塞
    }

    // 有返回值的 supplyAsync 异步调用
    public void test2() throws ExecutionException, InterruptedException {
        // 没有返回值的 runAsync 异步调用
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1024;
        });

        // 获取异步执行的结果 会阻塞
        completableFuture.whenComplete((t, u)->{
            // 调用成功的
            System.out.println("t => " + t); // 正常的返回结果
            System.out.println("u => " + u); // 异常信息
        }).exceptionally((e)->{
            // 调用失败的 返回的是错误信息
            System.out.println(e.getMessage());
            return 504;
        }).get();
    }
}

二、深层原理

1、JMM

谈谈Volatile 的理解

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

1、保证可见性

2、不保证原子性

3、禁止指令重排序

什么是JMM

JMM:java 内存 模型,不存在的东西,概念!约定!

关于JMM的一些约定

1、线程解锁前,必须把共享变量立刻刷新到主内存中!

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

3、加锁和解锁必须是同一把锁。

线程:主内存 工作内存

8中操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tRDgxMgy-1628000760708)(.\imgs\01-JUC深层复习\image-20210725011149554.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nHybjCxF-1628000760709)(.\imgs\01-JUC深层复习\image-20210725011441913.png)]

JMM也规范了主内存和线程工作内存进行数据交换操作。一共包括8中操作,并且每个操作都是原子性的。

  1. **lock(锁定):**作用于主内存的变量,一个变量在同一时间只能一个线程锁定。该操作表示该线程独占锁定的变量。
  2. **unlock(解锁):**作用于主内存的变量,表示这个变量的状态由处于锁定状态被释放,这样其他线程才能对该变量进行锁定。
  3. **read(读取):**作用于主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用。
  4. **load(载入):**作用于线程的工作内存的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中(副本是相对于主内存的变量而言的)。
  5. **use(使用):**作用于线程的工作内存中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作。
  6. **assign(赋值):**作用于线程的工作内存的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作。
  7. **store(存储):**作用于线程的工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用。
  8. **write(写入):**作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。

JMM规定了以上8中操作需要按照如下规则进行

  • 不允许read和load、store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者从工作内存发起回写了但主内存不接受的情况出现。
  • 不允许一个线程丢弃它的最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存。
  • 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
  • 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
  • 一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
  • 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
  • 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdVxOwZ0-1628000760710)(.\imgs\01-JUC深层复习\image-20210725012304142.png)]

2、Volatile

1、保证可见性

public class VolatileTest {
    // status 值加volatile再能保证 A线程能读取到最新的值
    private volatile static int status = 0 ;

    public static void main(String[] args) {
        new Thread(()->{
            while (status == 0){

            }
        }, "A").start();

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

        status = 1;
        System.out.println(status);
    }
}

2、不保证原子性

/**
 * 不保证原子性
 */
public class Test2 {

    private volatile static int num = 0;

    public static void add(){
        num++;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    add();
                }
            }, "A").start();
        }
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println("num = " + num);
    }
}

如果不加lock 或 synchronized 怎么样保证原子性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a8KCLUFB-1628000760710)(.\imgs\01-JUC深层复习\image-20210725014846797.png)]

使用原子类

public class Test2 {

    private volatile static AtomicInteger num = new AtomicInteger();

    public static void add(){
        // cas 操作
        num.getAndIncrement();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    add();
                }
            }, "A").start();
        }
        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println("num = " + num);
    }
}

这些类的底层都直接和底层系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在

指令重排

什么是 指令重排:源代码–>编译器优化的重排–>指令并行也可能重排–>内存系统也可能重排–>执行

处理器在指令重排的时候,考虑: 数据之间的依赖性,要保证结果是正确的

int x = 1 ;
int y = 2 ;
x = x + 5 ;
y = x * x ;

我们所期望的 1234 但是可能执行的时候变回 2134 1324 

可能造成影响的结果: a b x y

线程A线程B
x=ay=b
b=1a=2

正常的结果:x = 0 ; y =0 ; 但可能由于指令重排

线程A线程B
b=1a=2
x=ay=b

指令重排导致的诡异结果: x = 2; y = 1;

volatile可以避免指令重排

内存屏障。CPU指令。作用:

1、保证特定的操作的执行顺序!

2、可以保证某些变量的内存可见性(利用这个特性volatile实现的可见性)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4wIIX3pI-1628000760711)(.\imgs\01-JUC深层复习\image-20210725022224622.png)].

volatile 可以保证 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

3、单例模式

饿汉式

/**
 *  饿汉式 单例
 */
public class Hungry {
    // 可能浪费空间
    private byte[] data1 = new byte[1024];
    private byte[] data2 = new byte[1024];
    private byte[] data3 = new byte[1024];
    private byte[] data4 = new byte[1024];

    private Hungry() {
        this.hungry = hungry;
    }

    private Hungry hungry = new Hungry();

    public Hungry getInstance(){
        return hungry;
    }

}

DCL 懒汉式

/**
 * 懒汉式 单例
 */
public class LazyMan {
    private static volatile LazyMan lazyMan;

    // 标志位 防止反射找到这个字段
    private static boolean rentianjie = false;

    private LazyMan() {
        synchronized (LazyMan.class){
            if(rentianjie){
                rentianjie = true;
            } else {
                throw new RuntimeException("不要试图使用反射破环异常");
            }
        }
    }

    // 双重检锁  懒汉式单例  DCL懒汉式
    public static LazyMan getInstance(){
        if(lazyMan == null){
            synchronized (LazyMan.class){
                if(lazyMan == null){
                    lazyMan = new LazyMan(); // 不是原子性操作
                }
            }
        }
        return lazyMan;
    }

    /* *
     * 1、分配空间
     * 2、执行构造方法
     * 3、把这个对象指向这个空间
     *
     * 多线程情况下 可能导致指令重排
     */
}

静态内部类

/**
 * 静态内部类
 */
public class Holder {

    private Holder() {
    }

    public Holder getInstance() {
        return InnerClass.HOLDER;
    }

    public static class InnerClass {
        private static final Holder HOLDER = new Holder();
    }
}

使用反射破坏单例

// 反射
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//        LazyMan lazyMan = LazyMan.getInstance();
        Field rentianjie = LazyMan.class.getDeclaredField("rentianjie");
        rentianjie.setAccessible(true);

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan = declaredConstructor.newInstance();

        rentianjie.set(lazyMan, false);
        LazyMan lazyMan2 = declaredConstructor.newInstance();

        System.out.println(lazyMan);
        System.out.println(lazyMan2);
    }

枚举

/**
 * enum 是什么? 本身也是一个class类
 */
public enum EnumSingle {
    INSTANCE;

    public EnumSingle getInstance() {
        return INSTANCE;
    }
}

class Test {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle enumSingle = EnumSingle.INSTANCE;
        // 枚举类的构造器是 (String , int)
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle1 = declaredConstructor.newInstance();

        System.out.println(enumSingle);
        System.out.println(enumSingle1);
    }
}

jad 反编译 jad -sjava ***.class

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingle.java

package com.example.single;


public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/example/single/EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
        super(s, i);
    }

    public EnumSingle getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}

4、CAS

什么是 CAS

public class CASDemo {

    // CAS compareAndSet 比较并交换!
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        // 期望 、更新
        // public final boolean compareAndSet(int expect, int update)
        // 如果我期望的值达到了,那么就更新,否则,就不更新,CAS 是CPU的并发原语!
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement();
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

Unsafe 类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uE64j9EB-1628000760712)(.\imgs\01-JUC深层复习\image-20210725034951876.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hBzPHCdE-1628000760712)(D:\workspace\my-learn\my-juc\doc\imgs\01-JUC深层复习\image-20210725035253322.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m4OREj7s-1628000760713)(.\imgs\01-JUC深层复习\image-20210725035418760.png)]

CAS:比较当前工作内存中的值和主内存中的值,如果这个是期望值,那么就执行操作!如果不是就一直循环!

缺点:

1、循环会耗时

2、一次性只能保证一个共享变量的原子性

3、ABA问题

CAS : ABA问题(狸猫换太子)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BKDOLHDZ-1628000760714)(.\imgs\01-JUC深层复习\image-20210725130537070.png)]

线程A取比较内存中的A值过后,线程B也来操作内存中的A值,先改为了又改为了1,这个时候线程A读取的值虽然是期望值,但是已经不是最开始的值了,线程A不能返现值A被修改过,这就是ABA问题。

5、原子引用

解决ABA 问题,引入原子引用!对应的思想:乐观锁!

带版本号的原子操作!

// CAS aba问题
@Test
public void test1() {
    // 期望值 , 版本戳
    // AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
    AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference(1, 1);
    new Thread(()->{
        int stamp = atomicInteger.getStamp();//获取版本号
        System.out.println("A 1 ->> stamp = " +stamp);

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(atomicInteger.compareAndSet(1, 2, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
        System.out.println("A 2 ->> stamp = " +stamp);
        System.out.println(atomicInteger.compareAndSet(2, 1, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
        System.out.println("A 3 ->> stamp = " +stamp);

    },"A").start();
    // 乐观锁同理
    new Thread(()->{
        int stamp = atomicInteger.getStamp(); //获取版本号
        System.out.println("B 1 ->> stamp = " +stamp);
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(atomicInteger.compareAndSet(1, 6, stamp, stamp + 1));

        System.out.println("B 2 ->> stamp = " +atomicInteger.getStamp());

    },"B").start();

    while (Thread.activeCount() > 2){
        Thread.yield();
    }
}

Integer 使用了对象缓存机制,默认范围是 -127 ~ 128 ,推荐使用静态工厂方法value of 获取对象的实例,而不是new,因为 value of 使用缓存,而 new 一定会创建新的对象分配新的内存空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iz4zGeH9-1628000760714)(.\imgs\01-JUC深层复习\image-20210725133257115.png)]

6、各种锁

1)公平锁、非公平锁

公平锁:非常公平,不能插队,必须先来后到

非公平锁:不公平,可以插队(默认都是非公平锁)

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
2)可重入锁

可重入锁(递归锁)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NWU3DZcS-1628000760715)(.\imgs\01-JUC深层复习\image-20210725140105814.png)]

synchronized 版本

public class Demo1 {

    public static void main(String[] args) {
        Phone p = new Phone();
        new Thread(()->{
            p.sms();
        },"A").start();

        new Thread(()->{
            p.sms();
        },"B").start();

    }

}

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName() + "sms");
        call();
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName() + "call");
    }
}

Lock 版

public class Demo2 {
    public static void main(String[] args) {
        Phone2 p = new Phone2();
        new Thread(()->{
            p.sms();
        },"A").start();

        new Thread(()->{
            p.sms();
        },"B").start();

    }
}

class Phone2{
    private Lock lock = new ReentrantLock();
    public synchronized void sms(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "sms");
            call();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    public synchronized void call(){
        lock.lock(); // 细节问题,lock锁必须配对使用,lock unlock,防止死锁
        try {
            System.out.println(Thread.currentThread().getName() + "call");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
3)自旋锁

判断是否可以拿到锁,拿不到就继续拿,直到拿到所位置。

自实现自旋锁

public class SpinlockDemo {

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

    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "=>> myLock");
        // 自旋锁
        while (!atomicReference.compareAndSet(null, thread)){
        }
    }

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

测试

public class SpinlockTest {

    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();
            }
        },"T-1").start();

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

死锁是什么

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ku3fASai-1628000760716)(.\imgs\01-JUC深层复习\image-20210725144540802.png)]

/**
 * 死锁
 */
public class DeadLockTest {

    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new MyThread(lockA,lockB)).start();
        new Thread(new MyThread(lockB,lockA)).start();

    }
}

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

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized(lockA){
            System.out.println(Thread.currentThread().getName() + "-LockA=>>getLockB");

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + "-LockB=>>getLockA");
            }
        }
    }
}

解决问题

1、使用 jps -l 定位进程号

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SHwmifYG-1628000760716)(.\imgs\01-JUC深层复习\image-20210725150226138.png)].

2、使用 jstack 进程号找到问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uvKJm0km-1628000760717)(.\imgs\01-JUC深层复习\image-20210725151104634.png)]

死锁的四个必要条件

1、互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
破坏其中一个即可解锁。

ull);
System.out.println(thread.getName() + “=>> myUnLock”);
}
}


> 测试

```java
public class SpinlockTest {

    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();
            }
        },"T-1").start();

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

死锁是什么

[外链图片转存中…(img-ku3fASai-1628000760716)]

/**
 * 死锁
 */
public class DeadLockTest {

    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new MyThread(lockA,lockB)).start();
        new Thread(new MyThread(lockB,lockA)).start();

    }
}

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

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized(lockA){
            System.out.println(Thread.currentThread().getName() + "-LockA=>>getLockB");

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName() + "-LockB=>>getLockA");
            }
        }
    }
}

解决问题

1、使用 jps -l 定位进程号

[外链图片转存中…(img-SHwmifYG-1628000760716)].

2、使用 jstack 进程号找到问题

[外链图片转存中…(img-uvKJm0km-1628000760717)]

死锁的四个必要条件

1、互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
破坏其中一个即可解锁。
参考狂神大佬

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值