狂神说juc笔记

1.入门

JUC就是java.util.concurrent包及其子包

java默认有两个线程,一个是main线程,一个是GC线程

对于java而言有三中开启线程的方式:Thread,Runnable,Callable,但是实际上并不是java开启的线程,调用的是底层的C++开启线程

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

并行:多个线程同时进行,只有多核cpu才能并行

java获取CPU核心数量

//获取CPU的核心数量
Runtime.getRuntime().availableProcessors();

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

线程的状态:

  • NEW 新生
  • RUNNABLE 运行
  • BLOCKED 阻塞
  • WATING 等待
  • TIMED_WATING 超时等待
  • TERMINATED 终止

wait和sleep的区别

来自不同类,wait来自Object,sleep来自Thread

企业中休眠使用的是TimeUnit类在java.util.concurrent包下

wait会释放锁,sleep方法不会释放锁

wait只能在同步代码块中使用,sleep可以在任何地方使用

wait不需要捕获异常,sleep必须要捕获异常

2.Lock锁

2.1.synchronized

卖票的案例

public class SaleTicket1 {
    public static void main(String[] args) {
        //多线程卖票
        Ticket ticket = new Ticket();

        new Thread(() -> {
            for(int i=0;i<60;++i){
                ticket.sale();
            }
        },"A").start();
        new Thread(() -> {
            for(int i=0;i<60;++i){
                ticket.sale();
            }
        },"B").start();
        new Thread(() -> {
            for(int i=0;i<60;++i){
                ticket.sale();
            }
        },"C").start();
    }
}

//资源类
class Ticket{
    //票的数量
    private Integer number = 50;

    //卖票
    public synchronized void sale(){
        if(number > 0){
            System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票"+",剩余"+number+"张票");
        }
    }
}

2.2.Lock接口

在java.util.concurrent包下,有三个实现类

  • ReentrantLock 可重用锁(常用)
    • 公平锁 需要排队
    • 非公平锁 可以插队(默认)
  • ReentrantReadWriteLock.ReadLock 读锁
  • ReentrantReadWriteLock.WriteLock 写锁

使用lock的步骤

  1. 在使用前加锁
  2. 要用try…catch语句
  3. 在finally中释放锁

仍然是卖票案例

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

public class SaleTicket2 {
    public static void main(String[] args) {
        //多线程卖票
        Ticket ticket = new Ticket();

        new Thread(() -> {
            for(int i=0;i<60;++i){
                ticket.sale();
            }
        },"A").start();
        new Thread(() -> {
            for(int i=0;i<60;++i){
                ticket.sale();
            }
        },"B").start();
        new Thread(() -> {
            for(int i=0;i<60;++i){
                ticket.sale();
            }
        },"C").start();
    }
}

//资源类
class Ticket2{
    //票的数量
    private Integer number = 50;

    Lock lock = new ReentrantLock();

    //卖票
    public void sale(){
        lock.lock();//加锁
        try {
            if(number > 0){
                System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票"+",剩余"+number+"张票");
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }
    }
}

lock和synchronized的区别

  • synchronized是内置java关键字 Lock是一个java类
  • synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
  • synchronized会自动释放锁,Lock需要自己手动释放锁,如果不释放就是死锁
  • synchronized加锁之后,如果得到锁的对象产生了阻塞就会一直等下去,但是Lock就不一定会一直等待,lock会有一个方法就是lock.tryLock();会尝试获取锁
  • synchronized是可重用锁,非公平的,不可以被中断的,Lock更自由可重用,可以判断锁,可以自己设置公平与非公平,设置方法就是在构造方法中传入boolean值,true为公平
  • synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码

2.3.生产者消费者问题

传统版使用synchronized

/*
* 线程之间的通信问题:生产者消费者问题
* 线程交替执行 A B线程操作同一个变量 num = 0
* A num+1
* B num-1
* */
public class A {
    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();
    }
}

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);
        //通知其他线程
        this.notifyAll();
    }

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

问题:如果有更多的线程,还是会出现问题,会出现虚假唤醒,因为只判断了一次number的值,我们应该使用while防止问题的发生

/*
* 线程之间的通信问题:生产者消费者问题
* 线程交替执行 A B线程操作同一个变量 num = 0
* A num+1
* B num-1
* */
public class A {
    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();

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

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

class Data{//数字资源类
    private int number = 0;

    //+1操作
    public synchronized void increment() throws InterruptedException {
        while(number != 0) {
            //等待
            this.wait();
        }
        number ++;
        System.out.println(Thread.currentThread().getName() + "->" + number);
        //通知其他线程
        this.notifyAll();
    }

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

使用lock实现

在Lock中,等待和唤醒使用的都是同包下另一个接口condition中的方法awaitsingalAll

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

public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        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();

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

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

class Data2{//数字资源类
    private int number = 0;

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

    //+1操作
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while(number != 0) {
                //等待
                condition.await();
            }
            number ++;
            System.out.println(Thread.currentThread().getName() + "->" + number);
            //通知其他线程
            condition.signalAll();
        }catch (Exception 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 (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

以上这种写法产生的效果和传统方式是一样的,所以这并不是真正使用Lock的原因,使用Condition我们可以实现精准唤醒,可以让线程按照A,B,C,D的顺序执行

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 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 int number = 1;//1执行A  2执行B  3执行C

    private Lock lock = new ReentrantLock();
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();

    public void printA(){
        lock.lock();
        try {
            //业务
            while(number != 1){
                //等待
                conditionA.await();
            }
            System.out.println(Thread.currentThread().getName()+"AAAA");
            number = 2;
            //唤醒B
            conditionB.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            //业务
            while(number != 2){
                //等待
                conditionB.await();
            }
            System.out.println(Thread.currentThread().getName()+"BBBB");
            number = 3;
            //唤醒C
            conditionC.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            //业务
            while(number != 3){
                //等待
                conditionC.await();
            }
            System.out.println(Thread.currentThread().getName()+"CCCC");
            number = 1;
            //唤醒A
            conditionA.signal();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

2.4.进阶问题

https://www.bilibili.com/video/BV1B7411L7tE?p=10&share_source=copy_web

synchronized锁普通方法锁的是方法调用者

synchronized锁静态方法锁的是class

3.集合类不安全

在多线程操作集合的时候会产生ConcurrentModificationException并发修改异常

3.1.List集合

解决方案:

  1. 使用线程安全的集合Vector
  2. List<String> list = Collections.synchronizedList(new ArrayList<>());使用Collections工具类
  3. 使用juc下的集合List<String> list = new CopyOnWriteArrayList<>(); CopyOnWrite写入时复制,COW,计算机程序设计领域的一种优化策略,多个线程调用的时候是该固定的,写入的时候复制一份,避免覆盖

CopyOnWrite比Vector优秀的地方:读的地方没有锁,只有写的时候有锁

3.2.Set集合

由于set集合没有可以替代的安全集合,所以我们只能使用后两种方式

CopyOnWriteArraySet

HashSet底层就是Hashmap

3.3.Map集合

Map集合也是不安全的

安全的集合是ConcurrentHashMap,还有Collections中的synchronizedMap

4.Callable

Callable类似于Runnable,他们都是为其实例可能由另一个线程执行的类设计的,然而,Runnable不返回结果,也不能够抛出被检查的异常

所以Callable的特点

  • 可以有返回值
  • 可以抛出异常
  • 方法不同,Callable中是call()方法

由于创建线程只能使用new Thread的方法,但是使用new Thread方法只能接收runnable接口的实现类,所以我们需要找打Callable和runnable的联系

runnable有一个实现类叫做Futuretask,这个类的构造方法既可以传入runnable还可以传入一个callable,所以我们需要通过传入futuretask来传入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 myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);
        new Thread(futureTask, "threadA").start();
        //获取返回值
        String res = (String) futureTask.get();
        System.out.println(res);
    }
}

class MyThread implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println("执行了call方法");
        return "hello";
    }
}

如果启动了两个线程使用的是同一个futuretask,那么只会执行一次,因为有缓存,提高了效率

get的结果可能需要等待,会产生阻塞

5.常用的辅助类

5.1.CountDownLatch

计数器,可以让让指定的一些线程任务在另一些任务的前面完成

案例:只有学生全部离开才能关门

package com.hty.add;

import java.util.concurrent.CountDownLatch;

//计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //倒计时 总数是6
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for(int i=1;i<=6;++i){//因为计数器设置的就是6 那么如果是5个学生的话就会导致线程阻塞,如果大于6的话就会导致一部分学生没有出教室就关门了
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " go out");
                countDownLatch.countDown(); // -1
            },String.valueOf(i)).start();
        }

        countDownLatch.await();//等待计数器归零然后再向下执行(等待前面的线程任务结束)

        System.out.println("关门");
    }
}

5.2.CyclicBarrier

加法计数器

package com.hty.add;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.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){
            final int temp = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集"+temp+"个龙珠");
                //+1
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

只有等待的线程数量达到7的时候才会执行给CyclicBarrier传入的线程

5.3.Semaphore

Semaphore:信号量

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

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();
                    System.out.println(Thread.currentThread().getName() + "抢到车位");
                    TimeUnit.SECONDS.sleep(2);//停车2s
                    System.out.println(Thread.currentThread().getName() + "离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //release()释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

semaphore.acquire();开始执行,如果信号量到达阈值已经满了就会等待,直到可以执行

semaphore.release();释放,将当前线程占用的信号量释放

6.读写锁

readwritelock接口,一个ReadWriteLock维护一对关联的locks,一个用于只读操作,一个用于写入,read lock可以由多个阅读器线程同时进行,write lock只能有一个线程去写

使用这个锁的原因是提高多线程的效率

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

public class readwritelockTest {
    public static void main(String[] args) {
        MyCache2 myCache = new MyCache2();
        for(int i=0;i<10;++i){
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }

        for(int i=0;i<10;++i){
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}

//自定义缓存 加锁
class MyCache2 {
    private volatile Map<String, Object> map = new HashMap<>();
    //读写锁
    private ReadWriteLock readWriteLock  = new ReentrantReadWriteLock();

    //存 写 写入的时候只希望同时只有一个线程去写
    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();//加写锁
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入成功");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }
    }

    //取 读
    public Object get(String key) {
        readWriteLock.readLock().lock();
        Object o = null;
        try {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
        return o;
    }
}

//自定义缓存 未加锁
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() + "写入成功");
    }

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

这个案例如果加锁就可以保证写入的时候按顺序写,读的时候任意顺序读

还有两个名词,独占锁(写锁),共享锁(读锁)

7.阻塞队列(blockingqueue)

写入的时候,如果队列满了就会阻塞,取得时候如果队列为空就会阻塞

在多线程并发处理和线程池中会使用到阻塞队列

7.1.阻塞队列的四组API

方式抛出异常不会抛出异常,有返回值阻塞等待超时等待
添加addofferputoffer的重载方法
移除removepolltakepoll的重载方法
查看队列首部elementpeek--
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) {
        Test.test2();
    }
    //抛出异常
    public static void test1(){
        //要传入队列容量
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(arrayBlockingQueue.add("a"));
        System.out.println(arrayBlockingQueue.add("b"));
        System.out.println(arrayBlockingQueue.add("c"));
//        System.out.println(arrayBlockingQueue.add("c")); //再次添加就会报错
        System.out.println("===========");
        //出队
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());


    }

    //不抛出异常
    public static void test2(){
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        System.out.println(arrayBlockingQueue.offer("a"));
        System.out.println(arrayBlockingQueue.offer("b"));
        System.out.println(arrayBlockingQueue.offer("c"));
//        System.out.println(arrayBlockingQueue.offer("a"));
        System.out.println("====");
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
    }

    //一直阻塞
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        arrayBlockingQueue.put("a");
        arrayBlockingQueue.put("b");
        arrayBlockingQueue.put("c");

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

        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
    }

    //等待超时
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        arrayBlockingQueue.offer("a");
        arrayBlockingQueue.offer("b");
        arrayBlockingQueue.offer("c");
        arrayBlockingQueue.offer("d",2,TimeUnit.SECONDS);//设置超时时间两秒

        System.out.println("===");
        arrayBlockingQueue.poll();
        arrayBlockingQueue.poll();
        arrayBlockingQueue.poll();
        arrayBlockingQueue.poll(2,TimeUnit.SECONDS);
    }
}

8.synchronousQueue(同步队列)

没有容量,必须进去一个元素,然后等待被取出之后才能放入元素,相当于只能存储一个元素,操作的方法put,take

package com.hty.bq;

import java.util.concurrent.SynchronousQueue;

//同步队列
public class SynchronousQueueTest {
    public static void main(String[] args) throws InterruptedException {
        SynchronousQueue<String> queue = new SynchronousQueue<>();
        new Thread(() -> {
            for(int i=0;i<3;++i){
                final int temp = i;
                try {
                    queue.put(temp+"");
                    System.out.println(Thread.currentThread().getName()+"线程放入"+i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();
        new Thread(() -> {
            for(int i=0;i<3;++i){
                final int temp = i;
                try {
                    String take = queue.take();
                    System.out.println(Thread.currentThread().getName()+"线程取出"+take);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t2").start();
    }
}

9.线程池

线程池的优点:

  • 降低资源的消耗
  • 提高响应的速度
  • 方便管理

9.1.线程池三大方法

import java.util.concurrent.Executors;
//Executors工具类,三大方法
public class Demo1 {
    public static void main(String[] args) {
        Executors.newSingleThreadExecutor();//单个线程
        Executors.newFixedThreadPool(5);//固定线程池的大小
        Executors.newCachedThreadPool();//可变大小线程池
    }
}

案例

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Executors工具类,三大方法
public class Demo1 {
    public static void main(String[] args) {
        ExecutorService threadPool = null;//单个线程
        try {
            threadPool = Executors.newSingleThreadExecutor();
            for(int i=0;i<10;++i){
                //通过线程池创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭线程池
            threadPool.shutdown();
        }

    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Executors工具类,三大方法
public class Demo1 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(5);//固定线程池的大小
        try {

            for(int i=0;i<10;++i){
                //通过线程池创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭线程池
            threadPool.shutdown();
        }

    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Executors工具类,三大方法
public class Demo1 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newCachedThreadPool();//可变大小线程池
        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();
        }

    }
}

9.2.七大参数

观察源码我们可以发现,开辟线程池的本质就是调用了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.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;
}

在真实的业务中一般都是用的是ThreadPoolExecutor类来自定义线程池,因为juc提供的方法容易造成OOM

案例:手动创建线程池

package com.hty.pool;

import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Demo2 {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                3L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        try {
            //最大承载8
            for(int i=0;i<9;++i){
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

问题:最大线程数量如何定义?

  1. CPU密集型 几核CPU就可以设置为几

    //使用代码获取CPU核心数量
    Runtime.getRuntime().availableProcessors();
    
  2. IO密集型 判断程序中十分耗IO的线程,那么就设置为IO线程数量的两倍

9.3.四种拒绝策略

当线程池满了,阻塞队列也满了的时候,就会触发四种拒绝策略的一种

  • new ThreadPoolExecutor.AbortPolicy() 抛出异常
  • new ThreadPoolExecutor.CallerRunsPolicy() 哪里来的去哪里,上面的例子就是抛给main线程执行
  • new ThreadPoolExecutor.DiscardPolicy() 不会抛出异常,会丢失任务
  • new ThreadPoolExecutor.DiscardPolicy() 队列满了,就会和最先执行的线程去竞争,也不会抛出异常

10.四大函数式接口

函数式接口就是只有一个方法的接口,例如Runnable接口

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

简化编程模型,在新版本框架底层大量应用

10.1.function接口

函数型接口

import java.util.function.Function;
/*
Function函数型接口,有一个输入参数,有一个输出参数
只要是函数型接口,就可以使用lambda表达式简化
*/
public class Demo1 {
    public static void main(String[] args) {
        //工具类 输出输入的值
        Function<String, String> function = new Function<String, String>() {
            @Override
            public String apply(String str) {
                return str;
            }
        };
        //lambda表达式写法
        Function<String,String> function = (str) -> {return str;};
        //简化lambda表达式
        Function<String,String> function = str -> {return str;};
        System.out.println(function.apply("123"));
    }
}

10.2.Predicate接口

这个接口返回的是一个布尔值

import java.util.function.Predicate;
//断定型接口 有一个输入参数 返回值只能是布尔值
public class Demo2 {
    public static void main(String[] args) {
        //判断字符串是否为空
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s == null || s.equals("");
            }
        };
		//lambda表达式简化
        Predicate<String> predicate = (s) -> {return s == null || s.equals("");};
        System.out.println(predicate.test("123"));
    }
}

10.3.Consumer接口

消费型接口

import java.util.function.Consumer;

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

10.4.Supplier接口

供给型接口

import java.util.function.Supplier;
//供给型 没有参数只有返回值
public class Demo4 {
    public static void main(String[] args) {
        Supplier<String> stringSupplier = new Supplier<String>() {
            @Override
            public String get() {
                return "asd";
            }
        };
        System.out.println(stringSupplier.get());
    }
}

11.Stream流式计算

案例

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

/*
* 题目 使用一行程序完成
* 有5个用户 筛选
* 1.ID必须为偶数
* 2.年龄必须大于23岁
* 3.用户名转为大写字母
* 4.用户名字母倒着排序
* 5.只输出一个用户
* */
public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(5,"e",25);

        //集合用来存储
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);

        //流用来计算
        list.stream().filter(u -> {return u.getId() % 2 == 0;})
                .filter(u -> {return u.getAge() > 23;})
                .map(u -> {return u.getName().toUpperCase(Locale.ROOT);})
                .sorted((uu1,uu2) -> {return uu2.compareTo(uu1);})
                .limit(1)
                .forEach(System.out::print);
    }
}

12.ForkJoin

在1.7之后ForkJoin就有了,这个主要是用来并行执行任务,提高效率。

Forkjoin特点:工作窃取

A线程和B线程同时执行任务,B线程先执行完,就会将A线程中未执行完的一部分偷取过来执行

里面维护的都是双端队列

案例

任务类

package com.hty.forkjoin;

import java.util.concurrent.RecursiveTask;

/*
 * 求和计算
 * 使用forkjoin需要通过forkjoinPool来执行,使用其中的execute方法执行
 * 继承RecursiveTask这个任务类代表这个类是一个任务类 这个任务类是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;
    }

    @Override
    protected Long compute() {
        if(end - start < temp){
            long sum = 0L;
            for(long i=start;i<=end;++i){
                sum+=i;
            }
            return sum;
        }else{
            //分支合并计算
            long mid = (start + end) / 2;//中间值
            ForkJoinDemo forkJoinDemo1 = new ForkJoinDemo(start, mid);//任务
            forkJoinDemo1.fork();//拆分任务 把线程加入队列
            ForkJoinDemo forkJoinDemo2 = new ForkJoinDemo(mid + 1, end);
            forkJoinDemo2.fork();

            //获取结果
            long sum = forkJoinDemo1.join() + forkJoinDemo2.join();
            return sum;
        }
    }
}

测试类

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        long sum = 0L;
        for(long i=1;i<10_0000_0000L;++i) sum += i;
        long end = System.currentTimeMillis();
        System.out.println(end - start);//使用普通运算的时间

        long start1 = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinDemo forkJoinDemo = new ForkJoinDemo(1L,10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo);
        Long compute = submit.get();
        long end1 = System.currentTimeMillis();
        System.out.println(end1 - start1);//使用分支任务的时间


        long start2 = System.currentTimeMillis();
        LongStream.rangeClosed(1L,10_0000_0000L).parallel().reduce(0,Long::sum);
        long end2 = System.currentTimeMillis();
        System.out.println(end2 - start2);//使用stream并行流

    }
}

13.异步回调

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

我们一般使用Future的实现类CompletableFuture

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

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //异步执行  先打印111过了2s之后才输出异步任务中的内容 runAsync没有返回值
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "runAsync=>Void");
        });

        //获取执行结果
        System.out.println("111");
        completableFuture.get();
		
        //--------------------------------------------------------------------------
        
        //有返回值的异步回调  要么成功,要么失败
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            int i = 10/0;
            return 1024;
        });
        System.out.println(completableFuture.whenComplete((t, u) -> {//成功的回调
            System.out.println(t);//正常的返回结果
            System.out.println(u);//失败的返回结果
        }).exceptionally((e) -> {//失败的回调
            e.printStackTrace();
            return 233;
        }).get());
    }
}

14.JMM

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

JMM的同步约定

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

JMM的操作有4组八个

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

规则:

  • 不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write。
  • 不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存。
  • 不允许线程将没有assign的数据从工作内存同步到主内存。
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过load和assign操作。
  • 一个变量同一时间只能有一个线程对其进行lock操作。多次lock之后,必须执行相同次数unlock才可以解锁。
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
  • 一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存。

问题:程序不知道主内存的值被修改了,此时就需要使用volatile关键字

15.Volatile

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

保证可见性

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    /*
    * 如果没有添加volatile关键字,程序就会一直卡顿在线程1处,加上之后,
    * 线程1就可以知道主内存的值被修改了,从而终止循环,体现volatile的可见性
    * */
    private volatile static int num = 0;

    public static void main(String[] args) throws InterruptedException {//main线程
        new Thread(() -> {//线程1
             while(num == 0){

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

        num=1;

        System.out.println(num);
    }
}

不保证原子性 原子性就是不可分割的意思 线程A在执行任务的时候,不能被打扰的,也不能被分割,要么同时成功,要么同时失败

//不保证原子性
public class VolatileDemo1 {
    private volatile static int num = 0; //加上了volatile关键字发现结果仍然不正确 就验证了不保证原子性

    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<1000;++j){
                    add();
                }
            }).start();
        }
        System.out.println(num);//理论上结果应该为20000,但是结果并不是20000
    }
}

不加lock和synchronized的解决方案: 使用juc下的atomic包下的类

import java.util.concurrent.atomic.AtomicInteger;

//不保证原子性
public class VolatileDemo1 {
    //原子类的int
    private volatile static AtomicInteger num = new AtomicInteger();

    public static void add(){
        num.getAndIncrement();//+1方法 使用到了CAS
    }

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

        while(Thread.activeCount() > 2){//判断当前执行的线程是否大于2
            Thread.yield();//继续执行没有执行完的线程
        }

        System.out.println(num);//现在就是20000
    }
}

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

禁止指令重排

指令重排:自己写的程序,计算机并不是按照自己写的那样去执行的,

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

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

16.单例模式

饿汉式

//饿汉式单例
public class Hungry {
    //私有构造方法,防止其他地方new对象
    private Hungry(){}
    
    public static final Hungry HUNGRY = new Hungry();
    
    public static Hungry getInstance(){
        return HUNGRY;
    }

}

懒汉式

//懒汉式 单线程下没问题 多线程有问题
public class LazyMan {
    private LazyMan (){}

    public static LazyMan LAZY_MAN;

    public static LazyMan getInstance(){
        if(LAZY_MAN == null){
            LAZY_MAN = new LazyMan();
        }
        return LAZY_MAN;
    }
}

DCL懒汉式

package com.hty.single;

//懒汉式 单线程下没问题 多线程有问题
public class LazyMan {
    private LazyMan (){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    //需要禁止指令重排
    public volatile static LazyMan LAZY_MAN;

    //双重检测锁模式的懒汉式单例 简称DCL懒汉式
    public static LazyMan getInstance(){
        if(LAZY_MAN == null){
            synchronized (LazyMan.class){
                if(LAZY_MAN == null){
                    LAZY_MAN = new LazyMan();//这不操作并不是一个原子性操作 所以应该禁止指令重排
                }
            }
        }
        return LAZY_MAN;
    }

    public static void main(String[] args) {
        //多线程并发出现问题
        for(int i=0;i<20;++i){
            new Thread(() -> {
                LazyMan.getInstance();
            }).start();
        }
    }
}

17.CAS

CAS:compareAndSet比较工作内存的值和主内存的值,如果这个值是期望的,那么就执行操作,如果不是就一直循环(底层是自旋锁)

import java.util.concurrent.atomic.AtomicInteger;

public class Demo1 {

    public static void main(String[] args) {
        //原子类 底层使用了CAS CAS是CPU的并发原语
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //第一个参数是期望,第二个是更新
        //如果期望的值达到了,就更新,否则就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));//CAS
        System.out.println(atomicInteger.get());//2021

        System.out.println(atomicInteger.compareAndSet(2020, 2021));//CAS
        System.out.println(atomicInteger.get());//修改失败
    }
}

CAS底层是靠Unsafe类实现的,unsafe类是java的后门,可以使用这个类操作内存

底层其实就是利用内存偏移量来计算下标,然后判断当前值是否满足期望,满足就修改

缺点:

  • 循环会耗时
  • 一次性只能保证一个共享变量的原子性
  • 存在ABA问题

18.CAS的ABA问题

ABA问题

  • 在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下
    • 1.线程1,期望值为A,欲更新的值为B
    • 2.线程2,期望值为A,欲更新的值为B
  • 线程1抢先获得CPU时间片,而线程2因为其他原因阻塞了,线程1取值与期望的A值比较,发现相等然后将值更新为B,然后这个时候出现了线程3,期望值为B,欲更新的值为A,线程3取值与期望的值B比较,发现相等则将值更新为A,此时线程2从阻塞中恢复,并且获得了CPU时间片,这时候线程2取值与期望的值A比较,发现相等则将值更新为B,虽然线程2也完成了操作,但是线程2并不知道值已经经过了A->B->A的变化过程。

ABA问题带来的危害:

  • 小明在提款机,提取了50元,因为提款机问题,有两个线程,同时把余额从100变为50
    线程1(提款机):获取当前值100,期望更新为50,
    线程2(提款机):获取当前值100,期望更新为50,
    线程1成功执行,线程2某种原因block了,这时,某人给小明汇款50
    线程3(默认):获取当前值50,期望更新为100,
    这时候线程3成功执行,余额变为100,
    线程2从Block中恢复,获取到的也是100,compare之后,继续更新余额为50!!!
    此时可以看到,实际余额应该为100(100-50+50),但是实际上变为了50(100-50+50-50)这就是ABA问题带来的成功提交。
  • 解决方法: 在变量前面加上版本号,每次变量更新的时候变量的版本号都+1,即A->B->A就变成了1A->2B->3A。

19.原子引用

使用原子引用可以解决ABA问题,原子引用就是带版本号的原子操作

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

package com.hty.cas;

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

public class Demo1 {

    public static void main(String[] args) {
        //原子类 底层使用了CAS
//        AtomicInteger atomicInteger = new AtomicInteger(2020);
		//使用这个类解决ABA问题
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();//获得版本号
            System.out.println("a1=>" + stamp);

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

        },"a").start();
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1=>" + stamp);

            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(1,3,stamp,stamp+1);
            stamp = atomicStampedReference.getStamp();
            System.out.println("b2=>" + stamp);
            atomicStampedReference.compareAndSet(3,1,stamp,stamp+1);
        },"b").start();
    }
}

20.公平锁、非公平锁

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

非公平锁:非常不公平,可以插队

ReentrantLock就是一个非公平锁

21.可重入锁(递归锁)

可重入 就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。

而锁的操作粒度是”线程”,而不是调用(至于为什么要这样,下面解释).同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁,这就是可重入锁。

java里面内置锁(synchronize)和Lock(ReentrantLock)都是可重入

//synchronized 版
public class Demo1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sms();
        },"A").start();
        new Thread(() -> {
            phone.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");
    }
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

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

class Phone2{
    Lock lock = new ReentrantLock();
    public void sms(){
        lock.lock();//
        try {
            System.out.println(Thread.currentThread().getName() + "---sms");
            call();//这里还有一把锁
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

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

22.自旋锁(spinlock)

案例:unsafe源码

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

    return var5;
}

自定义自旋锁

package com.hty.lock;

import java.util.concurrent.atomic.AtomicReference;

//自旋锁
public class spinDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();
    //加锁
    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);
    }
    public static void main(String[] args) {

    }
}

测试 B线程会等到A线程执行完毕之后才会执行

package com.hty.lock;

import java.util.concurrent.TimeUnit;

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        spinDemo spinDemo = new spinDemo();

        new Thread(() -> {
            spinDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinDemo.myUnlock();
            }

        },"A").start();

        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {
            spinDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinDemo.myUnlock();
            }

        },"B").start();

    }
}

23.死锁

死锁就是互相获取对方的锁

死锁的排查:

  1. 使用jps命令定位进程号
  2. 使用jstack 进程号查看详细信息 找到死锁问题
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值