JUC

Lock 锁:(重点)

 回忆synchronized 锁:

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

/**
 * 模拟卖票
 */
public class Demo1 {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();

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

}
class  Ticket{
    private int num = 10;

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

Lock 锁:

使用三步骤:

1、new ReentrantLock();  // 点进去这个类,可以看到默认是非公平的!

2、加锁  lock.lock();

3、解锁  lock.unlock();

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

/**
 * 模拟卖票
 */
public class Demo1 {

    public static void main(String[] args) {
        Ticket ticket = new Ticket();

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

}
class  Ticket{
    private int num = 10;

    Lock lock = new ReentrantLock();

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

        }finally {
            // 解锁
            lock.unlock();
        }
    }
}

synchronized 和 Lock 区别:

1、synchronized 是内置的java 关键字,lock 是java 类

2、synchronized  无法判断 获取锁的状态,lock 可以判断是否获取锁

3、synchronized  会自动释放锁,lock 不会自动释放,如果不释放,就会死锁

4、synchronized  如果线程阻塞,另一个会一直等,lock 就不一定了,它有 .tryLock() 方法,尝试获取锁

5、synchronized  可重入的,不可以中断,非公平,lock 可以判断锁,可以自己设置是否公平的锁;new ReentrantLock 时候给参数

6、synchronized  适合少量代码同步,lock 适合大量同步代码块

 传统的 生产者、消费者,以及虚假唤醒问题

线程之间的通信问题:生产者和消费者问题。 等待唤醒,通知唤醒

比如说:线程交替 A B 操作同一个变量 num = 0

/**
 * 线程之间的通信问题:生产者和消费者问题。 等待唤醒,通知唤醒
 * 线程交替     A   B 操作同一个变量 num = 0
 * A    num+1
 * B    num-1
 */
public class Demo2 {
    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 num = 0;

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

这样打印的是正常的,因为只有两个线程,

如果再加两个线程 C  D,那就会出现意向不到的问题,打印的结果就会乱了,if 判断会造成虚假唤醒

解决办法很简单,把if 换成 while

Lock 锁 版的生产者和消费者

还是Lock 用法的三步,new ReentrantLock();  它里面有.newCondition 这个方法,

这个方法里有await 和 signal两个方法,和wait 跟 notify 这两个是一样的,

然后还是和正常使用Lock 锁是一样的,加锁、释放。

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

/**
 * 线程之间的通信问题:生产者和消费者问题。 等待唤醒,通知唤醒
 * 线程交替     A   B 操作同一个变量 num = 0
 * A    num+1
 * B    num-1
 */
public class Demo3 {
    public static void main(String[] args) {
        Data3 data = new Data3();
        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 Data3{
    private int num = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    // +1
    public void increment() throws InterruptedException {

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

这样还是存在一个问题,这样跟传统的sync 同步锁是打印的没有去别的,因为四个线程的

执行顺序不是按ABCD 顺序执行的,它没有实现精准唤醒下一个线程执行的一个效果。

记住,任何的新出来的技术,绝对不只是替换原来的旧技术!

Condition 实现精准唤醒

A执行完调用B,B执行完调用C,C执行完调用A,依次循环

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

public class Demo4 {
    public static void main(String[] args) {
        Data4 data4 = new Data4();
        new Thread(()->{for (int i = 0; i < 10; i++) data4.printA();},"A").start();
        new Thread(()->{for (int i = 0; i < 10; i++) data4.printB();},"B").start();
        new Thread(()->{for (int i = 0; i < 10; i++) data4.printC();},"C").start();
    }
}
class Data4{
    private Lock lock = new ReentrantLock();
    // 3个监视器,来判断
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
    // 1的话让A执行,2让B,3让C,初始是1
    private int num = 1;

    public void printA(){
        lock.lock();
        try {
            // 业务、判断-》执行-》通知
            while (num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"-->AAA");
            // 唤醒,唤醒指定的人
            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()+"-->BBB");
            // 唤醒,唤醒指定的人
            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();
        }
    }
}

这样就可以实现精准唤醒。

CopyOnWriteArrayList

首先想一下,ArrayList 真的安全吗,如果是单线程运行的话,是肯定安全的,如果多线程呢?

来看下这段错误的代码:  

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class Demo5 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        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();
        }
    }
}

这个代码就会报错:java.util.ConcurrentModificationException  修改并发编程异常!

于是有一下几种解决方案:

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

public class Demo5 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();

        /**
         * 解决方案:
         */
        //  1、 List<String> list = new Vector<>();
        //  2、 List<String> list = Collections.synchronizedList(new ArrayList<>());
        //  3、 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();
        }
    }
}

不用说,肯定是第三章方案 CopyOnWriteArrayList 比较好,那么它有什么牛逼的地方呢?

前两种方案,不妨点进去看源码可以发现都是用的 synchronize 关键字,那么,只要有这个 synchronize 就会影响效率,但是 CopyOnWriteArrayList 用的是Lock 锁,相比较起来好一点吧。

Vector 和 ArrayList 我们经常做比较它们的区别,其实Vector 比 ArrayList 出的还要早,实在1.0 后出的,ArrayList 是在1.2 出的

CopyOnWriteArraySet

和CopyOnWriteArrayList 一样的,我们先看一下这个错误的代码:

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

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

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

解决办法同理:

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

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

        /**
         * 解决方案:
         * 1、 Set<String> set = Collections.synchronizedSet(new HashSet<>());
         * 2、 Set<String> set = new CopyOnWriteArraySet<>();
         */

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

原理一样的,虽然Collections 这个类下对应的方法可以解决问题,但是用的还是synchronize 关键字,所以推荐使用 CopyOnWriteArraySet

HashSet 底层是什么呢?

是HashMap,它的源码里就是new 了一个HashMap,就是这个map 的key ,因为key 是无法重复的,这就是set 的不可重复的特性的原因,它底层就是这个map.add 添加进去了。

ConcurrentHashMap

map 也不安全,这个要知道。还是先来看一下错误代码:

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

public class MapTest {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();

        /**
         * map 是这样用的吗?它的默认等价于什么?这个如果不知道,面试的时候就直接拜拜了!
         * 肯定不是这样用的,因为我们工作过不用HashMap,
         * HashMap 有个初始化容量和一个 加载因子。
         * 它等价于:new HashMap<>(16, 0.75); 点进源码可以寻找到答案,一个位运算和一个默认加载0.75
         */

        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();
        }
    }
}

这个代码运行也是会报错并发编程异常,

解决办法:

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

public class MapTest {
    public static void main(String[] args) {
        Map<String, String> map = 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();
        }
    }
}

Callable

 它是线程创建的一种方式,它和Runnable 的区别就是:

1、可以有返回值

2、可以抛出异常

3、执行方法是 call()  Runnable 是run 方法执行的

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();
        // 启动Callable 一样要用一个适配类,它就是 FutureTask
        FutureTask futureTask = new FutureTask(thread);
        // 启动
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();     // 如果两个线程都去启动,只会打印一个call 的字段,Callable 是有缓存的
        // 获取Callable 的返回结果
        String value = (String) futureTask.get();   // 这个get 方法可能会产生阻塞,因为那个call 的方法里如果有耗时的操作,
        // 所以这个 .get 方法最好放在后面执行,或者使用异步操作处理
        System.out.println(value);      // abc
    }
}

/**
 * 用法是 实现Callable,它有泛型,泛型里输入的是什么类型,返回值就一定要是什么类型的
 * 就是重写它里面call 这个方法的返回值类型
 */
class MyThread implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("call");
        return "abc";
    }
}

常用辅助类

1、CountDownLatch

 它是计数用的。倒计时减数用的,一个减法计数器

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++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"go");
                // 线程数量 -1
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        // 这个方法千万别忘记,代表计数器归零,然后就可以向下执行,不然会乱的
        countDownLatch.await();

        System.out.println("close");
    }
}

它里面主要有两个方法:

 .countDown();  数量 -1

 .await();    用法,等待计数器归零,才可以向下执行,如果计数器没有归零就唤醒这个方法,会乱的。所以这个方法写在了那个for 循环的外面

2、CyclicBarrier

 也是计数器,加法计数器。

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

public class CylicBarrierDemo {
    public static void main(String[] args) {
        /**
         * 集齐7个龙珠召唤神龙
         */
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("神龙召唤成功");
        });
        for (int i = 1; i <= 7; i++) {
            final int tem = i;
            // lambda 的这个表达式是new 了一个Thhread 类,它是是拿不到 i 的
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集了"+tem+"颗龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

加法计数器的.await();  这个方法是写在for 循环里面了,它要等待加到7 ,初始时候设置的。

3、Semaphore

 信号量。个人理解是,开闭了一个数据量的大小,进行造作他的数量。

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

public class SemaphoreDemo {
    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);  // 线程延迟2 秒
                    System.out.println(Thread.currentThread().getName()+"离开了");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    // 释放
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

ReadWriteLock -- 读写锁

 目前只有一个实现类:ReentrantReadWreteLock

ReadWriteLock 维护一对关联的Lock,也就是两个,一个用于读,一个用于写。读的时候可以多线程同时读,写的时候只能一个线程去写。

package com.biao;

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.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache2 myCache = new MyCache2();
        // 写入
        for (int i = 1; i <= 5; i++) {
            final int tem = i;
            new Thread(()->{
                myCache.put(tem+"",tem+"");
            },String.valueOf(i)).start();
        }
        // 读取
        for (int i = 1; i <= 5; i++) {
            final int tem = i;
            new Thread(()->{
                myCache.get(tem+"");
            },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){
        // 写入锁时候用.writeLock() 方法,然后加锁 .lock()  就是给读写锁解锁
        readWriteLock.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 {
            // 解锁
            readWriteLock.writeLock().unlock();
        }
    }
    // 取 读,所有人都可以读,同时多线程读
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}
/**
 * 自定义伪造一个缓存
 */
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);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取OK");
    }
}

如果用的不是解锁的缓存那个类,就会在写入的时候乱套,写的时候不可以乱套的。但读 的时候,是可以多线程同时读,所以可以乱套。

阻塞队列 BlockIngQueue

 分为阻塞、队列,先理解是什么意思。

队列:先进先出,排队的。

阻塞:就是等待的意思。

就是说,如果队列满了,就必须要阻塞了,要停止了。如果队列是空的,就必须不能阻塞,赶紧运行开工啦。

 BlockIngQueue 这个类是个集合类,在Collection 类下的一个子类,Collection 下都是到有一个List 和 Set,其实还有一个同等级的类,就是BlockIngQueue。

List 下有ArrayList 和 LinkedList,这个BlockIngQueue 下也有 ArrayBlockIngQueue 和 LinkedBlockIngQueue

 

 

 

所以这个BlockingQueue 并不是新的东西,如果你不知道,那是你原来就不知道。

BlockIngQueue 四组 API

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

 

 

 

 

 

示例:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.TimeUnit.SECONDS;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        test1();
        test2();
        test3();
        test4();
    }

    /**
     * 抛出异常
     */
    public static void test1(){
        // 给出队列大小
        ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        // 给队列添加元素,返回true 表示成功
        System.out.println(arrayBlockingQueue.add("a"));
        System.out.println(arrayBlockingQueue.add("b"));
        System.out.println(arrayBlockingQueue.add("c"));
        // 再加一个就会有异常报错,队列已满
//        System.out.println(arrayBlockingQueue.add("d"));

        // 查看队首是哪个,也就是第一个元素是谁
        System.out.println(arrayBlockingQueue.element());   // a

        // 移除队列里的元素,表示移除3次,返回元素,表示移除的是哪个 a b c
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        // 如果再次移除,就会报错,队列里的元素不存在
//        System.out.println(arrayBlockingQueue.remove());
    }

    /**
     * 不抛出异常
     */
    public static void test2(){
        // 给出队列大小
        ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        // 存入队列,成功返回true
        System.out.println(arrayBlockingQueue.offer("a"));
        System.out.println(arrayBlockingQueue.offer("b"));
        System.out.println(arrayBlockingQueue.offer("c"));
        // 返回false,不抛出异常
//        System.out.println(arrayBlockingQueue.offer("d"));

        // 查看队首
        System.out.println(arrayBlockingQueue.peek());

        // 从队列弹出/移除3次
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        // 返回null,不报异常
//        System.out.println(arrayBlockingQueue.poll());
    }
    /**
     * 阻塞、等待
     */
    public static void test3() throws InterruptedException {
        // 给出队列大小
        ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        // 给队列添加元素,这个没有返回值,就不输出打印了
        arrayBlockingQueue.put("a");
        arrayBlockingQueue.put("b");
        arrayBlockingQueue.put("c");
        // 这个不会报错,它会一直等,什么时候弹出一个元素,这个就会添加进去
//        arrayBlockingQueue.put("d");    // 线程不会停止,开发中如果这样,就等于写死了。。。

        // 弹出元素,返回值是当前弹出的元素 a b c
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        // 不会报错,会一直阻塞,什么时候队列里有,就直接弹出
//        System.out.println(arrayBlockingQueue.take());
    }
    /**
     * 超时退出
     */
    public static void test4() throws InterruptedException {
        // 给出队列大小
        ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        // 给队列添加元素,没有返回值
        arrayBlockingQueue.offer("a");
        arrayBlockingQueue.offer("b");
        arrayBlockingQueue.offer("c");
        // 会一直等待,如果2 秒后队列还是满的,就直接退出不执行了
//        arrayBlockingQueue.offer("d",2, SECONDS);

        // 取出,返回值是元素 a b c
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        // 超过2 秒内,如果队列里还没有,就退出不去拿了
//        arrayBlockingQueue.poll(2, SECONDS);
    }
}

同步队列 SynchronousQueue

 这个相比 阻塞队列要简单,api 很少

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

public class SynchronousQueueDemo {
    public static void main(String[] args) {
        // 同步队列,是同步的,这边存,那边就要立马去取出来
        SynchronousQueue synchronousQueue = new SynchronousQueue();
        //
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"put 1");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName()+"put 2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName()+"put 3");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();
        //
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"=>"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"=>"+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+"=>"+synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

线程池(重点)3大方法、7大参数、4种拒绝策略

 池化技术:就是提前建立多个连接,用的时候就直接用了,避免了一直重复的连接、关闭,避免消耗资源。比如连接池、线程池、内存池。。。

Executors:线程池的工具类,这个在阿里巴巴开发手册说不推荐使用。

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

/**
 * Executors 工具类 3 大方法
 */
public class ThredPool {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();     // 单个线程的线程池
//        ExecutorService executor = Executors.newFixedThreadPool(5);     // 创建一个5个线程的线程池
//        ExecutorService executor = Executors.newCachedThreadPool();     // 可伸缩大小的线程池,遇强则强,遇弱则弱。。

        try {
            for (int i = 0; i < 10; i++) {
                executor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 用完后需要关闭线程池的连接
            executor.shutdown();
        }
    }
}
Executors的那三个方法的源码里,本质都是用的一个 ThreadPoolExecutor

7 大参数和自定义线程池

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

import java.util.concurrent.*;

/**
 * Executors 工具类 3 大方法
 */
public class ThredPool {
    public static void main(String[] args) {
        // 自定义线程池
        ExecutorService executor = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()    // 拒绝策略,有好几种,具体用哪种看业务场景
        );

        try {
            for (int i = 0; i < 10; i++) {
                executor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 用完后需要关闭线程池的连接
            executor.shutdown();
        }
    }
}

cpu 密集型 和 IO 密集型(调优)

cpu 密集型:看电脑是几核的就是几,这样可以保持cpu 的效率最高

  获取电脑上cpu 的核数: Runtime.getRuntime().availableProcessors()   // 返回值就是cpu 核数的数量

io 密集型: 判断程序中十分耗 IO 的线程有多少个,然后给出相应的线程数量来处理,一般是双倍。就是很耗io 线程的数量的双倍。

四大函数式接口lambda(必须要会)

比如:lambda表达式、链式编程、函数式接口、Stream流式计算,这些都是函数式接口。

Function:函数式接口:意思就是只有一个方法的接口,这种的在java里非常的多,底层大量的运用。

import java.util.function.Function;

public class Demo {
    public static void main(String[] args) {
//        Function<String, String> function = new Function<String, String>() {    // 参数1:给的参数类型,参数2:返回的参数类型
//            @Override
//            public String apply(String s) { // 这里的返回参数类型一定要和上面的一样
//                return s;
//            }
//        };

        // lambda 表达式
        Function<String,String> function = s -> {return s;};
        System.out.println(function.apply("abc"));
    }
}

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

import java.util.function.Predicate;

public class Demo {
    public static void main(String[] args) {
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.isEmpty();
            }
        };

        System.out.println(predicate.test("abc"));  // 输入有值就返回false,没有值就返回true
    }
}

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

import java.util.function.Consumer;

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

        // lambda 表达式写法
        Consumer<String> consumer = s -> {System.out.println(s);};
        consumer.accept("abc");  // 返回的就直接打印了
    }
}

Supplier:供给型接口:没有参数,只有返回值,就是返回给别人一个东西

import java.util.function.Supplier;

public class Demo {
    public static void main(String[] args) {
//        Supplier<Integer> supplier = new Supplier<Integer>() {
//            @Override
//            public Integer get() {
//                return 123;
//            }
//        };

        // lambda 表达式写法
        Supplier supplier = ()->{return 123;};
        
        System.out.println(supplier.get());
    }
}

Stream 流式计算 

大数据:存储 + 计算

计算都应该交给 流 来操作。

示例:一个实体类

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    
    private int id;
    private String name;
    private int age;
    
}
import java.util.Arrays;
import java.util.List;

/**
 * 要求:一分钟内完成此题,只能用一行代码实现。
 * 筛选:
 *  1、id 必须是偶数
 *  2、年龄必须大于23岁
 *  3、用户名转为大写字母
 *  4、用户名字母倒序排序
 *  5、只输出一个用户
 */
public class StreamTest {
    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);
        // 计算交给Stream
        list.stream()
                .filter(user -> {return user.getId()%2==0;})    // id 必须式偶数
                .filter(user -> {return user.getAge() > 23;})   // 年龄大于 23
                .map(user -> {return user.getName().toUpperCase();})    // 名字转为大写
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})    // 倒序排列
                .limit(1)   // 只输出一个,分页实现
                .forEach(System.out::println);  // 打印
    }
}

ForkJoin

 在jdk1.7 之后出现的,可以并行执行任务,提高效率,适用于大数据量

大数据:Map Reduce--意思就是 把大任务拆分成小任务,把计算出的多个结果再合并成一个最终的结果。这种思想就是 ForkJoin

特点:工作窃取。就是可以提高效率。意思就是一个双端队列(两端都可以取出),两边都可以操作,一边执行完了以后,另一边还没有执行完,执行完的那一边,就会把这边没执行完的任务拿过来做。

import java.util.concurrent.RecursiveTask;

/**
 * 求和计算
 * 优秀的人用ForkJoin,牛逼的人用Stream
 * 使用ForkJoin:1、forkjoinPool 来执行
 * 2、使用它里面的 .execute(ForkJoinTask task) 计算任务
 * 3、记得首先要使用的这个类,要去继承 RecursiveTask<>,然后去重写里面的方法 compute
 */
public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;     // 开始值
    private Long end;       // 结束值
    private Long tem = 10000L;  // 临界值

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

    // 重写计算方法,返回值就是 继承的类的 泛型
    @Override
    protected Long compute() {
        if ((end - start) < tem){     // 如果 结束值 - 开始值 < 临界值,就分支合并计算
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }else {
            // 分支合并计算
            long middle = (start + end) / 2;      // 中间值
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork();   // 拆分任务了,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            task1.fork();   // task2 也要压入
            return task1.join() + task2.join();
        }
    }
}

测试类:

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

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        test1();
//        test2();
        test3();
    }
    // 普通人
    public static void test1(){
        Long sum = 0L;
        long start = System.currentTimeMillis();    // 时间
        for (Long i = 0L; 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);  // 这个式异步提交 .execute 是同步
        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_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+"时间="+(end-start));
    }
}

异步回调

异步调用首先想到的式ajax,但是java线程也是可以异步调用的。

 CompletableFuture:

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

/**
 * 异步请求
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 没有返回值的:
        // 发送一个请求,没有返回值,泛型应该写Void,V 大写
//        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
//            try {
//                TimeUnit.SECONDS.sleep(2);  // 休眠2 秒
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
//        });
//        System.out.println("111");  // 这个111 会先打印,因为上面休眠了2 秒,它就会异步先来执行下面的
//        // 获得阻塞结果
//        completableFuture.get();

        // 有返回值的异步回调,
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName()+"runAsync=>Integer");
            return 1024;
        });
        // 有返回值回调的话,就分返回成功 和 返回失败
        completableFuture.whenComplete((t,u)->{
            System.out.println("t=>"+t);    // t 是正常的
            System.out.println("u=>"+u);    // u 是错误的信息
        }).exceptionally((e)->{
            e.printStackTrace();
            System.out.println(e.getMessage());
            return 500;
        }).get();
    }
}

JMM java内存模型

JMM 的同步约定:

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

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

3、加锁和解锁式同一把锁

线程内存分:工作内存、主内存

8 种操作:

 

 如果两条线程都去主内存去拿,中间改变了Flag 的值,主内存没有来得及刷新,不能改变它的可见性。

这些指令必须承对出现,不可以单独使用。

先看下这个错误的代码:

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    private static int num = 0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{    // 现在有线程1 和 main 线程
           while (num == 0){    // num 改变成1 ,线程就不会停止

           }
        }).start();
        TimeUnit.SECONDS.sleep(1);  // 让线程1 睡一秒

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

这个问题是:num 的值发生变化,但主内存不知道,没有确保主内存变量的可见性。

volatile

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    // 添加volatile 关键字,确保它的可见性
    private volatile static int num = 0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{    // 现在有线程1 和 main 线程
           while (num == 0){    // num 改变成1 ,线程就不会停止

           }
        }).start();
        TimeUnit.SECONDS.sleep(1);  // 让线程1 睡一秒

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

volatile:是java 关键字,java虚拟机提供 轻量级的同步机制

  1、保证可见性

  2、不保证原子性(要么成功,要么失败)

  3、禁止指令重排

验证:volatile 不能保证原子性:

public class Demo2 {
    // 就算加上volatile 是不能保证原子性
    private volatile static int num = 0;

    // 如果在这个方法加 synchronized 可以确保num 原子性
    public static void add(){  // 只要调用add 方法,num++
        num++;
    }

    public static void main(String[] args) {
        // num 理论上应该是 20000
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2){   // 如果线程大于2
            Thread.yield();     // 让线程迭让
        }
        System.out.println(Thread.currentThread().getName()+" "+num);
    }
}

那么,如果不用synchronized 或者Lock 锁,怎么确保原子性??

原子类:比如int,就用AtomicInteger()

import java.util.concurrent.atomic.AtomicInteger;

public class Demo2 {
    // 原子类的Integer
    private volatile static AtomicInteger num = new AtomicInteger();
    
    public static void add(){  // 只要调用add 方法,num++
//        num++;
        num.getAndIncrement();  // 这个getAndIncrement 并不是简单用了+1 操作,用的是CAS,效率极高
    }

    public static void main(String[] args) {
        // num 理论上应该是 20000
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2){   // 如果线程大于2
            Thread.yield();     // 让线程迭让
        }
        System.out.println(Thread.currentThread().getName()+" "+num);
    }
}

这样就可以确保原子性,原子类的底层非常牛逼,直接和操作系统挂钩,直接在内存中修改值!Unsafe 类是一个很特殊的存在,往下看!

volatile - 禁止指令重排

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

虽然我们正常写的代码可能永远都不会重排,但这个几率是一定存在的。

volatile 加上以后,可以禁止重排序。

单例模式

volatile 的内存屏障 在单例模式使用的最多,单例模式分 懒汉模式、饿汉模式

懒汉式里有个DCL懒汉式

 饿汉式单例回顾:

/**
 * 饿汉 单例模式
 */
public class Hungry {
    private Hungry() {
    }
    private final static Hungry HUNGRY = new Hungry();

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

懒汉式单例:

package com.biao.single;

/**
 * 懒汉式单例
 */
public class LazMan {
    private LazMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private static LazMan lazMan;

    public static LazMan getInstance(){
        if (lazMan == null){
            lazMan = new LazMan();
        }
        return lazMan;
    }
    // 单线程下,单例确实没毛病,但是多线程下,是有问题的
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazMan.getInstance();
            }).start();
        }
    }
}

怎么解决单线程下的单例模式? 双重检验锁!

package com.biao.single;

/**
 * 懒汉式单例
 */
public class LazMan {
    private LazMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    private volatile static LazMan lazMan;

    // 双重检验锁,锁它的class   DCL 懒汉式
    public static LazMan getInstance(){
        if (lazMan == null){
            synchronized (LazMan.class){
                if (lazMan == null){
                    lazMan = new LazMan();  // 它不是原子性操作,所以一定要在上面加 volatile,避免重排序
                }
            }
        }
        return lazMan;
    }
    // 单线程下,单例确实没毛病,但是多线程下,是有问题的
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazMan.getInstance();
            }).start();
        }
    }
}

静态内部类:

/**
 * 静态内部类
 */
public class Holder {
    private Holder() {
        System.out.println(Thread.currentThread().getName()+"ok");
    }

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

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

}

单例模式式不安全的,可以通过反射破坏,最好的解决办法是:枚举

CAS 理解

cas 是cpu 的并发原理。

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        // 比较并更换,默认是2020,如果是2020的话就返回2021
        System.out.println(atomicInteger.compareAndSet(2020, 2021));    // true
        System.out.println(atomicInteger.get());    // 返回2021

        System.out.println(atomicInteger.compareAndSet(2020, 2021));    // false
        System.out.println(atomicInteger.get());    // 还是返回2021,因为上面已经修改了,所以条件是2020 不成立
    }
}

这个 .compareAndSet(,)  方法,就是CAS的缩写,这个方法底层调用的 Unsafe 类,它调用的是内存里的方法,所以效率非常快,里面的 getAndAddInt 方法,是个自旋锁。

说到CAS,面试时候可能会问ABA 的问题。

ABA:说白了就是狸猫换太子。

CAS 的缺点:循环耗时(自旋锁)、一次只能保证一个共享变量的原子性、ABA问题

原子引用解决ABA问题

引用原子引用,对应的是乐观锁 的思想

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

public class CASDemo {
    public static void main(String[] args) {
        // 如果泛型是一个包装类,注意对象引用问题,正常工作中比较的都是一个对象,如果用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();
            }
            atomicStampedReference.compareAndSet(1,2,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println("a2=>"+atomicStampedReference.getStamp());
            System.out.println(atomicStampedReference.compareAndSet(2,1,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1));
            System.out.println("a3=>"+atomicStampedReference.getStamp());
        },"a").start();


    }
}

就是改完以后,如果再要改,那就会失败,因为原有的基础上已经发生的改变,所以要在第一改完以后,再给改回来。

可重入锁

又称 递归锁,意思是,拿到了外面的锁,就可以打开里面的锁,自动获得。有了大门钥匙,就可以进入房间里的门。

// 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(); // 这里调用cass 方法里的锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"call");
    }
}

如果使用Lock,就等于加了两把锁,每次都是锁上后再释放,但synchronized 是自动释放锁,所以感觉是,虽然是一把锁,但是只进入锁一次。

Lock 锁:如果 .lock() 方法锁了两次,一定也要在finally 里面释放两次,不然会是死锁。

自旋锁

CAS 自定义的自旋锁:

package com.biao.lock;

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

/**
 * 自旋锁
 */
public class SpinlockDemo {
    // Thread 默认值是null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    // 加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"=>mylock");
        // 自旋锁
        while (!atomicReference.compareAndSet(null,thread)){    // 条件不成立,就一直去执行

        }
    }
    // 解锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"=>myUnLock");
        // 解锁不用自旋,如果里面有线程,就给它 null
        atomicReference.compareAndSet(thread,null);
    }
    // 测试:T1 和T2 都去执行,T1先拿到锁,T1 解锁之后,T2 才能解锁
    public static void main(String[] args) {
        SpinlockDemo lock = new SpinlockDemo();
        new Thread(()->{
            // 加锁,自己写的锁
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                // 解锁
                lock.myUnLock();
            }
        },"T1").start();

        new Thread(()->{
            // 加锁,自己写的锁
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                // 解锁
                lock.myUnLock();
            }
        },"T2").start();
    }
}

死锁

两个锁掐架了。。。

都去试图获取对方的锁

排除死锁:

import java.util.concurrent.TimeUnit;

/**
 * 死锁
 */
public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        // A 想获得B,B想获得A,结果死锁
        new Thread(new MyThread(lockA,lockB),"T1").start();
        new Thread(new MyThread(lockB,lockA),"T2").start();
    }
}
class MyThread implements Runnable{
    private String lockA;
    private String lockB;

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

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"=>"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"=>"+lockA);
            }
        }
    }
}

解决问题:

jps         定位进程号

jstack  进程号    然后就可以查看锁的信息

 

posted @ 2020-08-27 15:21  aBiu--  阅读(75)  评论(0编辑  收藏  举报