JUC学习-B站KUANG

线程和进程

如果不能用一句话说出来的技术,不扎实!

线程是操作系统能够进行运算调度的最小单位被包含在进程之中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程是一段程序的执行过程,系统进行资源分配和调度的基本单位

wait和sleep的区别

  1. 来自不同的类

    wait -> Object

    sleep->Thread

  2. 关于锁的释放

    wait会释放锁,sleep不会

  3. 使用的范围不同

    wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用

    sleep在任何地方

  4. 是否需要捕获异常

    sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常(中断异常)

Lock锁(重点)

传统synchronized

Lock

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

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

synchronized和Lock区别

  1. synchronized是内置的JAVA关键字,Lock是一个java类
  2. synchronized无法获取锁的状态,Lock可以判断是否获取到了锁
  3. synchronized会自动释放锁,lock必须手动释放锁!如果不,产生死锁
  4. synchronized线程1{获得锁,阻塞}、线程2(等待,傻傻的等);Lock不一定等待下去
  5. synchronized 可重入锁,不可以中断,非公平;Lock可重入锁,可以判断锁,公平自己设置
  6. synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码

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

生产者和消费者问题

面试:单例模式,排序算法,生产者和消费者,死锁

Synchronized版

 /* A 执行完 执行B  执行C  执行A ...*/
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}
//等待、业务、通知
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.notify();
    }
    //-1
    public synchronized void decrement() throws InterruptedException {
        if (number == 0){
            //等待
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        //通知
        this.notify(); 
    }
}

如果存在多个线程,会造成虚假唤醒!

将if改为while

JUC版

package pc;

/**
 * @author Pillar
 * @version 1.0
 * @date 2022/6/3 18:37
 */

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

/**
* @Description: 线程之间的通信问题:生产者和消费者问题
 * 线程交替执行 A B 操作同一个变量 num = 0
 * A num + 1
 * B num - 1
*/
public class b {
    public static void main(String[] args) {
        Data1 data = new Data1();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                data.increment();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                data.increment();
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

//等待、业务、通知
class Data1 {
    private int number = 0;

    private Lock lock = new ReentrantLock();

    private 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() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0){
                //等待
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            //通知
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }


    }
}

condition 精准的通知和唤醒线程

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

class Data3 {
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1;
    public void printA(){
        lock.lock();
        try {
            //业务:判断、执行、通知
            while (num!=1){
                //等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"-A");
            num = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            //业务:判断、执行、通知
            while (num!=2){
                //等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"-BB");
            num = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            //业务:判断、执行、通知
            while (num!=3){
                //等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"-CCC");
            num = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

8锁现象

Test1

package com.pillar.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author Pillar
 * @version 1.0
 * @date 2022/6/6 16:38
 * 8锁问题 :关于锁的8个问题
 * 1. 标准情况下,两个线程 打印顺序 首先是打电话还是发短信 ?  1 发短信 2 打电话
 * 2. sendSms延时4s,两个线程 打印顺序 首先是打电话还是发短信 ?  1 发短信 2 打电话
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();

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

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

        new Thread(()->{
            phone.call();
        },"B").start();
    }
}
class Phone {
    // synchronized 锁的对象是方法的调用者!
    // 在main中用的是同一个phone对象,所以两个方法用的是同一个锁,谁先拿到谁执行
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

Test2

package com.pillar.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author Pillar
 * @version 1.0
 * @date 2022/6/6 16:38
 * 8锁问题 :关于锁的8个问题
 * 1. 标准情况下,两个线程 打印顺序 首先是打电话还是发短信 ?  1 发短信 2 打电话
 * 2. sendSms延时4s,两个线程 打印顺序 首先是打电话还是发短信 ?  1 发短信 2 打电话
 * 3. 增加了一个普通方法后,先执行发短信还是hello? 普通方法
 * 4. 两个对象,两个同步方法,首先是打电话还是发短信 ? 打电话
 */
public class Test2 {
    public static void main(String[] args) {
        //两个对象,两个调用者,两把锁
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();

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

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

        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
class Phone2 {
    // synchronized 锁的对象是方法的调用者!
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

    public synchronized void call(){
        System.out.println("打电话");
    }
    //这里没有锁!不是同步方法,不受锁的影响
    public void hello(){
        System.out.println("hello");
    }
}

Test3

package com.pillar.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author Pillar
 * @version 1.0
 * @date 2022/6/6 17:12
 * 5. 增加两个静态同步方法,只有一个对象, 首先发短信还是打电话? 发短信
 * 6. 增加两个静态同步方法,有两个对象, 首先发短信还是打电话? 发短信
 */
public class Test3 {
    public static void main(String[] args) {
        //两个对象,两个调用者,两把锁
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();

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

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

        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
//Phone3 只有唯一的一个class对象
class Phone3 {
    // synchronized 锁的对象是方法的调用者!
    //static是静态方法
    //类一加载就有了, 锁的是class,全局唯一
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

}

Test4

package com.pillar.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author Pillar
 * @version 1.0
 * @date 2022/6/6 18:30
 */
public class Test4 {
    public static void main(String[] args) {
    //两个对象,两个调用者,两把锁
    Phone4 phone1 = new Phone4();
    Phone4 phone2 = new Phone4();

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

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

    new Thread(()->{
        phone2.call();
    },"B").start();
}
}
//Phone3 只有唯一的一个class对象
class Phone4 {
    //静态的同步方法 ,锁的是Class类模板
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

}

小结

  1. synchronized 锁的对象是方法的调用者!也就是new出来的对象

    同一个对象中,带有synchronized 的方法,谁先拿到谁先执行(同步)

  2. static synchronized 锁的是class,类本身,全局唯一

    只要是这个类的对象,调用static synchronized的方法,谁先拿到谁先执行(同步)

集合类不安全

List不安全

public class ListTest {
    public static void main(String[] args) {
        // 并发下ArrayList不安全
        /**
        * 解决方案:
         * 1. List<String> list = new Vector<>();
         * 2. List<String> list = Collections.synchronizedList(new ArrayList<>());
         * 3. List<String> list = new CopyOnWriteArrayList<>();
        */
        //CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略;https://blog.csdn.net/u014203449/article/details/83867921
        //解决了并发编程中,多线程写入时覆盖问题。
        // add加锁了,复制一个容器,在这个复制的容器中添加元素,添加完之后,再将引用指向这个新容器。
        //get没加锁,不需要加锁,因为当前容器不会添加任何元素
        // 读写分离
        // Vector 、synchronizedList 、CopyOnWriteArrayList三者区别 :https://blog.csdn.net/dimu9293/article/details/107729756
        /*vector	        所有操作都要加锁,性能不佳	即使在高并发环境下都能保证数据的一致性
        synchronizedList	同上	                    同上
        CopyOnWriteArrayList写操作会有一定的延迟         只有写加锁,读不加锁*/
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

    }
}

Vector 、synchronizedList 、CopyOnWriteArrayList三者区别

  1. 与vector以及Collections.synchronizedList()实现原理上的区别

1、vector是通过在每个方法加synchronized实现,包括最简单的size()方法

    public synchronized int size() {
        return elementCount;
    }
        public synchronized boolean isEmpty() {
        return elementCount == 0;
    }
123456

2、synchronizedList是通过在每个方法内部加 synchronized实现,包括最简单的size()方法

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }
123456789101112131415

3、CopyOnWriteArraylist是通过复制一份副本,然后通过修改副本之后再赋值回去,lock是new()出来的,用于在set,add等写操作的时候进行锁操作

final transient Object lock = new Object();

public E set(int index, E element) {
        synchronized (lock) {      
            Object[] es = getArray();
            E oldValue = elementAt(es, index);

            if (oldValue != element) {
                es = es.clone();
                es[index] = element;
                setArray(es);
            }
            return oldValue;
        }
    }


//获取迭代器的时候是复制了一个新的数组
public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
}
123456789101112131415161718192021

2. 三者各有什么区别

废话不多说,直接上表

类型 缺点 优点
vector 所有操作都要加锁,性能不佳 即使在高并发环境下都能保证数据的一致性
synchronizedList 同上 同上
CopyOnWriteArrayList 写操作会有一定的延迟 只有写加锁,读不加锁

解析

​ 1、由于vector与synchronizedList都是通过加synchronized实现,两者性能上差距并不大。但是由于每次调用方法都要加锁,导致在大数据量的情况下性能不理想。

​ 2、CopyOnWriteArrayList 由于写锁读不锁使得性能更好,但是由于写操作是在副本上进行的,在数据量大的情况下数组的复制比较耗时,所以写完后不能立马体现出来,有一定的延迟。

3. CopyOnWriteArrayList 读写不一致的场景示例

1.循环中删除元素不报错

例子
List<String> list = new CopyOnWriteArrayList();    //CopyOnWriteArrayList换成ArrayList则直接报错
        list.add("A1");
        list.add("A2");
        list.add("A3");
        for (String str : list) {
            if ("A1".equals(str)) {
                list.remove(str);
            }
        }
System.out.println(list);

//运行结果        
[A2, A3]
1234567891011121314

2.迭代过程中往list添加或减少元素不会立马体现

由于迭代是复制了一份新的数组,然后对新数组进行迭代。所以修改数组并不会立马反馈到迭代器中。

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();
list.add("A1");
list.add("A2");
list.add("A3");

Iterator<String> it = list.iterator();
list.add("A4");
while (it.hasNext()){
    System.out.println(it.next());
}

//运行结果
A1
A2
A3
123456789101112131415

3.不支持remove操作

为了保证数据不错乱,CopyOnWriteArrayList不支持在迭代器中进行remove操作。

在CopyOnWriteArrayList中

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}
123

在 COWIterator 中

public void remove() {
    throw new UnsupportedOperationException();
}
123

测试

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList();
list.add("A1");
list.add("A2");
list.add("A3");

Iterator<String> it = list.iterator();
list.add("A4");
while (it.hasNext()){
    it.remove();
    System.out.println(it.next());
}

结果:
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.base/java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1120)
	at STU_CopyOnWriteArraylist.read(STU_CopyOnWriteArraylist.java:20)
	at STU_CopyOnWriteArraylist.main(STU_CopyOnWriteArraylist.java:7)

set不安全

/**
* java.util.ConcurrentModificationException并发修改异常
 * 解决方案:
 * 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
 * 2. Set<String> set = Collections.synchronizedSet(new HashSet<>());
*/
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 < 10; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

hashSet?

public HashSet() {
    map = new HashMap<>();
}
//add 本质就是 map 的 key是无法重复的!
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
// PRESENT为不变的值
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

Map不安全

Callable(简单)

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不用,call()

测试

public class CallTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //提问
        // 线程怎样使用callable? 利用FutureTask作为适配类,来接受实现了callable的线程对象,然后再new Thread(futureTask).start就能启动了
        //为什么用FutureTask? 因为线程类的参数中可以接受Runnable接口的实现类对象,参数中不能接受Callable接口的实现类对象,
        //                    但是FutureTask,实现了RunnableFuture接口,并且实现了RunnableFuture接口继承了Runnable接口。这样线程就能实现callable接口了。
        // 功能:成功后,可以访问其结果

        //new Thread(new Runnable()).start();
        //new Thread(new FutureTask<V>()).start();
        //new Thread(new FutureTask<V>(Callable)).start();
//        new Thread().start(); //怎么启动callable
        MyThread myThread = new MyThread();
        //适配类
        FutureTask futureTask = new FutureTask(myThread);
        new Thread(futureTask,"A").start();
        //获取callAble.返回结果
        //get()
        //等待计算完成,然后检索其结果。
        // 方法可能会产生阻塞!把它放到最后或者使用异步通信来处理!
        new Thread(futureTask,"B").start();
       /* 这个并不是缓存,是由于JVM第二次再调用FutrueTask对象所持有的线程
        此时FutrueTask的state此时已非NEW状态(各个状态,这边不做详细解释)
        则此时会直接结束对应线程,就会导致任务也不执行
        只是在第一次调用时返回结果保存了,可能这就是老师所说的缓存*/
        String o = (String) futureTask.get();
        System.out.println(o);
    }
}
class MyThread implements Callable<String> {

    @Override
    public String call() {
        System.out.println("call方法");
        return "123";
    }
}

细节:

1. 
//get()
        //等待计算完成,然后检索其结果。
        // 方法可能会产生阻塞!把它放到最后或者使用异步通信来处理!

2. new Thread(futureTask,"A").start();
   new Thread(futureTask,"B").start();
   只打印一遍结果
/* 这个并不是缓存,是由于JVM第二次再调用FutrueTask对象所持有的线程
        此时FutrueTask的state此时已非NEW状态(各个状态,这边不做详细解释)
        则此时会直接结束对应线程,就会导致任务也不执行
        只是在第一次调用时返回结果保存了,可能这就是老师所说的缓存*/

常用的辅助类

CountDownLatch

//计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //总数是6 , 必须要执行任务的时候,再使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; 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(),假设计数器变为0await()就会被唤醒,继续执行!

CyclicBarrier

https://blog.csdn.net/Shockang/article/details/118559080

CyclicBarrier 是另外一种多线程并发控制工具。

和 CountDownLatch 非常类似,它也可以实现线程间的计数等待,但它的功能比 CountDownLatch 更加复杂且强大。

CyclicBarrier 可以理解为循环栅栏。

public class CyclicBarrierDemo2 {
    public static class Soldier implements Runnable {
        private String soldier;
        private final CyclicBarrier cyclic;

        Soldier(CyclicBarrier cyclic, String soldierName) {
            this.cyclic = cyclic;
            this.soldier = soldierName;
        }

        public void run() {
            try {
                //等待所有士兵到齐
                cyclic.await();
                doWork();
                //等待所有士兵完成工作
                cyclic.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }

        void doWork() {
            try {
                Thread.sleep(Math.abs(new Random().nextInt() % 10000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(soldier + ":任务完成");
        }
    }

    public static class BarrierRun implements Runnable {
        boolean flag;
        int N;

        public BarrierRun(boolean flag, int N) {
            this.flag = flag;
            this.N = N;
        }

        public void run() {
            if (flag) {
                System.out.println("司令:[士兵" + N + "个,任务完成!]");
            } else {
                System.out.println("司令:[士兵" + N + "个,集合完毕!]");
                flag = true;
            }
        }
    }

    public static void main(String args[]) throws InterruptedException {
        final int N = 10;
        Thread[] allSoldier = new Thread[N];
        boolean flag = false;
        CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(flag, N));
        //设置屏障点,主要是为了执行这个方法
        System.out.println("集合队伍!");
        for (int i = 0; i < N; ++i) {
            System.out.println("士兵 " + i + " 报道!");
            allSoldier[i] = new Thread(new Soldier(cyclic, "士兵 " + i));
            allSoldier[i].start();
        }
    }
}

Semaphore

public class SemaphoreDemo {
    public static void main(String[] args) {
        //线程数量,停车位,限流
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                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 释放
                }
            }).start();
        }
    }
}

原理:

semaphore.acquire();获得,如果已经满了,等待其他线程被释放
semaphore.release();释放,唤醒等待的线程

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

读写锁ReadWriteLock

/**
 * @author Pillar
 * @version 1.0
 * @date 2022/6/9 10:40
 * 独占锁(写锁)一次只能被一个线程占有
 * 共享锁(读锁)多个线程同时占有
 * ReadWriteLock
 * 读-读 可共存
 * 读-写 不可
 * 写-写 不可
 */
public class ReadWriteLockDemo {
    public static void main(String[] args) {
//        MyCache myCache = new MyCache();
        MyCacheLock myCache = new MyCacheLock();
        //写入操作
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(String.valueOf(temp),temp);
            },String.valueOf(i)).start();
        }

        //读取操作
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(String.valueOf(temp));
            },String.valueOf(i)).start();
        }

    }
}
/*
* 自定义缓存
 */
class MyCache {
    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()+"写入OK");
    }
    //取、读
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"开始读"+key);

        System.out.println(Thread.currentThread().getName()+"读取OK");
    }
}
class MyCacheLock {
    private volatile Map<String,Object> map = new HashMap<>();
    //读写锁:更加细粒度的控制
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    //存、写入的时候,只希望同时只有一个线程写
    public void put(String key,Object value){
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key, value);
            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);
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

读写锁和锁Lock的最大区别就是读写锁读写是互斥,而Lock读写是非互斥的。读的时候没加读写锁(写的时候就开始读了),可能会出现脏读。

阻塞队列

什么情况下会使用 阻塞队列?

多线程并发处理,线程池!

使用

增、删

四组API

  1. 抛出异常
  2. 不会抛出异常
  3. 阻塞等待
  4. 超时等待
public class Test {
    public static void main(String[] args) throws InterruptedException {
        test4();
    }
    /*
    * 抛出异常
     */
    public static void test1(){
        //队列大小
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("aa"));
        System.out.println(blockingQueue.add("aaa"));

//      java.lang.IllegalStateException: Queue full
//        System.out.println(blockingQueue.add("a"));
        System.out.println("====================");
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
//        java.util.NoSuchElementException
//        System.out.println(blockingQueue.remove());
    }
    /*
    * 有返回值, 没有异常
     */
    public static void test2(){
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("s"));
        System.out.println(blockingQueue.offer("ss"));
        System.out.println(blockingQueue.offer("sss"));
        //false
        //System.out.println(blockingQueue.offer("sss"));

        System.out.println(blockingQueue.peek());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        //null
        //System.out.println(blockingQueue.poll());
    }
    /*
    * 等待,阻塞(一直)
     */
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        //一直阻塞
        blockingQueue.put("a");
        blockingQueue.put("aa");
        blockingQueue.put("aaa");
        //队列没有位置,一直阻塞
        //blockingQueue.put("a");
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        //一直阻塞
        //System.out.println(blockingQueue.take());
    }

    /*
     * 等待,阻塞(等待超时)
     */
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("aa"));
        System.out.println(blockingQueue.offer("aaa"));
        //blockingQueue.offer("aaaa", 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同步队列

没有容量,进去一个元素,必须等待取出来,才能再向里面添加元素

线程池(重点)

三大方法、7大参数、4种拒绝策略

好处:

  1. 降低系统资源的消耗
  2. 提高响应的速度
  3. 方便管理

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

线程池:三大方法

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

        int max = 0;
        try {
            for (int i = 0; i < 100; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"  ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }

    }
}

7大参数

源码分析

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,//21亿
                                  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;
}

银行办理业务

1号2号窗口开(core),3、4、5号窗口休息。

来两个人正常办理业务,多的人来候客区(blockingQueue)等待。

如果候客区也满了,根据多的人数来开启3、4、5号窗口(max)。

如果候客区满,窗口满,再来的人有四种拒绝策略(rejectedHandle)。

手动创建一个线程池

public class Demo {
    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定线程池的大小
//        ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强、遇弱则弱
        //自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                1,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
//                new ThreadPoolExecutor.AbortPolicy());
//                new ThreadPoolExecutor.CallerRunsPolicy());
//                new ThreadPoolExecutor.DiscardPolicy());
                new ThreadPoolExecutor.DiscardOldestPolicy());
        try {
            for (int i = 1; i <= 9; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"  ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }

    }
}

4种拒绝策略

new ThreadPoolExecutor.AbortPolicy()
new ThreadPoolExecutor.CallerRunsPolicy()
new ThreadPoolExecutor.DiscardPolicy()
new ThreadPoolExecutor.DiscardOldestPolicy()
  1. 它直接在execute方法的调用线程中运行被拒绝的任务,除非执行程序已经关闭,在这种情况下,任务被丢弃
  2. 它会抛出RejectedExecutionException
  3. 该处理程序将默默地丢弃被拒绝的任务
  4. 它丢弃最老的未处理请求,然后重试执行,除非执行程序关闭,在这种情况下,任务被丢弃

小结与拓展

最大的线程池大小

了解:CPU密集型IO密集型(调优)

  1. CPU密集型,几核就是几,可以保证CPU效率最高
  2. IO密集型, 判断程序中十分消耗IO的线程,大于这个,比如两倍
//获取CPU核数
System.out.println(Runtime.getRuntime().availableProcessors());

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

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

函数式接口,简化编程模型

函数式接口:只有一个方法的接口,比如

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

Function函数式接口

public class Demo01 {
    public static void main(String[] args) {
        /*Function function = new Function<String,String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };*/
//        Function function = (str)->{return str;};
        Function function = s->s;
        System.out.println(function.apply("123"));
    }
}

断定性接口:有一个参数,返回值是 布尔值

public class Demo02 {
    public static void main(String[] args) {
        //判断字符串是否为空
        /*Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };*/
        Predicate<String> predicate = str -> str.isEmpty();
        System.out.println(predicate.test("123"));
        System.out.println(predicate.test(""));
    }
}

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

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

供给型接口:没有输入,有返回值

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

Stream流式计算

什么是Stream流式计算

Stream(流)是一个来自数据源的元素队列(可以使集合或者数组等),并且支持聚合操作;对于集合数据的一些处理。

可以参考:https://blog.csdn.net/java_Trainees/article/details/123236971

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

/**
 * 题目要求:一分钟内完成此题,只能用一行代码实现!
 * 现在有5个用户!筛选:
 * 1、ID 必须是偶数
 * 2、年龄必须大于23岁
 * 3、用户名转为大写字母
 * 4、用户名字母倒着排序
 * 5、只输出一个用户!
 **/
/*
二.常用方法
        1.filter(Predicate<? super T> predicate) 返回由与此给定谓词匹配的此流的元素组成的流。
        2.count() 返回此流中的元素数。
        3.forEach(Consumer<? super T> action) 对此流的每个元素执行操作。
        4.sorted(Comparator<? super T> comparator) 返回由该流的元素组成的流,根据提供的 Comparator进行排序。
        5.map(Function<? super T,? extends R> mapper)返回由给定函数应用于此流的元素的结果组成的流。
 */
public class Test {
    public static void main(String[] args) {
        User user1 = new User(1,"张三",21);
        User user2 = new User(2,"李四",25);
        User user3 = new User(3,"王五",22);
        User user4 = new User(4,"赵六",23);
        User user5 = new User(5,"孙七",24);
        User user6 = new User(6,"王五",26);
        User user7 = new User(7,"赵六",27);
        User user8 = new User(8,"孙七",28);
        List<User> users = Arrays.asList(user1, user2, user3, user4, user5,user6,user7,user8);
        users.stream()
                .filter(u->u.getId()%2==0)//1、ID 必须是偶数
                .filter(u->u.getAge()>23)//2、年龄必须大于23岁
                .map(u->u.getName().toUpperCase())//3、用户名转为大写字母
                .sorted((u1,u2)->u2.compareTo(u1))//用户名字母倒着排序
                .limit(1)//只输出一个用户!
                .forEach(System.out::println);
    }
}

ForkJoin

什么是ForkJoin

Fork/Join框架是Java 7提供的一个用于并行执行任务的框架, 核心思想就是把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果 - <分而治之> (在大数据量环境下)

特点:工作窃取

其他线程完成的快,不闲这,帮未完成的线程的任务

/ * 如何使用ForkJoin
 * 1. 通过ForkJoinPool
 * 2. 计算任务 forkjoinPool.execute(ForkJoinTask task)
 * 3. 计算类要继承 ForkJoinTask
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    //临界值
    private Long temp = 10000L;

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

    /**
     * 计算方法
     * @return
     */
    @Override
    protected Long compute() {
        if((end-start)<temp){
            Long sum=0L;
            for (Long i = start; i < end; i++) {
                sum+=i;
            }
            return sum;
        }else { // forkjoin 递归
            long middle = (start + end) / 2; // 中间值
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork(); // 拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            task2.fork(); // 拆分任务,把任务压入线程队列
            return task1.join() + task2.join();
        }
    }
}

测试

/**
 * 同一个任务 别人比你高几十倍
 */
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();//5247
//        test2();//4210
//        test3();//151
    }
    //普通程序员
    public static void test1(){
        Long sum=0L;
        long start=System.currentTimeMillis();
        for (Long i = 1L; i <= 10_0000_0000; i++) {
            sum+=i;
        }
        long end=System.currentTimeMillis();
        System.out.println("sum="+sum+"时间:"+(end-start));
    }
    //会使用forkjoin
    public static void test2() throws ExecutionException, InterruptedException {
        long start=System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务
        Long sum = submit.get();
        long end=System.currentTimeMillis();
        System.out.println("sum="+sum+"时间:"+(end-start));
    }
    //会使用stream流
    public static void test3(){
        long start=System.currentTimeMillis();
        Long sum= LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end=System.currentTimeMillis();
        System.out.println("sum="+sum+"时间:"+(end-start));
    }
}

异步回调

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

JMM

请你谈谈对Volatile的理解

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

  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排

什么是JMM

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

关于JMM的一些同步的约定

  1. 线程解锁前,必须把共享变量立刻刷回主存
  2. 线程加锁前,必须读取主存中的最新值到工作内存中!
  3. 加锁和解锁是同一把锁

Volatile

  1. 保证可见性
public class JMMDemo {
    //不加 volatile 程序就会死循环!
    //加 volatile 可以保证可见性//这里的原理是MESI和嗅探机制
    private volatile static int num = 0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{//线程1 对主内存的变化不知道
            while (num==0){

            }
        }).start();
        TimeUnit.SECONDS.sleep(1);

        num = 1;
        System.out.println(num);
    }
}
  1. 不保证原子性

原子性:不可分割

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

//不保证原子性
public class VDemo02 {
    private volatile static int num = 0;

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

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println(num);
    }
}

如果不加Lock和Synchronized怎么样保证原子性

  1. 指令重排

什么是指令重排?

计算机并不是按照用户写的去执行。

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

彻底玩单例模式(必须会)

深入理解CAS

什么是CAS

大厂必须要深入研究底层!有所突破!

posted @   NeverLateThanBetter  阅读(34)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示