JUC笔记

Posted scanner小霸王

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC笔记相关的知识,希望对你有一定的参考价值。

synchronized的8种现象

new 锁的是 一个对象
static 锁的是 Class
1)锁的调用者是同一个对象,按照顺序执行
先打印邮件1,再发短信2

package com.coding.lock8;

import java.util.concurrent.TimeUnit;

/**
 * **1、标准访问,请问先打印邮件1还是短信2?**
 *
 *
 */
public class Test1 {
    public static void main(String[] args) {

        Phone phone = new Phone();

        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> {
            phone.sendEmail();
        }, "A").start();

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

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

    }
}

// 手机,发短信,发邮件
class Phone {
    // 被 synchronized 修饰的方法、锁的对象是方法的调用者、
    public synchronized void sendEmail() {
        System.out.println("sendEmail");
    }

    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

2)邮件方法暂停4秒钟,还是先打印邮件
// 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!

package com.coding.lock8;

import java.util.concurrent.TimeUnit;

/**
 * **2、邮件方法暂停4秒钟,请问先打印邮件还是短信?**
 */
public class Test2 {
    public static void main(String[] args) {

        Phone2 phone = new Phone2();

        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> {
            phone.sendEmail();
        }, "A").start();

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

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

    }
}


// 手机,发短信,发邮件
class Phone2 {
    // 被 synchronized 修饰的方法、锁的对象是方法的调用者、
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendMS() {
        System.out.println("sendMS");
    }
}

3)新增的方法没有被 synchronized 修饰,不是同步方法,所以不需要等待,其他线程用了一个把锁

package com.coding.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 3、新增一个普通方法hello()没有同步,请问先打印邮件还是hello?
 */
public class Test3 {

    // 回家  卧室(锁)   厕所
    public static void main(String[] args) {

        Phone3 phone = new Phone3();

        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> { // 一开始就执行了
            phone.sendEmail();
        }, "A").start();

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

        new Thread(() -> { // 一秒后执行
            phone.hello();
        }, "B").start();

    }
}

// 锁:竞争机制

// 手机,发短信,发邮件
class Phone3 {
    // 被 synchronized 修饰的方法、锁的对象是方法的调用者、
    public synchronized void sendEmail() {
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendMS() {
        System.out.println("sendMS");
    }

    // 新增的方法没有被 synchronized 修饰,不是同步方法,所以不需要等待,其他线程用了一个把锁
    public void hello() {
        System.out.println("hello");
    }

}

4)我们这里两个线程不是同个对象。

package com.coding.lock8;

import java.util.concurrent.TimeUnit;

/**
 * **4、两部手机、请问先打印邮件还是短信?**
 */
public class Test4 {

    // 回家  卧室(锁)   厕所
    public static void main(String[] args) {
        // 两个对象,互不干预
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> { // 一开始就执行了
            phone1.sendEmail();
        }, "A").start();

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

        new Thread(() -> { // 一秒后执行
            phone2.sendMS();
        }, "B").start();

    }

}

// 手机,发短信,发邮件
class Phone4 {
    // 被 synchronized 修饰的方法、锁的对象是方法的调用者、调用者不同,没有关系,量个方法用得不是同一个锁!
    public synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public synchronized void sendMS() {
        System.out.println("sendMS");
    }

}

5)/被 synchronized 修饰 和 static 修饰的方法,锁的对象是类的 class 对象!唯一的
是 同一把锁

package com.coding.lock8;

import java.util.concurrent.TimeUnit;

/*
 **5、两个静态同步方法,同一部手机,请问先打印邮件还是短信?**
 */
public class Test5 {
    // 回家  卧室(锁)   厕所
    public static void main(String[] args) {
        // 两个对象,互不干预
        Phone5 phone = new Phone5();

        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> { // 一开始就执行了
            phone.sendEmail();
        }, "A").start();

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

        new Thread(() -> { // 一秒后执行
            phone.sendMS();
        }, "B").start();

    }

}


// 手机,发短信,发邮件
class Phone5 {

    // 对象    类模板可以new 多个对象!
    // Class   类模版,只有一个

    // 被 synchronized 修饰 和 static 修饰的方法,锁的对象是类的 class 对象!唯一的
    // 同一把锁

    public static synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public static synchronized void sendMS() {
        System.out.println("sendMS");
    }

}

6)虽然是phone1和phone2不同对象调用,但是静态Static修饰之后,是个Class对象,锁的是同个对象,所以还是打电话优先

package com.coding.lock8;

import java.util.concurrent.TimeUnit;

/** 第一次听可能不会,第二次也可能不会,但是不要放弃,你可以!
 **6、两个静态同步方法,2部手机,请问先打印邮件还是短信?**
 */
public class Test6 {

    // 回家  卧室(锁)   厕所
    public static void main(String[] args) {
        // 两个对象,互不干预
        Phone6 phone = new Phone6();
        Phone6 phone2 = new Phone6();

        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> { // 一开始就执行了
            phone.sendEmail();
        }, "A").start();

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

        new Thread(() -> { // 一秒后执行
            phone2.sendMS();
        }, "B").start();

    }

}

// 手机,发短信,发邮件
class Phone6 {

    // 对象    类模板可以new 多个对象!
    // Class   类模版,只有一个

    // 被 synchronized 修饰 和 static 修饰的方法,锁的对象是类的 class 对象!唯一的
    // 同一把锁

    public static synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    public static synchronized void sendMS() {
        System.out.println("sendMS");
    }

}

7)一个普通同步方法,一个静态同步方法不同锁,先sendMS

package com.coding.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 7、一个普通同步方法,一个静态同步方法,同一部手机,请问先打印邮件还是短信?**
 */
public class Test7 {
    // 回家  卧室(锁)   厕所
    public static void main(String[] args) {
        // 两个对象,互不干预
        Phone7 phone = new Phone7();

        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> { // 一开始就执行了
            phone.sendEmail();
        }, "A").start();

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

        new Thread(() -> { // 一秒后执行
            phone.sendMS();
        }, "B").start();

    }
}


// 解析:连个方法的锁不同,所以不阻塞
class Phone7{

    // CLASS
    public static synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    // 对象
    // 普通同步方法
    public synchronized void sendMS() {
        System.out.println("sendMS");
    }

}

8)两个对象分别调用静态方法和非静态方法,sendMS优先

package com.coding.lock8;

import java.util.concurrent.TimeUnit;

/**
 * **8、一个普通同步方法,一个静态同步方法,2部手机,请问先打印邮件还是短信?**
 */
public class Test8 {

    public static void main(String[] args) {
        // 两个对象,互不干预
        Phone8 phone = new Phone8();
        Phone8 phone2 = new Phone8();

        // 我们这里两个线程使用的是同一个对象。两个线程是一把锁!先调用的先执行!
        new Thread(() -> { // 一开始就执行了
            phone.sendEmail();
        }, "A").start();

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

        new Thread(() -> { // 一秒后执行
        }, "B").start();

    }

}



class Phone8{

    // CLASS
    public static synchronized void sendEmail() {
        // 善意的延迟
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendEmail");
    }

    // 对象
    // 普通同步方法
    public synchronized void sendMS() {
        System.out.println("sendMS");
    }

}

集合类不安全

1)ArrayList不支持并发,效率高,可以使用CopyOnWriteArrayList,
CopyOnWriteArrayList比vector效率高,vector底层用了synchnized,CopyOnWriteArrayList用了locked锁

synchronized与Lock的区别
a) synchronized 是Java内置的关键字,使用后会自动释放锁
Lock是java.util.concurrent.Locks 包下的一个接口,必须要手动释放。特别是在发生异常时,需要在 finally 块中进行手动释放,否则会发生死锁行为
b)synchronized 是非公平锁,即不能保证等待锁线程的顺序;Lock的实现 ReentrantLock 可通过实例化true or false 的构造参数实现公平锁和非公平锁,默认为非公平锁
Lock lock =new ReentrantLock (true);公平锁

package com.coding.collunsafe;

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

/**
 * 善于总结:
 * 1、 故障现象: ConcurrentModificationException
 * 2、 导致原因: 多线程操作集合类不安全
 * 3、 解决方案:
 *      List<String> list = new Vector<>(); // Vector 是一个线程安全的类,效率低下  50
 *      List<String> list = Collections.synchronizedList(new ArrayList<>()); // 60
 *      List<String> list = new CopyOnWriteArrayList<>(); // JUC 100 推荐使用
 */
public class UnsafeList2 {
    public static void main(String[] args) {
        // 代码实现
        // ArrayList<Object> list = new ArrayList<>(); // 效率高,不支持并发!
        // List<String> list = new Vector<>(); // Vector 是一个线程安全的类,效率低下  50
        // List<String> list = Collections.synchronizedList(new ArrayList<>()); // 60

        // 多线程高并发程序中,一致性最为重要

        // 写入时复制;  COW 思想,计算机设计领域。优化策略
        //  思想: 多个调用者,想调用相同的资源; 指针
        // 只是去读,就不会产生锁!
        // 假如你是去写,就需要拷贝一份都自己哪里,修改完毕后,在替换指针!

        List<String> list = new CopyOnWriteArrayList<>(); // JUC 100

        // 测试多线程下是否安全List,3条线程都不安全了
        // 多线程下记住一个异常,并发修改异常 java.util.ConcurrentModificationException

        // Exception  ConcurrentModificationException
        for (int i = 1; i <= 30; i++) {
            new Thread(()->{
                // 3个结果
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }

    }
}

2)Set不安全
使用:CopyOnWriteArraySet
hashSet底层就是hashMap

package com.coding.collunsafe;

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

/**
 * 善于总结:
 * 1、 故障现象: ConcurrentModificationException 并发修改异常!
 * 2、 导致原因: 并发下 HashSet 存在安全的问题
 * 3、 解决方案:
 *     Set<String> set = Collections.synchronizedSet(new HashSet<>());  60
 *     Set<String> set =new CopyOnWriteArraySet<>();  // 100
 *
 */
public class UnsafeSet1 {
    public static void main(String[] args) {
        // Set<String> set = new HashSet<>(); // 底层是什么
        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();
        }

    }
}

3)hashMap不安全,使用ConcurrentHashMap

package com.coding.collunsafe;

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

// 任何存在想修改JDK源码都是不可取的

// ConcurrentModificationException
// 并发下 HashMap 不安全
// 解决方案:Map<String, String> map = new ConcurrentHashMap<>();

public class UnSafeMap {
    public static void main(String[] args) {
        // 在开发中会这样使用 HashMap 吗? 不会  一开始就知道 100大小的容量
        // 根据实际的业务设置初始值

        // 人生如程序,不是选择,就是循环,学习和总结十分重要!
        //Map<String, String> map = new HashMap<>();
        Map<String, String> map = new ConcurrentHashMap<>();

        // 加载因子,初始值
        // Map<String, String> map = new HashMap<>(100,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();
        }

    }
}

Callable

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


new Thread(task,“A”).start(); 的构造器中,传的是Runnable的接口,但是Callable和Runnable没有关系,需要借助FutureTask
FutureTask task = new FutureTask(myThread); // 适配类
// 会打印几次 end
new Thread(task,“A”).start(); // 执行线程
//返回值在FutureTask 拿到
System.out.println(task.get());// 获取返回值, get()

注意:
a)有缓存,所以System.out.println(task.get());只输出一次
b)结果可能需要等待,会阻塞

package com.coding.callabledemo;

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

// 练武不练功,到老一场空
// API 工程师,只会用,不会分析~
public class Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Thread(Runnable)
        // Thread(RunnableFuture)
        // Thread(FutureTask)

        MyThread myThread = new MyThread();
        FutureTask task = new FutureTask(myThread); // 适配类

        // 会打印几次 end
        new Thread(task,"A").start(); // 执行线程
        new Thread(task,"B").start(); // 执行线程。细节1:结果缓存!效率提高N倍

        System.out.println(task.get());// 获取返回值, get()

        // 细节2:task.get() 获取值的方法一般放到最后,保证程序平稳运行的效率,因为他会阻塞等待结果产生!
        // 线程是一个耗时的线程,不重要!

    }
}



class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("end");

        TimeUnit.SECONDS.sleep(3);

        return 1024;
    }

}

JUC常用的辅助类

1)CountDownLatch:减法计数器
countDownLatch.countDown();減一
countDownLatch.await(); // 阻塞等待计数器归零,才繼續執行

package com.coding.demo03;

import java.util.concurrent.CountDownLatch;

// 程序如果不加以生活的理解再加上代码的测试,你就算不会
public class CountDownLatchDemo {

    // 有些任务是不得不阻塞的  减法计数器
    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(6); // 初始值

        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Start");
                // 出去一个人计数器就 -1
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        countDownLatch.await(); // 阻塞等待计数器归零
        // 阻塞的操作 : 计数器  num++
        System.out.println(Thread.currentThread().getName()+"===END");

    }

    // 结果诡异的吗,达不到预期的 Main end 在最后一个
    public static void test1(){
        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Start");
            },String.valueOf(i)).start();
        }
        System.out.println(Thread.currentThread().getName()+"End");
    }


}


2)CyclicBarrier:加法计算器
CyclicBarrier cyclicBarrier = new CyclicBarrier(8, new Runnable() {
@Override
public void run() {
System.out.println(“神龙召唤成功!”);
}
});
cyclicBarrier.await(); // 等待 阻塞,只有得到8的时候才会继续执行

package com.coding.demo03;

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

// CyclicBarrier 栅栏 加法计数器
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        // 集齐7个龙珠召唤神龙 ++ 1

        //  public CyclicBarrier(int parties, Runnable barrierAction)
        // 等待cyclicBarrier计数器满,就执行后面的Runnable,不满就阻塞
        CyclicBarrier cyclicBarrier = new CyclicBarrier(8, new Runnable() {
            @Override
            public void run() {
                System.out.println("神龙召唤成功!");
            }
        });

        for (int i = 1; i <= 7; i++) {
            final int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集了第"+temp+"颗龙珠");

                try {
                    cyclicBarrier.await(); // 等待 阻塞
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }

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

        }


    }
}

3)Semaphore:信号量
// 模拟6个车,只有3个车位
Semaphore semaphore = new Semaphore(3); // 3个位置
semaphore.acquire(); // 得到,如果已经满了,就等待,直到有位置为止
semaphore.release(); // 释放位置,会将信号量释放+1,然后唤醒等待的线程
作用:多个共享资源共享资源互斥的使用!并发限流,控制最大的线程数

package com.coding.demo03;

import sun.misc.Unsafe;

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

// 抢车位
public class SemaphoreDemo {
    public static void main(String[] args) {
        // 模拟6个车,只有3个车位
        Semaphore semaphore = new Semaphore(3); // 3个位置

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

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

    }
}

读写锁

读的时候可以多个线程同时读取,写的时候只能有一个线程写
// 读写锁
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
readWriteLock.writeLock().lock();
readWriteLock.writeLock().unlock(); // lck.unlock();
readWriteLock.readLock().lock();
readWriteLock.readLock().unlock();

package com.coding.rwdemo;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteDemo {
    public static void main(String[] args) {

        MyCache2 myCache = new MyCache2();

        // 多个线程同时进行读写
        // 五个线程在写  线程是CPU调度的
        for (int i = 1; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }

        // 五个线程在读
        for (int i = 1; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }


    }
}

// 线程操作资源类,存在问题的
class MyCache{

    private volatile Map<String,Object> map = new HashMap<>();

    // 没有加读写锁的时候,第一个线程还没有写入完成,可能会存在其他写入~

    // 写。独占
    public void put(String key,String value){
        System.out.println(Thread.currentThread().getName()+"写入"+key);
        map.put(key,value);
        // 存在别的线程插队
        System.out.println(Thread.currentThread().getName()+"写入完成");
    }

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

}


// 线程操作资源类,存在问题的
class MyCache2{

    private volatile Map<String,Object> map = new HashMap<>();

    // ReadWriteLock --> ReentrantReadWriteLock   lock不能区分读和写
    // ReentrantReadWriteLock 可以区分读和写,实现更加精确的控制
    // 读写锁
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 写。独占
    public void put(String key,String value){
        // lock.lock 加锁
        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(); // lck.unlock();
        }
    }

    // 多线程下尽量加锁!

    // 读
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取结果:"+result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

}

阻塞队列

写入:如果队列满了,就必须阻塞等待
取:如果队列是空的,必须等待生产

四组API
1)抛出异常
add()
remove();
element():查看队首的元素

package com.coding.blocking;

import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;

public class Test1 {
    public static void main(String[] args) {
        // 队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); // 阻塞队列

        // add返回布尔值
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        // System.out.println(blockingQueue.add("d")); // java.lang.IllegalStateException: Queue full

        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.remove()); // a

        System.out.println(blockingQueue.element());

        blockingQueue.remove();

        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.remove()); // b
        System.out.println(blockingQueue.remove()); // c
        System.out.println(blockingQueue.remove()); // java.util.NoSuchElementException


    }
}

2)不会抛出异常
offer():添加—》返回boolean值
poll():弹出----》返回null
peek():查看队首的元素

package com.coding.blocking;

import java.util.concurrent.ArrayBlockingQueue;

// 通常!
public class Test2 {

    public static void main(String[] args) {

        // 队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); // 阻塞队列

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        // 等待(一直等待,超时就不等你)
        // System.out.println(blockingQueue.offer("d")); // false 我们通常不希望代码报错!这时候就使用offer

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

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll()); // null

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

    }



}

3)阻塞等待
put(); —>当队列的长度不够时候,将会陷入阻塞
take();当没有元素时候,将陷入阻塞

package com.coding.blocking;

import java.util.concurrent.ArrayBlockingQueue;

public class Test3 {

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

        // 队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); // 阻塞队列

        // 一直阻塞。超过3秒我就不等了, 业务必须要做!
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        // blockingQueue.put("d");

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take()); // 阻塞等待拿出元素
    }
}

4)超时等待
blockingQueue.offer(“d”,3L,TimeUnit.SECONDS);
等3秒,没有位置就退出
blockingQueue.poll(3L,TimeUnit.SECONDS)

package com.coding.blocking;

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

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

        // 队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3); // 阻塞队列

        // 设置超时的时间
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        // 超过3秒就不等待了
        blockingQueue.offer("d",3L,TimeUnit.SECONDS); // 不让他一直等待,然后也不想返回false,设置等待时间

        System.out.println(blockingQueue.poll()); // a
        System.out.println(blockingQueue.poll()); // b
        System.out.println(blockingQueue.poll()); // c
        System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS)); // 阻塞
    }
}

同步队列 SynchronousQueue

每一个 put 操作。必须等待一个take。否则无法继续添加元素!
相当于长度只有一个

package com.coding.blocking;

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

// 同步队列
// 每一个 put 操作。必须等待一个take。否则无法继续添加元素!
public class Test5 {
    public static void main(String[] args) {
        // 不用写参数!
        SynchronousQueue<String> queue = new SynchronousQueue<>();


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


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


    }
}

线程池

1)池化技术:
池化技术简单点来说,就是提前保存大量的资源,以备不时之需。在机器资源有限的情况下,使用池化
技术可以大大的提高资源的利用率,提升性能等。
2)线程池好处:
a)降低资源的消耗。
b)提高响应的速度。
c)方便管理。

它的主要特点为:线程复用,控制最大并发数,管理线程。

3)Executors工具的三大方法:
本质调用的是:ThreadPoolExecutor方法

ExecutorService threadpool1 = Executors.newFixedThreadPool(5); // 固定大小
ExecutorService threadpool2 = Executors.newCachedThreadPool(); //可以弹性伸缩的线程池,遇强则强
ExecutorService threadpool3 = Executors.newSingleThreadExecutor(); // 只有一个

a)newSingleThreadExecutor

b)newFixedThreadPool

c)newCachedThreadPool

package com.coding.pool;

import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Executors.
 * ExecutorService.execute
 */
public class Test1 {
    public static void main(String[] args) {
        // 平时我们创建一些类使用工具类操作 s
        // 总数可以管理

        // 线程池  Executors原生三大方法
        ExecutorService threadpool1 = Executors.newFixedThreadPool(5); // 固定大小
        ExecutorService threadpool2 = Executors.newCachedThreadPool(); //可以弹性伸缩的线程池,遇强则强
        ExecutorService threadpool3 = Executors.newSingleThreadExecutor(); // 只有一个


        try {
            // 10个线程,会显示几个线程~
            for (int i = 1; i <= 100; i++) {
                // 线程池,执行线程
                threadpool3.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" running...");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池关闭
            threadpool3.shutdown();
        }


    }
}

4)七大参数ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大核心线程池大小
long keepAliveTime,//超时了没人调用就会释放
TimeUnit unit,//超时的单位
BlockingQueue 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;
}

5)四种拒绝策略

超过最大承载:maximumPoolSize+workQueue.size()将会使用拒绝策略

// 拒绝策略说明:
// 1. AbortPolicy (默认的:队列满了,就丢弃任务抛出异常!)
// 2. CallerRunsPolicy(哪来的回哪去? 谁叫你来的,你就去哪里处理)
// 3. DiscardOldestPolicy (尝试将最早进入对立与的人任务删除,尝试加入队列)
// 4. DiscardPolicy (队列满了任务也会丢弃,不抛出异常)

5)最大线程怎么设置:
a)CPU密集型 几核,就定义为几,保持CPU使用率最高

System.out.println(Runtime.getRuntime().availableProcessors());

b)IO密集型(判断你程序中十分耗IO的线程)>15
例如:程序有15个大型任务,io十分占用资源;

四大函数式接口

函数式接口:只有一个方法的接口;
只要有函数式接口,就会有lambda表达式的简化

1)函数型接口:

package com.coding.function4;

import java.util.function.Function;

public class Demo01 {
    public static void main(String[] args) {
        // new Runnable(); ()-> {}
//
//        Function<String,Integer> function = new Function<String,Integer>() {
            @Override // 传入一个参数,返回一个结果
            public Integer apply(String o) {
                System.out.println("into");
                return 1024;
            }
//        };

        // 链式编程、流式计算、lambda表达式
        Function<String,Integer> function = s->{return s.length();};
        System.out.println(function.apply("abc"));

    }
}

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

package com.coding.function4;

import java.util.function.Predicate;

public class Demo02 {
    public static void main(String[] args) {
//        Predicate<String> predicate = new Predicate<String>(){
//            @Override
//            public boolean test(String o) {
//                if (o.equals("abc")){
//                    return true;
//                }
//                return false;
//            }
//
//        };

        Predicate<String> predicate = s->{return s.isEmpty();};
        System.out.println(predicate.test("abced"));
    }
}

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

package com.coding.function4;

import java.util.function.Consumer;

public class Demo03 {
    public static void main(String[] args) {
        // 没有返回值,只能传递参数  消费者
//        Consumer<String> consumer = new Consumer<String> () {
//            @Override
//            public void accept(String o) {
//                System.out.println(o);
//            }
//        };

        Consumer<String> consumer =s->{System.out.println(s);};
        consumer.accept("123");

        // 供给型接口  只有返回值,没有参数  生产者


    }
}

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

package com.coding.function4;

import java.util.function.Supplier;

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

        Supplier<String> supplier = ()->{return "aaa";};
        System.out.println(supplier.get());
    }
}

Stream流计算

“集合讲的是数据,流讲的是计算!”

package com.coding.stream;

import com.sun.corba.se.spi.orbutil.threadpool.WorkQueue;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;

/**
 * 一下数据,进行操作筛选用户:要求:一行代码做出此题,时长1分钟!
 * 1、全部满足偶数ID
 * 2、年龄都大于24
 * 3、用户名转为大写
 * 4、用户名字母倒排序
 * 5、只能输出一个名字
 */
public class StreamDemo {
    public static void main(String[] args) {
        User u1 = new User(11, "a", 23);
        User u2 = new User(12, "b", 24);
        User u3 = new User(13, "c", 22);
        User u4 = new User(14, "d", 28);
        User u5 = new User(16, "e", 26);

        // 集合管理数据
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        // 计算交给Stream
        // 过滤  filter
        // 映射 map
        // 排序, sort
        // 分页   limit
        list.stream()
                .filter(u->{return u.getId()%2==0;})
                .filter(u->{return u.getAge()>24;})
                .map(u->{return u.getUsername().toUpperCase();})
                .sorted((o1,o2)->{return o2.compareTo(o1);})
                .limit(1)
                .forEach(System.out::println);

        // 泛型、注解、反射
        // 链式编程 + 流式计算 + lambda表达式


        // ForkJoinPool 执行
        // ForkJoinTask
                // recursive

        // WorkQueue
    }
}

分支合并

并行执行任务的,提高效率,大数据量;
这个里面维护的是双端队列

1)ForkJoin
a)介绍
从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是讲一个大任务分割成若干小任
务,最终汇总每个小任务的结果得到这个大任务的结果

b)工作窃取

package com.coding.stream;

import java.util.concurrent.RecursiveTask;

// 吃糖,集中注意力,   计算的返回值类型
// 每一次痛苦都可以成长!
public class ForkJoinDemo extends RecursiveTask<Long> {

    private Long start;
    private Long end;
    private static final 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 middle = (end + start) / 2;
            ForkJoinDemo right = new ForkJoinDemo(start, middle);// 第一个任务
            right.fork();
            ForkJoinDemo left = new ForkJoinDemo(middle+1, end);// 第一个任务
            left.fork();

            // 合并结果
            return right.join() + left.join();
        }
    }
}

异步回调

package com.coding.stream;

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

// CompletableFuture 异步回调, 对将来的结果进行结果,ajax就是一种异步回调!
public class CompletableFutureDemo {
    public static void main(String[] args) throws Exception {
        // 多线程也可以异步回调
//
//        // 没有返回结果,任务执行完了就完毕了! 新增~
//        CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
//            // 插入数据,修改数据
//            System.out.println(Thread.currentThread().getName() + " 没有返回值!");
//        });
//
//        System.out.println(voidCompletableFuture.get());

        // 有返回结果  ajax。 成功或者失败!
        CompletableFuture<Integer> uCompletableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + " 有返回值!");
            // int i = 10/0;
            return 1024;
        });

        // 有一些任务不紧急,但是可以给时间做!占用主线程!假设这个任务需要返回结果!

        System.out.println(uCompletableFuture.whenComplete((t, u) -> { // 正常编译完成!
            System.out.println("=t==" + t); // 正常结果
            System.out.println("=u==" + u); // 信息错误!
        }).exceptionally(e -> { // 异常!
            System.out.println("getMessage=>" + e.getMessage());
            return 555; // 异常返回结果
        }).get());

    }
}

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

1)volitile 是 Java 虚拟机提供的轻量级的同步机制,三大特性:
a、保证可见性
b、不保证原子性
c、禁止指令重排

2)什么是JMM
JMM 本身是一种抽象的概念,并不真实存在,它描述的是一组规则或者规范~

3)JMM 关于同步的规定:
a、线程解锁前,必须把共享变量的值刷新回主内存
(线程操作变量的时候,其实是会拷贝主线程的内存到子线程工作内存中去,所以直接修改的是子线程的内存)
b、线程加锁前,必须读取主内存的最新值到自己的工作内存
c、加锁解锁是同一把锁

4)内存交互操作

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类
型的变量来说,load、store、read和write操作在某些平台上允许例外)
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量
才可以被其他线程锁定
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便
随后的load动作使用
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机
遇到一个需要使用到变量的值,就会使用到这个指令
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的
变量副本中
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存
中,以便后续的write使用
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内
存的变量中

JMM对这八种指令的使用,制定了如下规则:

不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须
write
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
不允许一个线程将没有assign的数据从工作内存同步回主内存
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量
实施use、store操作之前,必须经过assign和load操作
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解

如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,
必须重新load或assign操作初始化变量的值
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
对一个变量进行unlock操作之前,必须把此变量同步回主内存

Volatile

1)、保证可见性

volatile 读取的时候去主内存中读取在最新值!

package com.coding.jmm;

import java.util.concurrent.TimeUnit;

public class Test1 {
    // volatile 读取的时候去主内存中读取在最新值!
    private volatile static int num = 0;

    public static void main(String[] args) throws InterruptedException { // Main线程

        new Thread(()->{ // 线程A 一秒后会停止!  0
            while (num==0){

            }
        }).start();

        TimeUnit.SECONDS.sleep(1);

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

    }

}

2)、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败。

num++
a)获得这个值
b)+1
c)写回这个值

使用原子类:(原子类底层是CAS)
a)AtomicInteger num = new AtomicInteger();
num.getAndIncrement();//+1

package com.coding.jmm;

import java.util.concurrent.atomic.AtomicInteger;

// 遇到问题不要着急,要思考如何去做!
public class Test2 {

    private volatile static AtomicInteger num = new AtomicInteger();

    public static void  add(){
        num.getAndIncrement(); // 等价于 num++
    }

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

        // main线程等待上面执行完成,判断线程存活数   2
        while (Thread.activeCount()>2){ // main  gc
            Thread.yield();
        }

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

    }
}

3)、禁止指令重排

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

volatile可以避免指令重排:
内存屏障,CPU指令,作用:
a)保证特定的操作的执行顺序
b)可以保证某些变量的内存可见性

深入理解CAS

1)CAS : 比较并交换
java无法操作内存
java可以操作C++
C++可以操作内存

CAS:由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成的,用于完成某个功
能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU
的原子指令,不会造成所谓的数据不一致问题。

CAS(CompareAndSwap)
比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作
内存中的值一致为止。
2)CAS 应用
CAS 有3个操作数,内存值V,旧的预期值A,要修改的更新值B。且仅当预期值A 和 内存值 V 相同时,
将内存值 V 修改为B,否则什么都不做。
3)CAS 的缺点
a、循环时间长开销很大。
可以看到源码中存在 一个 do…while 操作,如果CAS失败就会一直进行尝试。
b、只能保证一个共享变量的原子操作。
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作。但是:
对多个共享变量操作时,循环CAS就无法保证操作的原子性,这时候就可以用锁来保证原子性。

Unsafe 类:java后门,可以通过这个类操作内存

package com.coding.cas;

import com.coding.demo02.A;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);

        // compareAndSet  简称 CAS 比较并交换!
        // compareAndSet(int expect, int update)  我期望原来的值是什么,如果是,就更新

        //  a
        System.out.println(atomicInteger.compareAndSet(5, 2020)+"=>"+atomicInteger.get());

        // c  偷偷的改动
        System.out.println(atomicInteger.compareAndSet(2020, 2021)+"=>"+atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2021, 5)+"=>"+atomicInteger.get());


        //  b
        System.out.println(atomicInteger.compareAndSet(5, 1024)+"=>"+atomicInteger.get());

    }
}

4)ABA问题
右边线程对变量A的操作:经过cas(1,3)改为3,在经过cas(3,1)改为1,但是左边的线程并不知道该变量被改变了

原子引用

带版本号的原子操作
AtomicStampedReference

package com.coding.cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.ReentrantLock;

/**
 * AtomicReference 原子引用
 * AtomicStampedReference 加了时间戳  类似于乐观锁! 通过版本号
 */
public class CASDemo2 {
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
        new Thread(()->{
            //1 、 获得版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("T1 stamp 01=>"+stamp);

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

            atomicStampedReference.compareAndSet(100,101,
                    atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);

            System.out.println("T1 stamp 02=>"+atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(101,100,
                    atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);

            System.out.println("T1 stamp 03=>"+atomicStampedReference.getStamp());

        },"T1").start();


        new Thread(()->{
            // GIT  看到数据被动过了!

            //1 、 获得版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("T1 stamp 01=>"+stamp);

            // 保证上面的线程先执行完毕!
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean b = atomicStampedReference.compareAndSet(100, 2019,
                    stamp, stamp + 1);
            System.out.println("T2 是否修改成功:"+b);
            System.out.println("T2 最新的stamp:"+stamp);
            System.out.println("T2 当前的最新值:"+atomicStampedReference.getReference());
        },"T2").start();



    }


}

各种锁的理解

1)公平锁和非公平锁
公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比现申请的线程
优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

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

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

2)可重入锁

可重入锁(也叫递归锁) 指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码 * 在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。


a)synchronized

package com.coding.lock;

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

public class RTLock {

    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            phone.sendSMS();
        },"T1").start();

        new Thread(()->{
            phone.sendMail();
        },"T2").start();
    }

}

class 

以上是关于JUC笔记的主要内容,如果未能解决你的问题,请参考以下文章

学习笔记 07 --- JUC集合

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口

尚硅谷JUC高并发编程学习笔记JUC简介与Lock接口

狂神JUC笔记

手写笔记23:初探JUC并发编程

JUC基础学习笔记