JUC初阶学习

JUC学习大纲

前言

hi,大家好,此篇笔记是作者通过观看狂神说JUC并且自己查阅一些资料编辑完成的JUC学习笔记,大家可以观看目录查找自己想要了解的问题,会不定时更新补充,欢迎大家阅读收藏!!

文章目录

  • JUC学习大纲
  • 前言
    • 1、回顾进程线程与并发并行
      • 1、进程与线程
      • 2、并发与并行
        • 1、线程有几个状态
        • 2、wait/sleep的区别
    • 2、Lock锁☆!!!!
      • 1、传统Synchronized
      • 2、JUC中的lock锁
    • ReentrantLock
      • 公平锁:先来先服务
      • 非公平锁:可以抢占资源(默认)
      • 3、传统Synchronized与lock锁的区别
    • 3、生产者与消费者问题
      • 1、虚假唤醒问题解决
      • 2、JUC版本的生产者与消费者问题
    • 4、8锁现象
      • 1、使用两个synchronized锁方法时,先执行哪个方法?
      • 2、使用synchronized并且调用普通方法时,是先执行发短信还是hello?
      • 3、两个对象,两个同步方法,先执行发短信还是打电话?
      • 4、使用static修饰的synchronized方法,一个对象先执行哪个方法?
      • 5、使用static修饰的synchronized方法,两个对象先执行哪个方法?
      • 6、一个方法使用static synchronized,一个使用普通synchronized,创建一个对象,调用两个方法谁先执行?
      • 7、一个方法使用static synchronized,一个使用普通synchronized,创建两个对象,分别调用两个方法谁先执行?
      • 8、两个使用static的同步方法,分别在不同的类中,如果同时调用相同的方法,谁先开始执行?
      • 9、总结
    • 5、集合类不安全
      • 1、List 不安全
      • 2、set 不安全
      • 3、Map不安全
    • 6、Callable
    • 7、常用的辅助类
      • 1、CountDownLatch 减法计数器
      • 2、CyclicBarrier 加法计数器
      • 3、Semaphore
    • 8、读写锁 ReadWriteLock
    • 9、阻塞队列
      • 1、队列的认识
      • 2、BlockingQueue阻塞队列的使用
      • 3、同步队列SynchronizedQueue
    • 10、线程池☆!!!!
      • 池化技术
      • 1、线程池的三大方法
      • 2、线程池的7大参数
      • 3、4种拒绝策略
      • 4、小结
    • 11、lambda表达式 ()->
    • 13、方法引用 ::
    • 14、四大函数式接口(必须掌握)
      • 1、什么是函数式接口
      • 2、函数型接口 Function
      • 3、断定型接口 Predicate
      • 4、消费型接口 Consumer
      • 5、供给型接口 Supplier
    • 15、stream流式计算
      • 1、Stream中间操作
        • 1、筛选与切片
        • 2、映射
        • 3、排序
      • 2、Stream的终止操作
        • 1、匹配与查找
        • 2、归约
        • 3、收集 ☆!!!!
    • 16、ForkJoin
      • 实际使用:
    • 17、异步回调
    • 18 JMM
      • 什么是JMM?
      • 内存交互操作
    • 19、Volatile
      • 1、保证可见性
      • 2、不保证原子性
      • 3、禁止指令重排
    • 20、彻底玩转单例模式
      • 1、饿汉式
      • 2、懒汉式
      • 3、单例模式的应用场景
    • 21、深入理解CAS
      • 1、什么是CAS
      • 2、unsafe类
      • 3、CAS:ABA问题(类似狸猫换太子问题)
      • 4、原子引用
    • 22、各种锁的理解
      • 1、公平锁、非公平锁
      • 2、可重入锁
      • 3、自旋锁
      • 4、死锁


1、回顾进程线程与并发并行

1、进程与线程

JUC初阶学习_第1张图片
java真的可以开启线程吗?不可以

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}
//native 本地方法,调用底层的c++来操作硬件。java无法直接操作硬件
private native void start0();

2、并发与并行

JUC初阶学习_第2张图片

//获取cpu的核数
System.out.println(Runtime.getRuntime().availableProcessors());

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

1、线程有几个状态
public enum State {
    //创建态
    NEW,
    
	//运行态
    RUNNABLE,
    
    //阻塞态
    BLOCKED,
    
    //等待态,会一直等下去
    WAITING,
    
    //超时等待,等一段时间
    TIMED_WAITING,

    //终止态
    TERMINATED;
}
2、wait/sleep的区别

1、来自不同的类

wait => Object

sleep => Thread

2、关于锁的释放

wait 会释放锁

sleep 不会释放

3、使用的范围是不同的

wait 必须在同步代码块中使用

sleep 可以在任何地方使用

2、Lock锁☆!!!!

1、传统Synchronized

//基本的卖盘例子
/*
* 公司开发中,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作!!!
* 1、属性、方法
* */
public class SaleTicketDemo01 {
    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();
    }
}

//资源类  oop编程
class Ticket{
    //属性  方法
    private int num=50;

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

2、JUC中的lock锁

是一个接口,具有三个实现类

ReentrantLock  可重入锁(最常用的)
ReadLock 读锁
WriteLock 写锁

ReentrantLock

public ReentrantLock() {
    //如果无参就为非公平锁
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    //如果为true,则为公平锁
    sync = fair ? new FairSync() : new NonfairSync();
}

公平锁:先来先服务

非公平锁:可以抢占资源(默认)

public class SaleTicketDemo02 {
    public static void main(String[] args) {
        Ticket1 ticket=new Ticket1();

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

//资源类  oop编程
class Ticket1{
    private int num=50;

    Lock lock = new ReentrantLock(true);

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

    }
}

3、传统Synchronized与lock锁的区别

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

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

3、Synchronized 会自动释放锁,lock 必须要手动释放锁!!如果不释放锁,会造成死锁问题

4、Synchronized 线程1(获得锁)的话,线程2会一直等待,如果线程1(阻塞),线程2还是会一直等待;lock锁就不一定会等待下去

lock.tryLock();

5、Synchronized 可重入锁,不可中断,非公平的;lock,可重入锁,可以判断锁,可以自己设置公平锁。

Lock lock = new ReentrantLock(true);//公平锁
Lock lock = new ReentrantLock();//非公平锁

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

3、生产者与消费者问题

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();
                }
            }
        },"生产者").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.reduce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"消费者1").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.reduce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"消费者2").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"生产者2").start();
    }
}
class Data{
    private int number=0;

    public synchronized void increment() throws InterruptedException {
        if(number != 0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        this.notifyAll();
    }

    public synchronized void reduce() throws InterruptedException {
        if(number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        this.notifyAll();
    }

}

1、虚假唤醒问题解决

上述代码A、B、C、D四个线程,会产生虚假唤醒

JUC初阶学习_第3张图片

为了解决这个问题,我们需要将if判断改为while

class Data{
    private int number=0;

    public synchronized void increment() throws InterruptedException {
        while (number != 0){
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        this.notifyAll();
    }

    public synchronized void reduce() throws InterruptedException {
        while (number == 0){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        this.notifyAll();
    }
}

2、JUC版本的生产者与消费者问题

1、synchronized与lock 对线程阻塞与唤醒的不同

synchronized: 	
	wait()//阻塞当前资源	
	notify()//随机唤醒一个资源	
	notifyAll()//唤醒所有资源
Lock:	
	await()//阻塞当前资源	
	signal()//唤醒当前资源	
	signalAll()//唤醒所有资源
public class B {
    public static void main(String[] args) {
        Data1 data=new Data1();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"生产者").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.reduce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"消费者1").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.reduce();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"消费者2").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"生产者2").start();
    }
}
class Data1{
    private int number=0;
    Lock lock=new ReentrantLock();
    Condition condition = lock.newCondition();
    public void increment() throws InterruptedException {
        try {
            lock.lock();
            while (number != 0){
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition.signalAll();
        }finally {
            lock.unlock();
        }

    }

    public void reduce() throws InterruptedException {
        try {
            lock.lock();
            while (number == 0){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

上述代码执行出的结果是随机的,如果想要让进程进行有序的执行的话可以使用Condition 进行精准的通知和唤醒线程。

具体操作

public class C {
    public static void main(String[] args) {
        //A 执行完调用B,B执行完调用C,C执行完调用A
        Data2 data=new Data2();
        new Thread(()->{
            for (int i=0;i<10;i++){
                data.printA();
            }
        },"A").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                data.printB();
            }
        },"B").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                data.printC();
            }
        },"C").start();
    }
}
class Data2{
    private int number=1;
    private Lock lock=new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    //业务:判断-> 执行-> 通知
    public void printA(){
        try {
            lock.lock();
            while (number!=1){
                //等待
                try {
                    condition1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"=>AAAA");
            //唤醒指定的人B
            number=2;
            condition2.signal();

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


}

运行结果:
JUC初阶学习_第4张图片

4、8锁现象

如何判断锁的是谁!!知道什么是锁,锁的是谁。

通过以下问题了解到什么是锁。

1、使用两个synchronized锁方法时,先执行哪个方法?

public class A {    
    /*    这个代码运行结果是 1、发短信 2、打电话    原因并不是因为因为 A先执行的,而是因为锁的存在         */    
    public static void main(String[] args) {        
    Phone phone=new Phone();//    由于两个方法用的同一个锁,所以谁先拿到谁先执行        
        new Thread(()->{ phone.senSms(); },"A").start();        
        try 
        {            
            TimeUnit.SECONDS.sleep(1);//阻塞一秒钟        
            } catch (InterruptedException e) {            
            e.printStackTrace();        
        }        
        new Thread(()->{ phone.call(); },"B").start();    
    }
}
class Phone{
    //    synchronized  锁的对象是方法的调用者    
    public synchronized void senSms(){        
        System.out.println("发短信");    
    }    
    public synchronized void call(){        
        System.out.println("打电话");    
    }
}

2、使用synchronized并且调用普通方法时,是先执行发短信还是hello?

public class A {
    /*
    这个代码运行结果是 1、hello 2、发短信
    原因:hello方法不是同步方法,不受锁的影响
     */
    public static void main(String[] args) {
        Phone phone=new Phone();
        new Thread(()->{ phone.senSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone.hello(); },"B").start();
    }
}
class Phone{
//    synchronized  锁的对象是方法的调用者
    public synchronized void senSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }

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

3、两个对象,两个同步方法,先执行发短信还是打电话?

public class A {
    /*
    这个代码运行结果是 1、打电话 2、发短信
    原因:由于两个方法用的不是同一个锁
     */
    public static void main(String[] args) {
        //两个对象,两个调用者,两把锁
        Phone phone1=new Phone();
        Phone phone2=new Phone();
        new Thread(()->{ phone1.senSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone2.call(); },"B").start();
    }
}
class Phone{
//    synchronized  锁的对象是方法的调用者
    public synchronized void senSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"发短信");
    }

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

    //没有锁,不是同步方法,不受锁的影响
    public void hello(){
        System.out.println(Thread.currentThread().getName()+"hello");
    }
}

4、使用static修饰的synchronized方法,一个对象先执行哪个方法?

public class B {    public static void main(String[] args) {        //一个对象        
    Phone1 phone1=new Phone1();        
    new Thread(()->{ phone1.senSms(); },"A").start();        
    try {            
        TimeUnit.SECONDS.sleep(1);        
    } catch (InterruptedException e) {            
        e.printStackTrace();        
    }        
    new Thread(()->{ phone1.call(); },"B").start();    }
}
class Phone1{    
    //static 类一加载就有了,锁的是Class模板    
    //因为两个方法都被static修饰了,所以使用的是同一个锁    
    public static synchronized void senSms(){
        try {
            TimeUnit.SECONDS.sleep(2);        
        } catch (InterruptedException e) { 
            e.printStackTrace();      
        }
        System.out.println(Thread.currentThread().getName()+"发短信");   
    }    
    public static synchronized void call(){      
        System.out.println(Thread.currentThread().getName()+"打电话"); 
    }
}

5、使用static修饰的synchronized方法,两个对象先执行哪个方法?

public class B {
    public static void main(String[] args) {
        //两个对象,两个调用者,两把锁,
        //因为是使用的static方法,所以使用的都是一个Class对象,所以先执行A,在B
        Phone1 phone1=new Phone1();
        Phone1 phone2=new Phone1();
        new Thread(()->{ phone1.senSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone2.call(); },"B").start();
    }
}

class Phone1{

    //static 类一加载就有了,锁的是Class模板
    //因为两个方法都被static修饰了,所以使用的是同一个锁
    public static synchronized void senSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"发短信");
    }

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

6、一个方法使用static synchronized,一个使用普通synchronized,创建一个对象,调用两个方法谁先执行?

public class C {
    public static void main(String[] args) {
        //此方法类似于创建两个对象,使用不同的锁,所以是b先运行,a最后
        Phone2 phone1=new Phone2();
        new Thread(()->{ phone1.senSms(); },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{ phone1.call(); },"B").start();
    }
}

class Phone2{
    //static 类一加载就有了,锁的是Class模板
    //一个静态synchronized锁,一个普通的synchronized锁
    public static synchronized void senSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"发短信");
    }

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

7、一个方法使用static synchronized,一个使用普通synchronized,创建两个对象,分别调用两个方法谁先执行?

public class C {    
    public static void main(String[] args) {        
        //因为是使用的static方法,所以使用的都是一个Class对象        
        //而因为call方法没有使用static,所以锁所属对象不是Class,所以B先执行   
        Phone2 phone1=new Phone2();   
        Phone2 phone2=new Phone2();    
        new Thread(()->{ phone1.senSms(); },"A").start();   
        try {       
            TimeUnit.SECONDS.sleep(1);     
        } catch (InterruptedException e) {   
            e.printStackTrace();    
        }       
        new Thread(()->{ phone2.call(); },"B").start();    
    }
}
class Phone2{
    //static 类一加载就有了,锁的是Class模板 
    //一个静态synchronized锁,一个普通的synchronized锁
    public static synchronized void senSms(){ 
        try {
            TimeUnit.SECONDS.sleep(2);   
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println(Thread.currentThread().getName()+"发短信");  
    }
    public synchronized void call(){ 
        System.out.println(Thread.currentThread().getName()+"打电话");   
    }
}

8、两个使用static的同步方法,分别在不同的类中,如果同时调用相同的方法,谁先开始执行?

public class C {    
    public static void main(String[] args) {        
        //因为是使用的static方法,所以使用的都是一个Class对象,所以先执行A,在B   
        Phone2 phone1=new Phone2(); 
        Phone3 phone2=new Phone3();   
        new Thread(()->{ phone1.call(); },"A").start(); 
        try {      
            TimeUnit.SECONDS.sleep(1);   
        } catch (InterruptedException e) { 
            e.printStackTrace();      
        }     
        new Thread(()->{ phone2.call(); },"B").start();   
    }
}
class Phone2{ 
    public synchronized void call(){     
        try {    
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {    
            e.printStackTrace();    
        }  
        System.out.println(Thread.currentThread().getName()+"打电话");
    }
}
class Phone3{  
    public static synchronized void call(){      
        System.out.println(Thread.currentThread().getName()+"打电话");   
    }
}

9、总结

new this 具体的一个类,也就是说你创建多个对象的话,每个对象的锁互不相关,对象在调用同步方法时,并不会阻塞其他对象调用其他方法。

static Class 唯一的一个模板,如果在同步方法上添加static字段,这个静态同步方法在被调用时,会自己通过反射调用一个Class模板,生成一个当前类static同步方法通用的锁。

5、集合类不安全

1、List 不安全

public class List1 {
    //java.util.ConcurrentModificationException 并发修改异常!!
    //当我们直接使用ArrayList进行并发操作时,会产生并发修改异常。
    public static void main(String[] args) {
        //在并发下,ArrayList是不安全的
        List<Object> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

为了解决 ArrayList 的线程不安全问题,我们有以下解决方法:

public class List1 {
        //java.util.ConcurrentModificationException 并发修改异常!!
    public static void main(String[] args) {
        //在并发下,ArrayList是不安全的
        /*
        * 解决方案:
        * 1、使用Vector 去解决并发安全问题
        * List list = new Vector<>();
        * 2、通过使用Collections工具类,使ArrayList变的安全
        * List list = Collections.synchronizedList(new ArrayList<>());
        * 3、使用JUC包下的CopyOnWriteArrayList<>();
        * List list = new CopyOnWriteArrayList<>();
        * CopyOnWrite 写入时复刻  COW 计算机程序设计领域的一种优化
        * 原理:
        * 多个线程调用的时候,list在读取的时候是固定的,在写入的时候会覆盖
        * 而COW在写入的时候避免覆盖造成数据问题,在写入的时候复制一份,
        * 复制后给调用者,调用者写完之后再给数据放回去
        *
        * 为什么不使用vector而使用CopyOnWriteArrayList?
        * 1、vector 使用的是synchronized方法,而使用synchronized方法效率很低
        * 2、CopyOnWriteArrayList使用的是lock锁
        *
        * */
//        List list = new Vector<>();
//        List list = new ArrayList<>();
//        List list = Collections.synchronizedList(new ArrayList<>());
        List<Object> list = new CopyOnWriteArrayList<>();

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

2、set 不安全

public class Set1 {
    //ConcurrentModificationException 并发修改异常!!
    public static void main(String[] args) {
        Set<String> set=new HashSet<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

为了使set变为安全的,我们可以使用以下方法:

public class Set1 {
    //ConcurrentModificationException 并发修改异常!!
    public static void main(String[] args) {
//        Set set=new HashSet<>();
//        Set set= Collections.synchronizedSet(new HashSet<>());
//        Set set=new CopyOnWriteArraySet<>();
        //方法原理与list相同
        Set<String> set=new CopyOnWriteArraySet<>();



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

3、Map不安全

public class Map1 {
    //ConcurrentModificationException 并发修改异常!!
    public static void main(String[] args) {

        //map 是这样用吗? 不是,工作中不使用HashMap
        //默认等价于什么?
//        Map map = new HashMap<>(16,0.75f);//加载因子,初始化容量
        Map<Object, Object> map = new HashMap<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

为了使Map变的更安全,我们需要使用以下方法。

public class Map1 {
    //ConcurrentModificationException 并发修改异常!!
    public static void main(String[] args) {

        //map 是这样用吗? 不是,工作中不使用HashMap
        //默认等价于什么?
//        Map map = new HashMap<>(16,0.75f);//加载因子,初始化容量
//        Map map = new HashMap<>();
//		  方法:
//        Collections.synchronizedMap(new HashMap<>());
//        new ConcurrentHashMap<>();//使用并发的hashmap
        Map<Object, Object> map = new ConcurrentHashMap<>();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

6、Callable

JUC初阶学习_第5张图片

1、可以抛出异常

2、可以有返回值

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

public class Callable1 {
    public static void main(String[] args) {
//        new Thread(new Runnable()).start();
//        new Thread(new FutureTask()).start();
//        new Thread(new FutureTask(Callable)).start();
        MyTh myTh=new MyTh();
        FutureTask futureTask=new FutureTask(myTh);//适配类
        new Thread(futureTask,"A").start();
        try {
            Integer o = (Integer) futureTask.get();//获取callable的call方法的返回值
                    //这个get方法会产生阻塞!!!,把他放到最后,或使用异步通信解决
            System.out.println(o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}
class MyTh implements Callable<Integer>{
    //这里继承什么属性,call方法的返回值就必须是什么属性
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"call");
        return 121;
    }
}

如果创建多个多线程同时调用同一个callable

public class Callable1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        new Thread(new Runnable()).start();
//        new Thread(new FutureTask()).start();
//        new Thread(new FutureTask(Callable)).start();
        MyTh myTh=new MyTh();
        FutureTask<Integer> futureTask=new FutureTask<>(myTh);
        FutureTask<Integer> task[] = new FutureTask[10];
        for (int i = 0; i < 10; i++) {
            task[i]=new FutureTask<Integer>(myTh);
            new Thread(task[i],String.valueOf(i)).start();
        }
//        new Thread(futureTask,"A").start();
//        new Thread(futureTask,"B").start();//结果会被缓存提高效率
        int num = 0;
        while(num<10){
            try {
                System.out.println(task[num].get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            num++;
        }
        //这个get方法会产生阻塞!!!,把他放到最后


    }
}
class MyTh implements Callable<Integer>{
    //这里继承什么属性,call方法的返回值就必须是什么属性
    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"call");
        return 121;
    }
}

main线程里,有三个线程,三个线程独立,task.get() 只有等待对应线程完成后才会执行、所以最后可以打印出10个结果。而且,效率明显比串行执行10次循环效率高。

7、常用的辅助类

1、CountDownLatch 减法计数器

闭锁,减法计数器

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //总数是6,必须要去执行任务的时候,再使用
        CountDownLatch countDownLatch=new CountDownLatch(6);
        for (int i = 1; i < 7; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"go");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }

        countDownLatch.await();//等待计数器归零,然后再向下执行
//        countDownLatch.countDown();//-1
        System.out.println("关门");
    }
}

原理:

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

countDownLatch.await();//等待计数器归零,然后再执行下面的代码

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

2、CyclicBarrier 加法计数器

java中关于线程的计数器,加法计数器

public class CyclicBarrierDemo {
    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+"个线程");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

    }
}

上述CyclicBarrier需要产生出7个线程后才会执行其中runnable的方法,如果设置为8个,如果只运行了7个线程,则会一直陷入阻塞状态,等待线程达到8个才会运行runnable的方法。

3、Semaphore

信号量

public class SemaphoreDemo {
    public static void main(String[] args) {
        //参数: 默认线程数量-->能有的线程数量
        //在限流的时候可以使用,每次访问的数量只能有这么多,不能超过,类似停车位。
        Semaphore semaphore=new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"得到了线程位置");
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//释放资源
                }
                // acquire()   得到
                // release()   释放

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

原理:

semaphore.acquire();//拿到通行证/信号量,如果信号量已经被拿空了,则等待,直到释放其他线程信号量

semaphore.release();//释放通行证/信号量

作用:多个共享资源互斥的使用!并发限流,控制最大的线程数。保证服务器的安全与高可用

8、读写锁 ReadWriteLock

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

/*
* 独占锁(写锁)  一次只能被一个线程占有
* 共享锁(读锁)  多个线程可以同时占有
* */
public class ReadWriteLockDemo {

    public static void main(String[] args) {
        MyCache2 myCache = new MyCache2();
        for (int i = 0; i < 5; i++) {
            final int temp=i;
            new Thread(()->{
                myCache.put(temp+"",Thread.currentThread().getName());
            },String.valueOf(i)).start();
        }
        for (int i = 0; i < 5; 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 lock=new ReentrantReadWriteLock();

    //存,在写入时,只希望同时只有一个线程可以写
    public void put(String key,Object o){
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key,o);
            System.out.println(Thread.currentThread().getName()+"写入完毕");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
    //取,在读取时,所有人都可以读
    public void get(String key){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取完毕");
        } catch (Exception exception) {
            exception.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }

    }
}

JUC初阶学习_第6张图片

结果:在写入时,并没有任何插队,而在读取时,有插队现象

JUC初阶学习_第7张图片

9、阻塞队列

1、队列的认识

JUC初阶学习_第8张图片

队列采用先进先出的方式存取数据

阻塞队列:BlockingQueue

JUC初阶学习_第9张图片

JUC初阶学习_第10张图片

阻塞队列与List、Set是同级关系

JUC初阶学习_第11张图片

什么时候我们需要使用阻塞队列?

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

2、BlockingQueue阻塞队列的使用

学会使用队列

添加、移除

四组API

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

方式一:抛出异常

public class BlockingQueueDemo {
    public static void main(String[] args) {
        /*
        Collection:
            List
            Set
            BlockingQueue
        */
        test1();
    }
    /*
    抛出异常
    */
    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"));
        // 队列满的时候再添加会抛出异常: Queue full
//        System.out.println(arrayBlockingQueue.add("d"));

        //element():返回并查看队首的元素
        System.out.println(arrayBlockingQueue.element());

        //当队列为空时,再弹出队列内容,会报错: NoSuchElementException
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
//        System.out.println(arrayBlockingQueue.remove());

        //如果队列元素为空,则出现异常:NoSuchElementException
        System.out.println(arrayBlockingQueue.element());

    }
}

方式二:有返回值,不抛出异常

public class BlockingQueueDemo {
    public static void main(String[] args) {
        test2();
    }
    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"));
        //如果队列满的时候再添加,会返回false并且添加失败
//        System.out.println(arrayBlockingQueue.offer("d"));

        //peek():返回并查看队首的元素
        System.out.println(arrayBlockingQueue.peek());


        //poll方法:空参为按队列从头部依次弹出
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        //如果队列空的时候再取出返回null,不会报错
        System.out.println(arrayBlockingQueue.poll());
        //如果队列空的时候再查看队首返回null,不会报错
        System.out.println(arrayBlockingQueue.peek());

    }
}

方式三:阻塞等待

/*
    * 等待,阻塞(一直等待)
    * */
    public static void test3() throws InterruptedException {
        //队列需要参数,参数是当前队列的大小
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
        arrayBlockingQueue.put("a");
        arrayBlockingQueue.put("b");
        arrayBlockingQueue.put("c");
        //队列没有位置了,就会一直阻塞
//        arrayBlockingQueue.put("d");

        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 arrayBlockingQueue = new ArrayBlockingQueue<>(3);
    System.out.println(arrayBlockingQueue.offer("a"));
    System.out.println(arrayBlockingQueue.offer("b"));
    System.out.println(arrayBlockingQueue.offer("c"));
    //阻塞2秒钟之后结束
    System.out.println(arrayBlockingQueue.offer("d", 2, TimeUnit.SECONDS));

    System.out.println(arrayBlockingQueue.poll());
    System.out.println(arrayBlockingQueue.poll());
    System.out.println(arrayBlockingQueue.poll());
    //等待2秒后结束
    System.out.println(arrayBlockingQueue.poll(2, TimeUnit.SECONDS));
}

3、同步队列SynchronizedQueue

同步队列没有容量

进去一个元素,必须等待取出来之后,才能再往里面放一个元素!!

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

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName()+"put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName()+"put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+": "+blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+": "+blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+": "+blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

输出结果:

T1put 1
T2: 1
T1put 2
T2: 2
T1put 3
T2: 3

10、线程池☆!!!!

池化技术

程序的运行,本质:占用系统的资源!优化CPU资源的使用!

线程池、连接池、内存池、对象池… 创建、销毁线程池十分浪费资源

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

线程池的好处:

1、降低资源的消耗

2、提高响应的速度

3、方便管理

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

线程池具有:三大方法、7大参数、4种拒绝策略

1、线程池的三大方法

JUC初阶学习_第12张图片

Executors.newSingleThreadExecutor();//单个线程
Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
Executors.newCachedThreadPool();//可伸缩的
//Executors 工具类,有三大方法
//使用线程池之后,要使用线程池来创建线程
public class Demo1 {
    public static void main(String[] args) {
//        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
        ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱
        try {
            for (int i = 0; i < 10; i++) {
                //使用线程池之后,要使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" OK");
                });
            }
        } finally {
            //线程池用完需要关闭线程池
            threadPool.shutdown();
        }
    }
}

2、线程池的7大参数

public static ExecutorService newSingleThreadExecutor() {
            return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(1, 1,
                                        0L, TimeUnit.MILLISECONDS,
                                        new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newFixedThreadPool(int nThreads) {
            return new ThreadPoolExecutor(nThreads, nThreads,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                          60L, TimeUnit.SECONDS,
                                          new SynchronousQueue<Runnable>());
    }
//本质:ThreadPoolExecutor() :7大参数
	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;
    }     

银行实例演示线程池:

JUC初阶学习_第13张图片

手动创建一个线程池

public class Demo1 {
    public static void main(String[] args) {
        ExecutorService threadPool =new ThreadPoolExecutor(
                2,//核心线程池大小
                5,//最大线程池大小
                3,//存活的时间,超时了没有人调用就会释放
                TimeUnit.SECONDS,//超时的单位
                new LinkedBlockingDeque<>(3),//阻塞队列,3个队列
                Executors.defaultThreadFactory(),//线程工程:创建线程的,一般不用动
                new ThreadPoolExecutor.AbortPolicy());
        //默认的拒绝策略: 队列满了,如果还有线程进入,则抛出异常
        try {
            //最大承载Deque+max,如果超出最大承载,会报异常
            for (int i = 0; i < 8; i++) {
                //使用线程池之后,要使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" OK");
                });
            }
        } finally {
            //线程池用完需要关闭线程池
            threadPool.shutdown();
        }
    }
}

3、4种拒绝策略

JUC初阶学习_第14张图片

new ThreadPoolExecutor.AbortPolicy(): 默认的拒绝策略,队列满了,如果还有线程进入,则抛出异常
new ThreadPoolExecutor.CallerRunsPolicy():哪来的回哪去  会让main线程执行
new ThreadPoolExecutor.DiscardPolicy():队列满了,如果还有线程进入,会丢掉任务,不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy():队列满了,会尝试竞争第一个线程,看是否还在运行,如果失败了就没了,如果成功就执行,也不会抛出异常。   

4、小结

任务的分类:

池的最大的大小如何去设置
任务的分类:
1、CPU 密集型  
既然名字里带有CPU了,说明其消耗的主要资源就是 CPU 了。
电脑是8核的,最多支持8条线程同时执行,所以我们在定义线程池参数时,定义maximumPoolSize时,我们有几核,最多也就能定义几个。
几核就选几这样效率最高
设置线程数时,针对单台机器,最好就是有几个 CPU ,就创建几个线程,然后每个线程都在执行这种任务,永不停歇。
Runtime.getRuntime().availableProcessors();//获取核数
2、IO 密集型
既然名字里带有IO了,说明其消耗的主要资源就是 IO 了。
判断你的程序中十分耗IO的线程的数量,我们需要设置的数量必须大于消耗资源的数量
我们所接触到的 IO ,大致可以分成两种:磁盘 IO和网络 IO。
磁盘 IO ,大多都是一些针对磁盘的读写操作,最常见的就是文件的读写,假如你的数据库、 Redis 也是在本地的话,那么这个也属于磁盘 IO。
网络 IO ,这个应该是大家更加熟悉的,我们会遇到各种网络请求,比如 http 请求、远程数据库读写、远程 Redis 读写等等。
IO 操作的特点就是需要等待,我们请求一些数据,由对方将数据写入缓冲区,在这段时间中,需要读取数据的线程根本无事可做,因此可以把 CPU 时间片让出去,直到缓冲区写满。

11、lambda表达式 ()->

JUC初阶学习_第15张图片
在这里插入图片描述
JUC初阶学习_第16张图片

JUC初阶学习_第17张图片

lambda表达式的使用举例:

/*
    Lambda表达式的使用

    1.举例:(o1,o2) -> Integer.compare(o1,o2);
    2.格式:
          -> : Lambda操作符 或 箭头操作符
          ->左边:Lambda形参列表 (其实就是接口中的抽象方法中的形参列表)
          ->右边:Lambda体 (其实就是重写的抽象方法的方法体)

   3.Lambda表达式的使用:(分为六种情况)
        总结:
        ->左边:Lambda形参列表的参数类型可以省略(类型推断);
               如果Lambda形参列表只有一个参数,其一对()也可以省略
        ->右边:Lambda体应该使用一对{}包裹;
                如果Lambda体只有一条执行语句(可能是return语句),可以省略这一对{}和 return关键字
   4.Lambda表达式的本质:作为函数式接口的实现
   5.如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口
      接口上面声明注解@FunctionalInterface

 */


public void test1(){
    //使用lambda表达式之前
    Runnable r1=new Runnable() {
        @Override
        public void run() {
            System.out.println("我爱北京");
        }
    };
    r1.run();
    
    System.out.println("**********************************");
    //使用lambda表达式之后
    Runnable r2=() -> {System.out.println("我爱故宫");}
    //使用lambda表达式之后
    Runnable r2=() -> System.out.println("我爱故宫");
    r2.run();

}

13、方法引用 ::

JUC初阶学习_第18张图片

/*
    方法引用的使用

    1.使用情景:当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

    2.方法引用,本质上就是Lambd表达式,而Lambda表达式作为函数式接口的实例。
        使用方法引用,也是函数式接口的实例

    3.使用格式: 类(或对象)  ::  方法名

    4.具体分为如下三种情况:
        对象 :: 非静态方法
        类 ::  静态方法
        类 ::  非静态方法

    5.方法引用使用的要求:要求接口中的抽象方法的形参列表和返回值类型与
        方法引用的方法的形参列表和返回值类型相同!(针对于情况1与情况2)


 */

情况一:对象::实例方法名

//情况一:对象::实例方法名
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1(){
    Consumer<String> con=(s)-> System.out.println(s);
    con.accept("北京");

    System.out.println("*************************");
    PrintStream ps = System.out;
    Consumer<String> con1=ps :: println;
    con1.accept("beijing");
}
//Supplier中的T get()
//Employee中的String getName()
@Test
public void test2(){
    Supplier<String> sup=new Supplier<String>() {
        @Override
        public String get() {
            return null;
        }
    };

    Employee emp=new Employee(1001,"Tom",23,5600);
    Supplier<String> sup1=() -> emp.getName();
    String s = sup1.get();
    System.out.println(s);

    Supplier<String> sup2= emp::getName;
    String s1 = sup2.get();
    System.out.println(s1);

}

情况二:类 :: 静态方法:

//情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3(){
    Comparator comparator=new Comparator() {
        @Override
        public int compare(Object o1, Object o2) {
            return 0;
        }
    };

    Comparator<Integer> com1=(t1,t2) -> Integer.compare(t1,t2);
    System.out.println(com1.compare(12, 21));

    Comparator<Integer> com2=Integer::compare;
    System.out.println(com2.compare(21, 2));
}
//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test4(){
    Function<Double,Long> function=new Function<Double, Long>() {
        @Override
        public Long apply(Double o) {
            return Math.round(o);
        }
    };
    System.out.println(function.apply(12.1));


    Function<Double,Long> f1=d ->Math.round(d);
    System.out.println(f1.apply(13.9));

    Function<Double,Long> f2=Math::round;
    System.out.println(f2.apply(13.2));
}

情况三:类 :: 实例方法:

//情况三:类 :: 实例方法
//Comparator中的int compare(T t1,T t2)
//String中的int t1.compareTo(t2)
@Test
public void test5(){
    Comparator<String> comparator=new Comparator<String>() {
        @Override
        public int compare(String o1, String o2) {
            return o1.compareTo(o2);
        }
    };

    Comparator<String> com1=(s1,s2) -> s1.compareTo(s2);
    System.out.println(com1.compare("abc", "abd"));

    Comparator<String> com2=String::compareTo;
    System.out.println(com2.compare("acs", "sadasd"));
}

//BiPredicate中的boolean test(T t1,T t2);
//String中的boolean t1.equals(t2)
@Test
public void test6(){
    BiPredicate biPredicate=new BiPredicate() {
        @Override
        public boolean test(Object o, Object o2) {
            return o.equals(o2);
        }
    };
    System.out.println(biPredicate.test("s", "s"));

    BiPredicate b1=(o1,o2)-> o1.equals(o2);
    System.out.println(b1.test("a", "c"));

    BiPredicate b2=Object::equals;
    System.out.println(b2.test("a", "a"));
}
//Functiono中的R apply(T t)
//Employee中的String getName();
@Test
public void test7(){
    Employee employee=new Employee(123,"Tom",12,123);
    Function<Employee,String> function=new Function<Employee, String>() {
        @Override
        public String apply(Employee employee) {
            return employee.getName();
        }
    };

    Function<Employee,String> f1=s -> s.getName();
    System.out.println(f1.apply(employee));

    Function<Employee,String> f2=Employee::getName;
    System.out.println(f2.apply(employee));
}

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

1、什么是函数式接口

我们需要掌握的技术:lambda表达式、链式编程、函数式接口、Stream流式计算

只有一个方法的接口被称为函数式接口@FunctionalInterface

//java具有超级多的@FunctionalInterface应用
//简化编程模型,在新版本的框架底层大量应用!!!
//例如:foreach(消费者类型的函数式接口为参数);
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

JUC初阶学习_第19张图片

JUC初阶学习_第20张图片

JUC初阶学习_第21张图片
JUC初阶学习_第22张图片

/*
    java内置的4大核心函数式接口

    消费型接口 Consumer    void accept(T t)
    供给型接口 Supplier    T get()
    函数型接口 Function  R apply(T t)
    断定型接口 Predicate   boolean test(T t)

 */
@Test
public void test1(){
    happyTime(500, new Consumer<Double>() {
        @Override
        public void accept(Double aDouble) {
            System.out.println("学习太累了,去天上人间消费了"+aDouble);
        }
    });

    happyTime(500, money -> System.out.println("学习太累了,去天上人间喝了口水消费了"+money));
}

public void happyTime(double money, Consumer<Double> con){
    con.accept(money);

}
@Test
public void test2(){
    List<String> list= Arrays.asList("北京","天津","南京","河南");
    List<String> list1 = filterString(list, new Predicate<String>() {
        @Override
        public boolean test(String s) {
            return s.contains("京");
        }
    });
    System.out.println(list1);

    List<String> list2 = filterString(list, s -> s.contains("京"));
    System.out.println(list2);
}
//根据给定的规则,过滤集合中的字符串。此规则由Predicate的方法决定
public List<String> filterString(List<String> list, Predicate<String> pre){
    ArrayList<String> filterList=new ArrayList<>();
    for(String s:list){
        if(pre.test(s)){
            filterList.add(s);
        }
    }
    return filterList;

}

2、函数型接口 Function

/*
* Function 函数型接口,有一个输入参数,有一个输出
* 只要是 函数式接口 可以用lambda表达式简化
* */
public class Demo1 {
    public static void main(String[] args) {
        Function<String,Object> function1= new Function<String,Object>() {
            @Override
            public Object apply(String o) {
                return o;
            }
        };
        System.out.println(function1.apply("hhh"));
        //使用lambda表达式简化
        Function function2 = (str)->{
            return str;
        };
        //使用lambda表达式简化
        Function function3 = str-> str;
        
    }
}

3、断定型接口 Predicate

/*
* 断定型接口:有一个输入参数,返回值只能是 布尔值!!
* 只要是 函数式接口 可以用lambda表达式简化
*/
public class Demo2 {
    public static void main(String[] args) {
        Predicate<String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String str) {
                return str.isEmpty();
            }
        };
        System.out.println(predicate.test("asd"));
		//使用lambda表达式简化
        Predicate<String> predicate1 = (str)->{
          return str.isEmpty();
        };
		//使用lambda表达式简化
        Predicate<String> predicate2 = str-> str.isEmpty();
    }
}

4、消费型接口 Consumer

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

        //使用lambda表达式简化
        Consumer<String> consumer2 =(str)->{
            System.out.println(str);
        };
        
		//使用lambda表达式简化
        Consumer<String> consumer3 =str-> System.out.println(str);
    }
}

5、供给型接口 Supplier

/*
* 供给型接口  没有参数,只有返回值
* */
public class Demo4 {
    public static void main(String[] args) {
        Supplier<String> supplier1 = new Supplier<String>() {
            @Override
            public String get() {
                return "hhh";
            }
        };
        System.out.println(supplier1.get());
        //使用lambda表达式
        Supplier<String> supplier2 =()->{
            return "hehe";
        };
    }
}

15、stream流式计算

JUC初阶学习_第23张图片

JUC初阶学习_第24张图片

JUC初阶学习_第25张图片

JUC初阶学习_第26张图片

/*
    1.Stream关注的是对数据的运算,与CPU打交道
      集合关注的是数据的存储,是与内存打交道的

    2.
    stream自己不会存储元素。
    stream不会改变源对象。相反,他们会返回一个持有结果的新Stream。
    Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

    3.Stream 执行流程
    ① Stream的实例化
    ② 一系列操作(过滤,映射、...)
    ③ 终止操作

    4.说明:
    4.1 一个中间操作链,对数据源的数据进行处理
    4.2 一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
 */

stream使用案例

/*
* 现在有五个用户!筛选:
* 1、ID 必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写字母
* 4、用户名字母倒着排序
* 5、只输入一个用户
* */
public class Demo1 {
    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);
        User u6=new User(6,"f",26);
        //集合就是存储
        List<User> users = Arrays.asList(u1, u2, u3, u4, u5,u6);
        //计算交给stream流
        //使用技术:lambda表达式、链式编程、函数式接口、stream流式计算
        users.stream().filter(user -> {return user.getId()%2==0;})
            	//返回由与此给定谓词匹配的此流的元素组成的流。
                .filter(user-> user.getAge()>23)
            	//map返回由给定函数应用于此流的元素的结果组成的流。 
                .map(user->{return user.getName().toUpperCase(Locale.ROOT);})
                .sorted((user1,user2)->{return user2.compareTo(user1);})
				//返回由此流的元素组成的流,截短长度不能超过 maxSize 。 
                .limit(1)
                .forEach(System.out::println);
    }
}

1、Stream中间操作

JUC初阶学习_第27张图片

JUC初阶学习_第28张图片

JUC初阶学习_第29张图片

1、筛选与切片
@Test
    public void test1(){
        List<Employee> list=EmployeeData.getEmployees();
//        filter(Predicate p):接收Lambda ,从流中排除某些元素
        Stream<Employee> stream = list.stream();
        System.out.println();

        //练习:查询员工表中薪资大于4000的员工信息
        stream.filter(e -> e.getSalary() >4000).forEach(System.out::println);
        System.out.println();
//        limit(long maxsize):截断流,使其元素不超过给定数量
        list.stream().limit(3).forEach(System.out::println);
        System.out.println();


//        skip(long n):跳过元素,返回—个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。
//                      与limit(n)互补
        System.out.println();
        list.stream().skip(3).forEach(System.out::println);

        //        distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素
        System.out.println();

        list.add(new Employee(1010,"刘强东",40,8000));
        list.add(new Employee(1010,"刘强东",40,8000));
        list.add(new Employee(1010,"刘强东",40,8000));
        list.stream().distinct().forEach(System.out::println);
    }
2、映射
@Test
public void test2(){
    //map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
    List<String> list = Arrays.asList("aa", "bb", "cc", "dd");
    list.stream().map(str -> str.toUpperCase()).forEach(System.out::println);
    System.out.println();
    //练习:获取员工姓名长度大于3的员工的姓名
    List<Employee> employees = EmployeeData.getEmployees();
    Stream<String> stringStream = employees.stream().map(Employee::getName);
    stringStream.filter(name->name.length()>3).forEach(System.out::println);
    System.out.println();


    //练习2:
    Stream<Stream<Character>> streamStream = list.stream().map(StreamAPITest1::fromStringToStream);
    streamStream.forEach(s ->{
        s.forEach(System.out::println);
    });

    System.out.println();

    //flatMap(Function f)
    //接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
    Stream<Character> characterStream = list.stream().flatMap(StreamAPITest1::fromStringToStream);
    characterStream.forEach(System.out::println);
    
}

//将字符串中的多个字符构成的集合转换成为对应的stream的实例
public static Stream<Character> fromStringToStream(String str){
    ArrayList<Character> list=new ArrayList<>();
    for(Character c:str.toCharArray()){
        list.add(c);
    }
    return list.stream();
}
3、排序
//排序
    @Test
    public void test4(){
        //sorted()---自然排序
        List<Integer> list = Arrays.asList(12, 13, 45, 6454, 21, 42);
        list.stream().sorted().forEach(System.out::println);

        //抛异常,原因:Employee没有实现Comparable接口
//        List employees = EmployeeData.getEmployees();
//        employees.stream().sorted().forEach(System.out::println);

        //sorted(Comparatoe com)--定制排序
        List<Employee> employees = EmployeeData.getEmployees();
        employees.stream().sorted((e1,e2)-> {
            int value= Integer.compare(e1.getAge(),e2.getAge());
            if(value!=0){
                return value;
            }else {
                return Double.compare(e1.getSalary(),e2.getSalary());
            }
                }
         ).forEach(System.out::println);
    }

2、Stream的终止操作

JUC初阶学习_第30张图片

JUC初阶学习_第31张图片

JUC初阶学习_第32张图片

JUC初阶学习_第33张图片

JUC初阶学习_第34张图片

JUC初阶学习_第35张图片

1、匹配与查找
//匹配与查找
@Test
public void test1(){
    List<Employee> employees = EmployeeData.getEmployees();
    // allMatch(Predicate p)——检查是否匹配所有元素。
    //练习:是否所有员工的年龄都大于18
    boolean b = employees.stream().allMatch(e -> e.getAge() > 18);
    System.out.println(b);

    //anyMatch(Predicate P)——检查是否至少匹配一个元素。
    // 练习:是否存在员工的工资大于6000
    boolean b1 = employees.stream().anyMatch(e -> e.getSalary() > 6000);
    System.out.println(b1);


    //noneMatch(Predicate p)——检查是否没有匹配的元素。
    // 练习:是否存在员工姓“叶”
    boolean b2 = employees.stream().noneMatch(e -> e.getName().startsWith("叶"));
    System.out.println(b2);


    //findFirst——返回第一个元素
    Optional<Employee> first = employees.stream().findFirst();
    System.out.println(first);


    //findAny——返回当前流中的任意元素
    Optional<Employee> any = employees.parallelStream().findAny();
    System.out.println(any);

    //count——返回流中元素的总个数
    long count = employees.stream().filter(e -> e.getSalary()>5000).count();
    System.out.println(count);

}

@Test
public void test2(){
    List<Employee> employees = EmployeeData.getEmployees();

    //max(Comparator c)——返回流中的最大值
    //练习:返回最高的工资
    Stream<Double> salaryStream = employees.stream().map(e -> e.getSalary());
    Optional<Double> max = salaryStream.max(Double::compare);
    System.out.println(max);

    //min(Comparator c)——返回流中的最小值
    //练习:返回最低的工资
    Stream<Double> salaryStream1 = employees.stream().map(e -> e.getSalary());
    Optional<Double> min = salaryStream1.min(Double::compare);
    System.out.println(min);

    Optional<Employee> min1 = employees.stream().min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
    System.out.println(min1);


    //forEach(Consumer c)——内部迭代
    employees.stream().forEach(System.out::println);
    
    //使用集合的遍历操作
    employees.forEach(System.out::println);
}
2、归约
//归约
    @Test
    public void test3(){
        //reduce(T iden, BinaryOperator b)
        //可以将流中元素反复结合起来,得到一个值。返回T
        //练习1:计算1-10的自然数的和
        List<Integer> list= Arrays.asList(1,2,3,4,5,6,7,8);
        Integer reduce = list.stream().reduce(0, Integer::sum);
        System.out.println(reduce);


        //reduce(BinaryOperator b)
        //可以将流中元素反复结合起来,得到一个值。返回OptionalT>
        //练习2:计算公司所有员工工资的总和
        List<Employee> employees = EmployeeData.getEmployees();
        Stream<Double> salaryStream = employees.stream().map(Employee::getSalary);
//        Optional reduce1 = salaryStream.reduce(Double::sum);
        Optional<Double> reduce1 = salaryStream.reduce((d1,d2)->d1+d2);
        System.out.println(reduce1.get());
    }
3、收集 ☆!!!!
//收集
    @Test
    public void test4(){
        //collect(Collector c)
        //将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法
//        Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map)。
//        另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
        //练习1:查找工资大于6000的员工,结果返回为一个List或Set
        List<Employee> employees = EmployeeData.getEmployees();
        List<Employee> list = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toList());

        list.forEach(System.out::println);
        System.out.println();
        List<Employee> employees1 = EmployeeData.getEmployees();
        Set<Employee> collect = employees.stream().filter(e -> e.getSalary() > 6000).collect(Collectors.toSet());
        collect.forEach(System.out::println);

    }

16、ForkJoin

Fork Join 在JDK1.7,并行执行任务!提高效率。大量数据!

大数据:Map Reduce (把大任务拆分成小任务)

JUC初阶学习_第36张图片

Fork Join特点:工作窃取

JUC初阶学习_第37张图片

在b线程执行完之后,会将a线程中的任务拿过来执行,提高工作效率!

JUC初阶学习_第38张图片

实际使用:

public class Test1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//        test1();//Sum=500000000500000000 时间:5879
//        test2();//Sum= 500000000500000000  时间:4190
        test3();//Sum= 500000000500000000  时间:201

    }

    //普通求和方法
    public static void test1(){
        long start = System.currentTimeMillis();
        Long sum=0L;
        for (Long i = 1L; i <= 10_0000_0000L; 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 ForkJoinDemo1(1L,10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
        Long sum = submit.get();

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

    //使用stream并行流,最推荐使用的
    public static void test3(){
        long start = System.currentTimeMillis();

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

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

ForkJoin锁创建的类

/*
* 求和计算的任务
*
* 数据量大的话可以使用ForkJoin/Stream并行流
*
* 如何使用ForkJoin
* 1、ForkJoin通过ForkJoinPool来执行
* 2、计算任务通过 ForkJoinPool.execute(ForkJoinTask task)
* 3、计算类要继承RecursiveTask<?>
*
* */
public class ForkJoinDemo1 extends RecursiveTask<Long> {
    private Long start;
    private Long end;

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

    public ForkJoinDemo1(Long start, Long end) {
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        Long sum=0L;
        if((end-start)<temp){
            for (Long i = start; i <= end; i++) {
                sum+=i;
            }
        }else {
            //分支合并计算
            Long middle = (end + start) / 2;
            ForkJoinDemo1 task1 = new ForkJoinDemo1(start, middle);
            task1.fork();//拆分任务,把任务压入队列
            ForkJoinDemo1 task2 = new ForkJoinDemo1(middle+1,end);
            task2.fork();

             sum = task1.join() + task2.join();
        }
        return sum;
    }
}

ForkJoin原理是通过递归不断调用自身进行计算,节省时间。

17、异步回调

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

使用CompletableFuture来进行异步回调

JUC初阶学习_第39张图片

没有返回值的runAsync异步回调

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //因为类似于ajax,所以可以有返回值,也可以没有返回值
        //没有返回值的话,泛型类就选Void
        //发起一个请求
        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName());
        });
        System.out.println("11");
        completableFuture.get();//获取执行结果
        //由于请求在执行时被阻塞,所以11先执行
    }
}

有返回值的异步回调

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //有返回值的异步回调
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"completableFuture");
            return 111;
        });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("T->" + t);//T 正常运行时的返回结果
            System.out.println("U->" + u);//U 是一个错误的信息
            //java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        }).exceptionally((e) -> {
            e.getMessage();
            return 404;
        }).get());


    }
}

18 JMM

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

1、保证可见性

2、不保证原子性

3、禁止指令重排

什么是JMM?

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

关于JMM的一些同步的约定:

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

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

3、加锁和解锁,必须是同一把锁!!!

​ JMM规定了内存主要划分为主内存和工作内存两种。此处的主内存和工作内存跟JVM内存划分(堆、栈、方法区)是在不同的层次上进行的,如果非要对应起来,主内存对应的是Java堆中的对象实例部分,工作内存对应的是栈中的部分区域,从更底层的来说,主内存对应的是硬件的物理内存,工作内存对应的是寄存器和高速缓存。

JUC初阶学习_第40张图片

​ JVM在设计时候考虑到,如果JAVA线程每次读取和写入变量都直接操作主内存,对性能影响比较大,所以每条线程拥有各自的工作内存,工作内存中的变量是主内存中的一份拷贝,线程对变量的读取和写入,直接在工作内存中操作,而不能直接去操作主内存中的变量。但是这样就会出现一个问题,当一个线程修改了自己工作内存中变量,对其他线程是不可见的,会导致线程不安全的问题。因为JMM制定了一套标准来保证开发者在编写多线程程序的时候,能够控制什么时候内存会被同步给其他线程。

内存交互操作

内存交互操作有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操作之前,必须把此变量同步回主内存

​ JMM对这八种操作规则和对volatile的一些特殊规则就能确定哪里操作是线程安全,哪些操作是线程不安全的了。但是这些规则实在复杂,很难在实践中直接分析。所以一般我们也不会通过上述规则进行分析。更多的时候,使用java的happen-before规则来进行分析

问题:当我们在main线程修改过主内存中的值后,但是由于JMM规范,导致运行中的线程并不知道主线程的值已经被main线程修改,还会继续使用运行时的值,就会产生阻塞问题。

public class JMMDemo {
    private static int num = 1;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{ //此线程对主内存的变化是不知道的
            while (num==1){

            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        num=0;
        System.out.println(num);
    }
}

19、Volatile

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

1、保证可见性

public class VolatileDemo {
    private volatile static int num = 1;
    //不加volatile程序就会死循环
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while (num==1){

            }
        }).start();
        TimeUnit.SECONDS.sleep(1);
        num=0;
        System.out.println(num);
    }
}

2、不保证原子性

//不保证原子性
public class VDemo2 {
    private volatile static int num = 0;
    public static void add(){
        num++;
    }

    //理论上,num结果应该为2w,实际结果达不到2w
    public static void main(String[] args) {
        for (int i = 1; i <= 20 ; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    add();
                }
            },String.valueOf(i)).start();
        }

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

    }
}

如果不加 Lock锁 和 synchronized,怎么样保证原子性?

通过指令:javap -c 当前文件名.class 进行反编译查看底层代码

JUC初阶学习_第41张图片

我们可以使用原子类解决原子性问题,非常高效,底层都和操作系统挂钩,直接再内存中修改值!!Unsafe类是一个很特殊的存在

//使用原子类解决原子性问题
public class VDemo2 {
    //原子类的int
    private volatile static AtomicInteger num = new AtomicInteger(0);
    public static void add(){
//        num++;
        num.getAndIncrement();//+1 方法,使用的底层的CAS,效率极高
    }

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

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

    }
}

3、禁止指令重排

什么是指令重排?

你写的程序,计算机并不是按照你写的那样去执行的。

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

int x = 1;//1
int y = 2;//2
x = x + 5;//3
y = x * x;//4
//我们所期望的执行步骤是 1、2、3、4、5

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

volatile可以避免指令重排:

内存屏障。CPU指令。作用:

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

2、可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)

JUC初阶学习_第42张图片

20、彻底玩转单例模式

1、饿汉式

JUC初阶学习_第43张图片

2、懒汉式

JUC初阶学习_第44张图片

上述存在线程不安全的问题,我们可以使用DCL懒汉式

public class SingleTest {
    Order order=Order.getInstance();
}

class Order{
    private Order() {
    }
    private static Order instance=null;

    public static Order getInstance(){
//        方式二:效率比较高  双重检测锁模式的 懒汉式单例 DCL懒汉式
        if(instance==null){
            synchronized (Order.class) {
                if(instance==null){
                    instance=new Order();
                    /**
                    1、分配内存空间
                    2、执行构造方法,初始化对象
                    3、把这个对象指向这个空间
                    我们期待的是123这样顺序执行,
                    但是有可能执行的是132
                    如果是132的话,
                    当a线程进入并且执行到3的时候,会将对象指向这个空间,突然来了一个b线程,
                    b线程会发现instance!=null,这样就直接return b线程中的结果,
                    但是此时instance还未进行构造,会产生问题。
                    
                    为了解决上述问题,我们必须在声明对象时,加上volatile属性。
                    */
                }
            }
        }
        return instance;
    }
}

但是DCL懒汉式还是存在着原子性问题。

使用volatile解决DCL懒汉式中存在的原子性问题

public class SingleTest {
    Order order=Order.getInstance();
}

class Order{
    private Order() {
    }
    private volatile static Order instance=null;

    public static Order getInstance(){
//        方式二:效率比较高  双重检测锁模式的 懒汉式单例 DCL懒汉式
        if(instance==null){
            synchronized (Order.class) {
                if(instance==null){
                    instance=new Order();
                }
            }
        }
        return instance;
    }
}

静态内部类实现懒汉式

//静态内部类实现
public class Holder {
    private Holder(){

    }

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

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

但是我们会发现,我们可以通过反射来破坏这种单例模式,为了解决这种异常,我们可以通过以下方法

public class LazyMan {
    private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }
    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if(lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan=new LazyMan();
                }
            }
        }
        return lazyMan;
    }
}

通过设置字段方式,防止反射破坏单例结构,但是还是不够,如果被获取到自定义的字段名,反射还能继续进行破坏

public class LazyMan {
    //红绿灯
    private static boolean zhangsan = false;


    private LazyMan(){
        //三重反射
        synchronized (LazyMan.class){
            if(zhangsan==false){
                zhangsan=true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }

        }
    }
    private volatile static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if(lazyMan==null){
            synchronized (LazyMan.class){
                if(lazyMan==null){
                    lazyMan=new LazyMan();
                }
            }
        }
        return lazyMan;
    }


    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//        LazyMan instance = LazyMan.getInstance();

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan lazyMan = declaredConstructor.newInstance();
        LazyMan lazyMan2 = declaredConstructor.newInstance();
        System.out.println(lazyMan);
        System.out.println(lazyMan2);


    }
}

为了防止反射破坏单例模式,我们最终只能使用枚举来组织反射。

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        EnumSingle instance = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = declaredConstructor.newInstance();
        System.out.println(enumSingle);
    }
}

我们通过探究源码发现,反射中说明枚举不可以通过反射进行创建对象

Cannot reflectively create enum objects

我们发现在通过idea或者javap进行反编译后中,enum也是一个继承了Enum类的java类,并且拥有空参构造器,但是实际使用时,却发现,空参构造器无法获取,于是我们使用jad进行反编译,发现实际使用的是继承Enum中父类的构造器参数,string与int,于是通过反射进行获取,发现无法获取对象。

JUC初阶学习_第45张图片

3、单例模式的应用场景

单例模式感觉不怎么用到,实际的应用场景有哪些呢?以下,我将列出一些就在咱们周边和很有意义的单例应用场景。

  1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~

  2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

  3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。

  4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。

  5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。

  6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。

  7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

  8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。

  9. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.

总结以上,不难看出:

单例模式应用的场景一般发现在以下条件下:

(1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。

(2)控制资源的情况下,方便资源之间的互相通信。如线程池等。

21、深入理解CAS

1、什么是CAS

public class CasDemo1 {

    //CAS 就是这个缩写compareAndSet 比较并且交换当前工作内存中的值和主内存中的值,
    //如果这个值是期望的,那么则执行!如果不就一直循环
    public static void main(String[] args) {
        AtomicInteger atomicInteger=new AtomicInteger(2021);
        //compareAndSet(int expect, int update) 期望  更新
        //如果我期望的值达到了就更新,否则就不更新  CAS 是CPU的并发原语
        System.out.println(atomicInteger.compareAndSet(2021, 2022));
        System.out.println(atomicInteger.get());

        atomicInteger.getAndIncrement();

        System.out.println(atomicInteger.compareAndSet(2021, 2022));
        System.out.println(atomicInteger.get());

    }
}

缺点:

1、循环会耗时间

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

3、会产生ABA问题

2、unsafe类

java无法操作内存,但是java可以通过调用C++ 使用native字段,C++可以操作内存,使用unsafe就相当于java的后门,可以通过这个类操作内存。

JUC初阶学习_第46张图片

JUC初阶学习_第47张图片

JUC初阶学习_第48张图片

3、CAS:ABA问题(类似狸猫换太子问题)

JUC初阶学习_第49张图片

public class CasDemo1 {

    //CAS 就是这个缩写compareAndSet 比较并且交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger=new AtomicInteger(2021);
        //对于我们我们平时写的sql来说,我们使用的是乐观锁


        //compareAndSet(int expect, int update) 期望  更新
        //如果我期望的值达到了就更新,否则就不更新  CAS 是CPU的并发原语
        //===========捣乱的线程================
        System.out.println(atomicInteger.compareAndSet(2021, 2022));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2022, 2021));
        System.out.println(atomicInteger.get());

        //=============期望的线程==================
        System.out.println(atomicInteger.compareAndSet(2021, 2022));
        System.out.println(atomicInteger.get());

    }
}

为了解决这个ABA问题,我们需要使用原子引用,与乐观锁类似

4、原子引用

解决这个ABA问题,我们需要使用原子引用,与乐观锁类似

public class CasDemo1 {

    //CAS 就是这个缩写compareAndSet 比较并且交换
    public static void main(String[] args) {
//        AtomicInteger atomicInteger=new AtomicInteger(2021);
        //对于我们我们平时写的sql来说,我们使用的是乐观锁
        //AtomicStampedReference  注意,如果泛型是一个包装类,要注意对象的引用问题
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
		//在正常业务操作中,这里面比较的都是一个个对象
        
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//获得版本号
            System.out.println("a1->"+stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(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();
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//获得版本号
            System.out.println("b1->"+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1,
                    2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("b2->"+atomicStampedReference.getStamp());
        },"b").start();

    }
}

结果:

a1->1
b1->1
true
a2->2
true
a3->3
true
b2->4

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

JUC初阶学习_第50张图片

22、各种锁的理解

1、公平锁、非公平锁

Lock lock1 = new ReentrantLock();//非公平锁
Lock lock2 = new ReentrantLock(true);//公平锁

公平锁:顾名思义非常公平,采用先来先服务策略,

//如果参数为true,则为公平锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

非公平锁:可以允许插队执行,类似短作业优先策略

//非公平锁:
public ReentrantLock() {
    sync = new NonfairSync();
}

2、可重入锁

可重入锁(递归锁)

JUC初阶学习_第51张图片

synchronized版本的可重用锁

public class Lock2 {
    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()+": 发信息");
        call();//这里会获得当前的锁,也就是可重用锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+": 打电话");
    }
}

Lock版本的可重用锁:具有细节问题

public class Lock3 {
    public static void main(String[] args) {
        MobilePhone mobilePhone=new MobilePhone();
        new Thread(()->{
            mobilePhone.sms();
        },"A").start();
        new Thread(()->{
            mobilePhone.sms();
        },"B").start();
    }
}
class MobilePhone{
    Lock lock =new ReentrantLock();
    public void sms(){
        lock.lock();
        //锁必须配对,否则容易死锁,必须 死锁 配对 开锁
        try {
            System.out.println(Thread.currentThread().getName()+": 发信息");
            call();//这里会获得当前的锁,也就是可重用锁
        }finally {
            lock.unlock();
        }

    }
    public void call(){
        lock.lock();//在这里又锁了一次,实际两把锁,而synchronized只有一把锁
        try {
            System.out.println(Thread.currentThread().getName()+": 打电话");
        }finally {
            lock.unlock();
        }
    }
}

3、自旋锁

SpinLock

底层的标准的自旋锁

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    //自旋锁
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}

他会不断的尝试,直到成功位置

自定义自旋锁

//自旋锁
public class Lock4 {
    //int 默认为0
    //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");
        atomicReference.compareAndSet(thread,null);

    }

}

测试:

public class LockTest {
    public static void main(String[] args) throws InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();

        //底层使用的自旋锁CAS
        Lock4 lock=new Lock4();

        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception a) {
                a.getMessage();
            } finally {
                lock.myUnlock();
            }
        },"T1").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception a) {
                a.getMessage();
            } finally {
                lock.myUnlock();
            }
        },"T2").start();
    }
}

t2会等t1解锁后,才能解锁

JUC初阶学习_第52张图片

4、死锁

JUC初阶学习_第53张图片

public class DeathDemo {
    public static void main(String[] args) {
        String locka="LockA";
        String lockb="LockB";
        new Thread(new A(locka,lockb),"T1").start();
        new Thread(new A(lockb,locka),"T2").start();

    }
}

class A implements Runnable{

    private String lockA;
    private String lockB;

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

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

死锁测试,怎么排除死锁?

1、使用 jps -l 定位进程号

JUC初阶学习_第54张图片

2、使用 jstack 进程号 找到死锁问题

JUC初阶学习_第55张图片

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