Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)

多线程进阶 JUC并发编程

(需要完整Markdown文件可以私聊我哦)

文章目录

  • 多线程进阶 JUC并发编程
    • 1. 进程和线程
    • 2. lock锁(重点)
    • 3. 生产者和消费者问题
    • 4. 如何判断锁的是谁?8锁现象
    • 5. 不安全类集合
    • 6 . Callable()
    • 7. 常用辅助类
      • 7.1、CountDownLatch(减法计数器)
      • 7.2、CyclicBarrier(加法计数器)
      • 7.3、Semaphore(信号量)
    • 8.读写锁
    • 9.阻塞队列
    • 10.同步队列
    • 11.线程池(重点)
    • 12.四大函数式接口(必须掌握)
    • 13.Stream流式计算
    • 14.ForkJoin
    • 15.异步回调
    • 16.JMM
    • 17.Volatile
    • 18.彻底玩转单例模式
    • 19.深入理解CAS
    • 20.原子引用
    • 21.各种锁的解释
      • 1、公平锁、非公平锁
      • 2、可重入锁
      • 3、自旋锁
      • 4、死锁

1. 进程和线程

public static void main(String[] args) {
    // 获取cpu核数
    System.out.println(Runtime.getRuntime().availableProcessors());
}

线程和进程

  • 进程:一个运行中的程序,QQ.exe 程序的集合
  • 一个进程往往可以包含多个线程,至少包含一个
  • java默认有几个线程? 2个 main gc
  • 线程 : 比如开了一个进程Typora 写字自动保存
  • **java真的可以开启线程吗?**不可以 调用的是本地的方法,底层是c++

并行 并发

并发:一时刻,多线程交替执行 并行:一时刻,多线程同时执行

  • 并发:(多线程操作同一个资源)

    cpu一核,模拟出来多条线程,天下武功唯快不破,快速交替

  • 并行: (多个人一起走)

    cpu多核, 多个线程同时执行;线程池

  • 并发编程的本质 : 充分利用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需要

2. lock锁(重点)

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第1张图片

公平锁:十分公平,先来后到

非公平锁: 十分不公平,可以插队(默认)

Synchronized 和 lock 区别

1、Synchronized 内置的java关键字, Lock是一个java类

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

3、Synchronized 会自动释放锁,Lock必须要手动释放锁,否则会死锁。

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

5、Synchronized 可重入锁,不可中断,非公平;Lock 可重入锁,可以判断锁,可以自行设置是否公平

可重入锁:可以多次获取同一个锁,释放也要多次释放

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

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

3. 生产者和消费者问题

生产者和消费者问题 Synchronized 版

/*
    线程之间的通信问题:生产者消费者问题 等待唤醒 通知唤醒
    线程交替执行 A B 操作同一个变量
 */

public class Main {
    public static void main(String[] args) {
        Data data = new Data();

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

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

    // 判断等待 业务 通知
    static class Data { // 资源类
        private int number = 0;
        // + 1
        public synchronized void increment() throws InterruptedException {
            if (number != 0) {
                // 等待
                this.wait();
            }
            number ++;
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            // 通知其他线程 +1完毕了
            this.notifyAll();
        }
        // - 1
        public synchronized void decrement() throws InterruptedException {
            if (number == 0) {
                // 等待
                this.wait();
            }
            number --;
            // 通知其他线程 -1完毕了
            System.out.println(Thread.currentThread().getName() + "=>" + number);
            this.notifyAll();
        }

    }
}

问题存在: a b c d 四个线程?

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第2张图片

解决:if改成while

1、wait会释放锁

2、是唤醒后如果是if,那么不重新判断直接执行,如果是while,就还需要判断wait是在哪里睡,就在哪里醒。notifyall会唤醒所有睡觉的线程。

结论:就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。

JUC 版本的生产者和消费者问题

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第3张图片

代码实现

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

public class Main {
    public static void main(String[] args) {
        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();
    }
    static class Data { // 资源类
        private int number = 0;

        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        // + 1
        public void increment() {
            lock.lock();
            try {
                while (number != 0) {
                    // 等待
                    condition.await();
                }
                number ++;
                System.out.println(Thread.currentThread().getName() + "=>" + number);
                // 通知其他线程 +1完毕了
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        // - 1
        public void decrement() {
            lock.lock();
            try {
                while (number == 0) {
                    // 等待
                    condition.await();
                }
                number --;
                // 通知其他线程 -1完毕了
                System.out.println(Thread.currentThread().getName() + "=>" + number);
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

Condation

优势:精准的通知和唤醒线程

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

/**
 * A 执行完调用B, B 执行完调用C, C执行完调用A
 */

// 判断 -> 执行 -> 通知
public class Main {
    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();
    }

    static class Data {
        private Lock lock = new ReentrantLock();
        private Condition condition1 = lock.newCondition();
        private Condition condition2 = lock.newCondition();
        private Condition condition3 = lock.newCondition();
        private int number = 1; // 1.A 2.B 3.C

        public void printA() {
            lock.lock();
            try {
                while (number != 1) {
                    // 等待
                    condition1.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>AAAAA");
                // 唤醒指定的人 B
                number = 2;
                condition2.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void printB() {
            lock.lock();
            try {
                while (number != 2) {
                    condition2.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>BBBBB");
                // 唤醒 C
                number = 3;
                condition3.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void printC() {
            lock.lock();
            try {
                while (number != 3) {
                    condition3.await();
                }
                System.out.println(Thread.currentThread().getName() + "=>CCCCC");
                number = 1;
                condition1.signal();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

4. 如何判断锁的是谁?8锁现象

锁 只会锁两个东西 1. 对象 2. class

package lock8;

import java.util.concurrent.TimeUnit;

/**
 * 8锁
 * 1. 标准情况下 两个线程 先发短信还是打电话? 发短信
 * 2. sendsms延迟4s 两个线程 先发短信还是打电话? 发短信
 */

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

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

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

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

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

import java.util.concurrent.TimeUnit;

/**
 * 3. 增加一个普通方法后  先hello还是打电话? (普通方法)
 * 4. 两个对象,两个同步方法 发短信还是打电话? (打电话)
 */

public class Main {
    public static void main(String[] args) {
        // 两个对象 两把锁 一个对象只能有一把锁
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone1.sendMes();
        }, "A").start();

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

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

    static class Phone {
        // synchronized锁的对象是方法的调用者
        public synchronized void sendMes() {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }

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

        // 这里没有锁 ! 不是同步方法 不受锁的影响
        public void hello() {
            System.out.println("Hello");
        }
    }
}
package lock8;

import java.util.concurrent.TimeUnit;

/**
 * 5. 增加两个静态同步方法,只有一个对象,先 发短信 还是 打电话? (发短信)
 * 6. 增加两个静态同步方法,两个对象,先 发短信 还是 打电话? (发短信)
 */

public class Main {
    public static void main(String[] args) {
        // 两个对象 class 模板只有一个 static 锁的是class
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone1.sendMes();
        }, "A").start();

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

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

    // Phone只有一个唯一的 class 对象
    static class Phone {
        // synchronized锁的对象是方法的调用者
        // static 静态方法 类加载就有了!锁的是Class
        public static synchronized void sendMes() {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }

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

    }
}
package lock8;

import java.util.concurrent.TimeUnit;

/**
 * 7. 一个静态同步方法 一个普通同步方法 一个对象 先打电话还是发短信? (打电话)
 * 8.一个静态同步方法 一个普通同步方法 两个对象 先打电话还是发短信? (打电话)
 */

public class Main {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone1.sendMes();
        }, "A").start();

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

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

    static class Phone {

        // 静态同步方法 锁的Class类模板
        public static synchronized void sendMes() {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("发短信");
        }

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

    }
}

总结

new (this)具体的一个手机(对象)

static( Class) 具体的一个模板

5. 不安全类集合

List 不安全

package 不安全集合;

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

// ConcurrentModificationException 并发修改异常
public class Main {
    public static void main(String[] args) {
        // 并发下 ArrayList不安全
        /**
         * 解决方法:
         *  1. List list = new Vector<>();
         *  2. List list = Collections.synchronizedList(new ArrayList<>());
         *  3. List list = new CopyOnWriteArrayList<>();
         */

        // CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
        // 写入的时候复制一个数组出来 写完再插入进去
        // CopyOnWriteArrayList 比 Vector 效率高
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                list.forEach(System.out::println);
            }, String.valueOf(i)).start();
        }
    }
}

Set 不安全

package 不安全集合;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;

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

//        Set set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                set.forEach(System.out::println);
            }, String.valueOf(i)).start();
        }
    }
}

map 不安全

package 不安全集合;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    public static void main(String[] args) {
        // 默认等价于 new HashMap<>(16, 0.75)
        // Map map = new HashMap<>();
        // Map map = Collections.synchronizedMap(new HashMap<>());
        Map<String, String> map = new ConcurrentHashMap<>();
        // 加载因子 初始容量
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(map);
            }, String.valueOf(i)).start();
        }
    }
}

6 . Callable()

1、可以有返回值

2 、可以抛出异常

3、方法不同,run() / call()

package callable;

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

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread);
        new Thread(futureTask, "A").start();

        Integer o = (Integer) futureTask.get();
        System.out.println(o);
    }


    // 泛型的参数等于方法的返回值
    static class MyThread implements Callable <Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println("CALL");
            return 1024;
        }
    }
}

7. 常用辅助类

7.1、CountDownLatch(减法计数器)

package add;

import java.util.concurrent.CountDownLatch;

// 计数器
public class Main {
    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("Close Door");
    }
}

原理:

countDownLatch.countDown(); // 数量-1

countDownLatch.await(); // 等待计数器归零 再向下执行

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

7.2、CyclicBarrier(加法计数器)

package add;

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

public class Main {
    public static void main(String[] args) {
        /**
         *  集齐七颗 龙珠召唤神龙
         */
        // 召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()-> System.out.println("召唤神龙成功"));
        for (int i = 1; i <= 7; i++) {
            final int tmp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集了第" + tmp + "个龙珠");
                try {
                    cyclicBarrier.await(); //等待 + 计数
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

7.3、Semaphore(信号量)

有点像pv操作

package add;

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

public class Main {
    public static void main(String[] args) {
        // 线程数量 : 停车位! 限流!
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++)
            new Thread(() -> {
                // acquire() 得到
                try {
                    // 线程满了 acquire就会阻塞
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "抢到车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // release() 释放
                    semaphore.release();
                }

            }).start();
    }
}

semaphore.acquire(); 获得,假设如果已经满了,等待,直到被释放为止!

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

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

8.读写锁

ReadWriteLock

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

读的时候不能有写操作,所以读的时候也要加锁。 这样可以更加细腻度的划分;

  • 读锁:(共享锁) 多个线程可以同时占有
  • 写锁:(独占锁)一次只能被一个线程占有
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        MyCache cache = new MyCache();

        for (int i = 1; i <= 5; i++) {
            final int tmp = i;
            new Thread(() -> {
                cache.put(tmp + "", tmp + "");
            }, String.valueOf(i)).start();
        }

        for (int i = 1; i <= 5; i++) {
            final int tmp = i;
            new Thread(() -> {
                cache.get(tmp + "");
            }, String.valueOf(i)).start();
        }

    }
    static class MyCache {
        //volatile 保证内存可见性!不能保证原子性!
        private volatile Map<String, Object> map = new HashMap<>();

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

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

    }
}

9.阻塞队列

BlockingQueue:有容量

什么情况下会是要用到阻塞队列? 多线程, 线程池。

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第4张图片

四组API

方式 抛出异常 有返回值,不抛出异常 阻塞等待 超时等待
添加 add() offer() put() offer(, ,)
移除 remove() poll() take() poll(,)
检测队首元素 element() peek() - -

10.同步队列

SynchronizedQueue :没有容量

package Queue;

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

/**
 * 和其他的BlockingQueue不一样 SynchronousQueue 不存储元素
 *  put了一个元素,必须从里面take取出来,否则不能再put进去值
 */

public class Main {
    public static void main(String[] args) {
        BlockingQueue<String> queue = new SynchronousQueue<>(); // 同步队列

        new Thread(() -> {
            try {
                queue.put("1");
                System.out.println(Thread.currentThread().getName() + " put 1");
                queue.put("2");
                System.out.println(Thread.currentThread().getName() + " put 2");
                queue.put("3");
                System.out.println(Thread.currentThread().getName() + " put 3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " get " + queue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " get "+ queue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + " get "+ queue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T2").start();
    }
}

11.线程池(重点)

线程池:三大方法、七大参数、四种拒绝策略

池化技术

程序的运行,本质:占用系统的资源!优化资源的使用 !=> 池化技术

线程池、连接池、内存池、对象池…

池化技术: 事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我。

线程池的好处

1、降低资源的消耗,线程可以复用

2、提高响应的速度

3、方便管理

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

线程池: 三大方法

package pool;

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

public class Main {
    public static void main(String[] args) {
        // 单个线程
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();
//        // 创建一个固定的线程池的大小
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);
//        // 可伸缩的 线程池
        ExecutorService threadPool = Executors.newCachedThreadPool();
        
        try {
            for (int i = 0; i < 10; i++) {
            // 使用线程池后,使用线程池来创建线程
            threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "创建成功"));
        }
        } 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) { // 拒绝策略
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第5张图片

keepAliveTime:线程的最大生命周期,这里的生命周期有两个约束条件,一:该参数针对的是超过corePoolSize数量的线程;二:处于非运行状态的线程。即非核心线程数存活时间。

手动创建一个线程池

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第6张图片

package pool;

import java.util.concurrent.*;

/**
 *  new ThreadPoolExecutor.AbortPolicy() // 银行满了还有人进来,就不处理这个人 抛出异常
 *  new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的回哪里(main) 执行
 *  new ThreadPoolExecutor.DiscardPolicy() // 队列满了,丢掉任务,不会抛出异常
 *  new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满了,抛弃队列里面最老的那个,代替他的位置进入队列里,不会对正在执行的线程有任何影响,不会抛出异常
 */

public class Main {
    public static void main(String[] args) {
        //手动创建线程池
        //前两个是核心线程,就是跟随线程池生命周期,后三个为临时线程,一旦超过设定时间无任务就会销毁
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满了,抛弃队列里面最老的那个,代替他的位置进入队列里
                );
        try {
            // 最大承载 : 队列 + max
            for (int i = 1; i <= 9; i++) {
            // 使用线程池后,使用线程池来创建线程
            threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "创建成功"));
        }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完 关闭线程池
            threadPool.shutdown();
        }
    }
}

小结和拓展

了解

最大线程到底该如何定义?

1、CPU 密集型 CPU几个核,几条线程就能同时进行

Runtime.getRuntime().availableProcessors()获取CPU的核数

2、IO 密集型 > 判断你的程序中十分耗IO的线程。(一般设成2倍)

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

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

函数式接口:只有一个【抽象】方法的接口

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

// 简化编程模型,在新版本的框架底层大量使用
// foreach(消费者类型的函数式接口)

代码测试:

package function;

import java.util.function.Function;

/**
 * Function 函数型接口 有一个输入参数,有一个输出参数
 * 只要是 函数型接口 都可以使用lambda表达式简化
 */

public class Main {
    public static void main(String[] args) {
//        Function function = new Function() {
//            @Override
//            public String apply(str) {
//                return str;
//            }
//        };

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

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

package function;

import java.util.function.Predicate;

// 断定形接口 有一个输入参数,返回值只能是布尔值
public class Main {
    public static void main(String[] args) {
//        Predicate predicate = new Predicate<>(){
//            // 判断字符串是否为空
//            @Override
//            public boolean test(String str) {
//                return str.isEmpty();
//            }
//        };
        Predicate<String> predicate = str -> str.isEmpty();
        System.out.println(predicate.test("qwe"));
    }
}

消费型接口:只有输入 没有返回值

package function;

import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
//        Consumer consumer = new Consumer() {
//            @Override
//            public void accept(String str) {
//                System.out.println(str);
//            }
//        };
        Consumer<String> consumer = System.out::println;
        consumer.accept("233");
    }
}

供给型接口:没有参数 只有返回值

package function;

import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
//        Supplier supplier = new Supplier() {
//            @Override
//            public String get() {
//                return "1024";
//            }
//        };
        Supplier<String> supplier = () -> "1024";
        System.out.println(supplier.get());
    }
}

13.Stream流式计算

什么是Stream流式计算?

大数据:存储 +计算

存储交给集合,计算都应该交给流来做!

package Stream;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
    private int id;
    private String name;
    private int age;
}
package Stream;

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

/**
 * 题目要求:一分钟内完成此题,只能用一行代码实现!
 * 现在有5个用户!筛选:
 * 1、ID必须是偶数
 * 2、年龄必须大于23岁
 * 3、用户名转为大写字母
 * 4、用户名字母倒着排序
 * 5、只输出一个用户!
 */

public class Main {
    public static void main(String[] args) {
        User user1 = new User(1, "a", 22);
        User user2 = new User(2, "b", 23);
        User user3 = new User(3, "c", 24);
        User user4 = new User(4, "d", 25);
        User user5 = new User(6, "e", 26);
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        list.stream()
                .filter(user -> user.getId() % 2 == 0)
                .filter(user -> user.getAge() > 23)
                .map(user -> user.getName().toUpperCase())
//                .sorted(((o1, o2) ->  o2.compareTo(o1)))
                .sorted((o1, o2) -> o2.charAt(0) - o1.charAt(0))
                .limit(1)
                .forEach(System.out::println);
    }
}

14.ForkJoin

什么是ForkJoin

ForkJoin :并行执行任务!提高效率,大数据量

分支计算,结果合并。

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第7张图片

ForkJoin特点:工作窃取

这里面维护的都是双端队列

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第8张图片

ForkJoin操作

package ForkJoin;

import java.util.concurrent.RecursiveTask;

/**
 * 1、forkJoinPooL通过它来执行
 * 2、计算任务forkJoinPooL.execute( ForkJoinTask task )
 * 3.计算类要基ForkJoinTask
 */

// 求和计算
public class ForkJoinTest extends RecursiveTask<Long> {

    private long start;
    private long end;

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

    public ForkJoinTest(long start, long end) {
        this.start = start;
        this.end = end;
    }

    // 计算方法
    @Override
    protected Long compute() {
        if (end - start < tmp) {
            long sum = 0L;
            for (long i = start; i <= end; i++)
                sum += i;
            return sum;
        } else { // ForkJoin
            long mid = start + end >> 1;
            ForkJoinTest task1 = new ForkJoinTest(start, mid);
            task1.fork(); // 拆分任务,将任务压入线程队列
            ForkJoinTest task2 = new ForkJoinTest(mid + 1, end);
            task2.fork(); // 拆分任务,将任务压入线程队列

            return task1.join() + task2.join();
        }
    }
}
package ForkJoin;

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

public class Main {

    public static void main(String[] args) throws Exception {
//        test1(); //413 ms
//        test2(); //355 ms
        test3(); //171 ms
    }

    public static void test1() {
        long start = System.currentTimeMillis();

        long sum = 0;
        for (long i = 1L; i <= 10_0000_0000; i++)
            sum += i;
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + "时间:" + (end - start));
    }

    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTest task = new ForkJoinTest(0L, 10_0000_0000);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + "时间:" + (end - start));
    }

    public static void test3() {
        long start = System.currentTimeMillis();
        // Stream

        long sum = LongStream
                .rangeClosed(0L, 10_0000_0000)
                .parallel()
                .reduce(0, Long::sum);

        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + "时间:" + (end - start));
    }
}

15.异步回调

Future 设计的初衷:对将来的某个事件的结果进行建模

package future;

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

/**
 * 异步调用 :ajax CompletableFuture
 * // 异步回调
 * // 成功回调
 * // 失败回调
 */

public class Main {
    public static void main(String[] args) throws Exception {
        // 发起一个请求
        // 没有返回值的 runAsync 异步回调
//        CompletableFuture completableFuture = CompletableFuture.runAsync(() -> {
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName() + "runAsync");
//        });
//
//        System.out.println(21323);
//
//        completableFuture.get(); // 获取阻塞执行结果

        // 有返回值的 runAsync 异步回调
        // ajax 成功和失败的回调
        // 成功 返回结果 失败 返回错误信息
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer");
            int i = 1 / 0;
            return 200;
        });

        System.out.println(
                completableFuture1.whenComplete((t, u) -> {
                    System.out.println("t===>" + t); // 正常返回结果
                    System.out.println("u===>" + u); // 错误信息
                }).exceptionally((e) -> {
                    System.out.println(e.getMessage());
                    return 500;
                }).get()
        );
    }
}

16.JMM

请你谈谈对Volatile的理解

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

1、保证可见性

2、不保证原子性

3、禁止指令重排

什么是JMM?

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

关于JMM的一些同步约定:

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

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

3、加锁和解锁一定是同一把锁

线程: 工作内存、主内存

8种操作:

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第9张图片
可能存在问题:

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第10张图片

  • lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。
  • unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
  • read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
  • use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
  • assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
  • store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
  • write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中、

代码测试:

import java.util.concurrent.TimeUnit;

public class Main {
    static int num = 0;
    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            while (num == 0) {
                
            }
        }).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            num = 1;
            System.out.println(num);
        }).start();
    }
}

问题:程序并不知道主内存的值已经被修改过了

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第11张图片

17.Volatile

1.保证可见性

import java.util.concurrent.TimeUnit;

public class JMMTest {
    // 如果不加volatile 程序就会死循环
    //      加volatile 可以保证可见性
    static volatile int num = 0;
    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> { // 线程1 对于主内存的变化不知道
            while (num == 0) {

            }
        }).start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            num = 1;
            System.out.println(num);
        }).start();
    }
}

2.不保证原子性

原子性:不可分割

线程A在执行任务的时候,不能被打扰,也不能被分割。要么同时成功,要么同时失败!

public class Main {

    // volatile 不保证原子性
    private volatile static int num = 0;

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

    public static void main(String[] args) {

        // 理论上num结果应该为2万
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) // main gc
            Thread.yield(); // 线程礼让

        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}

我们反编译这段代码看看:

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第12张图片

num++ 不是一个原子性操作,所以有可能会出现多个线程同时获得这个值再写回。即 加的次数是固定的,有的线程取的是旧的值,结果会变小。

原子类为什么这么高级?

如果不加lock和Synchronized 怎么保证原子性?

package Volatile_test;

import java.util.concurrent.atomic.AtomicInteger;

public class Main {

    // volatile 不保证原子性
    // 原子类的 Integer
    private static final AtomicInteger num = new AtomicInteger(0);

    public static void add() {
        //num ++;
        num.getAndIncrement(); //AtomicInteger + 1 方法
    }

    public static void main(String[] args) {

        // 理论上num结果应该为2万
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) // main gc
            Thread.yield(); // 线程礼让

        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}

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

3.禁止指令重排

什么是指令重排 : 你写的程序,计算机并不是按照你写的那样去与执行的。

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

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

int x = 1; // 1
int y = 2; // 2
x = x + 5; // 3
y = x * x; // 4

//我们期望的: 1234 
//可能出现的: 2143 1324
//不可能的:	  4213!

可能造成影响的结果: a b x y 这四个值的默认都是0

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

正常的结果 x = 0; y = 0;但是可能因为指令重排

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

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

volatile可以避免指令重排:

内存屏障:CPU指令 作用:

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

2、可以保证某些内存变量的可见性(利用这些特性volatile实现了可见性)
Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第13张图片

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

18.彻底玩转单例模式

饿汉式

package 单例模式;

// 饿汉式单例
public class Hungry {

    // 可能会浪费空间
    private byte[] data1 = new byte[1024 * 1024];
    private byte[] data2 = new byte[1024 * 1024];
    private byte[] data3 = new byte[1024 * 1024];
    private byte[] data4 = new byte[1024 * 1024];

    // 单例模式最重要的一个思想:构造器私有
    private Hungry() {

    }

    private final static Hungry HUNGRY = new Hungry();

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

懒汉式 DCL

package 单例模式;

import java.lang.reflect.Constructor;

// 懒汉式单例
// 道高一尺魔高一丈
public class LazyMan {

    private static boolean flag = false;

    private LazyMan() {

        synchronized (LazyMan.class) {
            if (!flag) {
                flag = true;
            } else {
                throw new RuntimeException("不要试图使用反射破坏单例模式!!");
            }
//            if (lazyMan != null)
//                throw new RuntimeException("不要试图使用反射破坏单例模式!!");
        }

        System.out.println(Thread.currentThread().getName() + " 启动");
    }

    private volatile static LazyMan lazyMan;

    //双重检测锁模式的 懒汉式单例 DCL懒汉式
    //这里双重检测是考虑到效率问题,加最外层if判断可以使后续的多个线程直接获得对象而不需要等待
    public static LazyMan getInstance() {
        if (lazyMan == null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null)
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                /**
                 * 1. 分配内存空间
                 * 2. 执行构造方法
                 * 3. 把这个对象指向这个空间
                 *
                 * 123
                 * 132 A
                 *     B // 此时lazyMan 还没有完成构造
                 *     所以要+volatile修饰 保证不发生指令重排现象
                 */
            }
        }
        return lazyMan;
    }

    // 多线程并发
    public static void main(String[] args) throws Exception{
//        for (int i = 1; i <= 10; i++) {
//            new Thread(() -> {
//                LazyMan.getInstance();
//            }).start();
//        }
//        LazyMan instance1 = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true); // 破除私有权限
        LazyMan instance2 = declaredConstructor.newInstance();
        LazyMan instance3 = declaredConstructor.newInstance();

//        System.out.println(instance1 == instance2); // false 反射可以破坏单例
        System.out.println(instance3 == instance2);
    }

}

静态内部类

package 单例模式;

public class Holder {

    private Holder() {

    }

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

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

单例不安全 因为有反射!

package 单例模式;

import java.lang.reflect.Constructor;

// 枚举 本身也是一个Class类
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance() {
        return INSTANCE;
    }

}

class Test {
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;

        //(不加参数报错)java.lang.NoSuchMethodException 没有这样一个构造器
        //(我们想看到的报错)Cannot reflectively create enum objects 不能通过反射破坏枚举
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

        System.out.println(instance1);
        System.out.println(instance2);
    }
}

枚举类型的最终反编译原码:

发现是有参构造!!

19.深入理解CAS

什么是CAS

package CAS;

import java.util.concurrent.atomic.AtomicInteger;

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

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

        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger);
    }
}

Unsafe类

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第14张图片

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第15张图片

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

缺点︰

1、循环会耗时

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

3、ABA问题

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

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第16张图片

package CAS;

import java.util.concurrent.atomic.AtomicInteger;

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

        // 对于我们平时写的sql 乐观锁!
        
        // 期望 、更新
        //public final boolean compareAndSet(int expectedValue, int newValue) {
        // 如果我期望的值达到了 就更新 否则 就不更新
        // CAS 是CPU的并发原语!
        //=================捣乱的线程=================
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger);

        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger);
        //=================期望的线程=================
        System.out.println(atomicInteger.compareAndSet(2020, 1024));
        System.out.println(atomicInteger);
    }
}

20.原子引用

解决ABA 问题

带版本号的原子操作!

cas只是比较和转换 ,乐观锁是cas+版本号控制实现的

注意:

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第17张图片

package CAS;

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

public class AtomicReferenceTest {
    // 什么是CAS : compareAndSet: 比较并交换!
    public static void main(String[] args) {

        //int Integer 如果泛型是一个包装类,注意对象的引用问题
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);

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

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

            System.out.println("A : " + atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("A2==>>" + atomicStampedReference.getStamp());

            System.out.println("A : " + atomicStampedReference.compareAndSet(2, 1,
                    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 (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("B : " + atomicStampedReference.compareAndSet(1, 10,
                    stamp, stamp + 1));

            System.out.println("B2==>>" + atomicStampedReference.getStamp());

        }, "B").start();

    }
}

21.各种锁的解释

1、公平锁、非公平锁

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

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

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

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

2、可重入锁

可重入锁:递归锁

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第18张图片

Synchronized

package lock;

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

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

    static class Phone {
        public synchronized void sms() {
            System.out.println(Thread.currentThread().getName() + "===>发信息");
            call(); // 这里也有锁
        }

        public synchronized void call() {
            System.out.println(Thread.currentThread().getName() + "===>打电话");
        }
    }
}

Lock

package lock;

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

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

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

    static class Phone {

        Lock lock = new ReentrantLock();

        public void sms() {
            lock.lock(); // 细节问题 拿到了两把锁
            // lock() 锁必须配对
            try {
                System.out.println(Thread.currentThread().getName() + "===>发信息");
                call(); // 这里也有锁
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void call() {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "===>打电话");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

3、自旋锁

spinlock

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第19张图片

自己实现一个自旋锁

package lock;

import java.util.concurrent.atomic.AtomicReference;

public class mySpinlock {
    //int 0
    //Thread null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    //加锁
    //第一个线程走到lock的时候能够继续执行下去 但此时不uncok,第二个线程一直会走while那里自旋
    //直到第一个线程释放锁,第二个线程才会获得锁,最后释放
    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();
        System.out.println(thread.getName() + "===> myUnlock");
        atomicReference.compareAndSet(thread, null);
    }

}

测试自己的自旋锁

package lock;

import java.util.concurrent.TimeUnit;

public class mySpinlockTest {
    public static void main(String[] args) throws InterruptedException {

        // 底层使用自旋锁
        SpinlockTest lock = new SpinlockTest();


        new Thread(() -> {
            lock.myLock();;

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        }, "T1").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            lock.myLock();

            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnLock();
            }
        }, "T2").start();


        lock.myLock();
        lock.myUnLock();
    }
}

4、死锁

死锁是什么?

Java面试 - 多线程进阶 JUC并发编程(狂神说JUC课堂笔记)(学完吊打面试官)_第20张图片

package lock;

import java.util.concurrent.TimeUnit;

public class Main {

    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";

        new Thread(new MyThread(lockA, lockB), "T1").start();
        new Thread(new MyThread(lockB, lockA), "T2").start();
    }


    static class MyThread implements Runnable {

        private final String lockA;
        private final 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() + "lock:" + lockA + "=>get" + lockB);

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

                synchronized (lockB) {
//                    System.out.println(Thread.currentThread().getName() + "lock:" + lockB + "=>get" + lockA);
                }

            }
        }
    }
}

怎么排除死锁?

解决问题

1、使用jps -l 定位进程号
2、使用jstack进程号查询

你可能感兴趣的:(java面试,java,面试)