JUC并发编程入门学习笔记(狂神说)

目录

1. 什么是JUC

2. 线程和进程

1)进程

2)线程

3)并发

4)并行

5)线程的状态

6)wait/sleep

3.Lock

1)传统的 synchronized

2)Lock

3).Synchronized 与Lock 的区别

4. 生产者和消费者的关系

1)Synchronzied 版本

2)JUC版本的生产者和消费者问题

5、6锁现象

6、不安全的集合类

HashSet底层是什么?

7、Callable

 8、常用的辅助类(必会!)

8.1 CountDownLatch​

8.2 CyclickBarrier

8.3 Semaphore

9、读写锁

 10、阻塞队列

BlockingQueue

如何使用阻塞队列呢?

SynchronousQueue同步队列

11、线程池(重点)

12、四大函数式接口(必需掌握)

13、Stream流式计算

14、ForkJoin

15、异步回调

(1)没有返回值的runAsync异步回调

 (2)有返回值的异步回调supplyAsync

16.深入理解CAS

17、原子引用

18. 各种锁的理解

1、公平锁、非公平锁

2、可重入锁

3.自旋锁

4.死锁


因为热爱所以坚持,因为热爱所以等待。熬过漫长无戏可演的日子,终于换来了人生的春天,共勉!!!

1. 什么是JUC

JUC就是java.util.concurrent下面的类包,专门用于多线程的开发。

2. 线程和进程

进程是操作系统中的应用程序、是资源分配的基本单位,线程是用来执行具体的任务和功能,是CPU调度和分派的最小单位

一个进程往往可以包含多个线程,至少包含一个

1)进程

一个程序,QQ.EXE Music.EXE;数据+代码+pcb

一个进程可以包含多个线程,至少包含一个线程!

Java默认有几个线程?2个线程! main线程、GC线程

2)线程

开了一个进程Typora,写字,等待几分钟会进行自动保存(线程负责的)

对于Java而言:Thread、Runable、Callable进行开启线程的。

提问?JAVA真的可以开启线程吗? 开不了的!

Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。

  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 */
            }
        }
    }
	//这是一个C++底层,Java是没有权限操作底层硬件的
    private native void start0();

3)并发

多线程操作同一个资源

  • CPU 只有一核,模拟出来多条线程,天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。
  • 并发编程的本质:充分利用CPU的资源!

4)并行

  • CPU多核,多个线程可以同时执行。 我们可以使用线程池!

获取cpu的核数

public class Test1 {
    public static void main(String[] args) {
        //获取cpu的核数
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

5)线程的状态

Thread类中有个内部枚举,可以看到六种状态

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
    	//运行
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
    	//运行
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
    	//阻塞
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * 
    *
  • {@link Object#wait() Object.wait} with no timeout
  • *
  • {@link #join() Thread.join} with no timeout
  • *
  • {@link LockSupport#park() LockSupport.park}
  • *
* *

A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called Object.wait() * on an object is waiting for another thread to call * Object.notify() or Object.notifyAll() on * that object. A thread that has called Thread.join() * is waiting for a specified thread to terminate. */ //等待 WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: *

    *
  • {@link #sleep Thread.sleep}
  • *
  • {@link Object#wait(long) Object.wait} with timeout
  • *
  • {@link #join(long) Thread.join} with timeout
  • *
  • {@link LockSupport#parkNanos LockSupport.parkNanos}
  • *
  • {@link LockSupport#parkUntil LockSupport.parkUntil}
  • *
*/ //超时等待 TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ //终止 TERMINATED; }

JUC并发编程入门学习笔记(狂神说)_第1张图片

6)wait/sleep

1、来自不同的类

wait => Object

sleep => Thread

一般情况企业中使用休眠是:

TimeUnit.DAYS.sleep(1); //休眠1天
TimeUnit.SECONDS.sleep(1); //休眠1s

2、关于锁的释放

wait 会释放锁;

sleep睡觉了,不会释放锁

3、使用的范围是不同的

wait 必须在同步代码块中;

sleep 可以在任何地方睡;

4、是否需要捕获异常

wait必须要捕获异常;

sleep必须要捕获异常;

3.Lock

1)传统的 synchronized

2)Lock

image-20200810221525974

 image-20200810221731649

公平锁: 十分公平,必须先来后到~;

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

3).Synchronized 与Lock 的区别

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

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

3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁

4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

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

4. 生产者和消费者的关系



1)Synchronzied 版本

public class A {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        },"A").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"B").start();
    }
}
class Data{
    //数字  资源类
    private int number = 0;

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

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

}

结果:实现A,B之间的生产者消费者问题JUC并发编程入门学习笔记(狂神说)_第2张图片

 但是存在一个问题,现在是A,B连两个线程,现在如果我有四个线程A B C D

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        
        new Thread(()->{
        for(int i=0;i<10;i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        },"A").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"B").start();
        
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"C").start();
        
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"D").start();
    }
}
class Data{
    //数字  资源类
    private int number = 0;

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

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

}

结果:出现错误

JUC并发编程入门学习笔记(狂神说)_第3张图片

解决方案: if 改为while即可,防止虚假唤醒

这样就不存在问题了:

public class A {
    public static void main(String[] args) {
        Data data = new Data();
        
        new Thread(()->{
        for(int i=0;i<10;i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        },"A").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"B").start();
        
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"C").start();
        
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"D").start();
    }
}
class Data{
    //数字  资源类
    private int number = 0;

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

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

}

结果:成功实现四个线程互斥

JUC并发编程入门学习笔记(狂神说)_第4张图片

2)JUC版本的生产者和消费者问题

await、signal 替换 wait、notify

在这里插入图片描述

 在这里插入图片描述

public class B {
	public static void main(String[] args) {
		Data2 data2 = new Data2();
		
		new Thread(() ->  {for (int i = 0; i < 10; i++) {
			data2.increment();
		}},"A").start();
		
		new Thread(() ->  {for (int i = 0; i < 10; i++) {
			data2.decrement();
		}},"B").start();
		
		new Thread(() ->  {for (int i = 0; i < 10; i++) {
			data2.increment();
		}},"C").start();
		
		new Thread(() ->  {for (int i = 0; i < 10; i++) {
			data2.increment();
		}},"D").start();
		
	}
}
class Data2 {
	private int number = 0;
	Lock lock = new ReentrantLock();
	Condition condition = lock.newCondition();
	
	public void increment() {
		lock.lock();	
		try {
			while (number != 0) {
				//等待
				condition.await();
			}
		    number++;
		    System.out.println(Thread.currentThread().getName()+"=>"+number);
		    //通知其他线程,+1完毕了
		    condition.signalAll();
		} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
		}
	}
	
	public void decrement() {
		lock.lock();	
		try {
			while (number == 0) {
				//等待
				condition.await();
			}
		    number--;
		    System.out.println(Thread.currentThread().getName()+"=>"+number);
		    //通知其他线程,+1完毕了
		    condition.signalAll();
		} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
		}
	}
}

结果:实现互斥操作

JUC并发编程入门学习笔记(狂神说)_第5张图片

 相比于wait和notify方法只能随机唤醒线程,Condition的优势:精准的通知和唤醒的线程!

通过condition1、condition2 、condition3 的交替使用,完成流程控制A-->B-->C

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

/**
 * A 执行完 调用B
 * B 执行完 调用C
 * C 执行完 调用A
 */

public class C {

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

class Data3{
    //资源类
    private Lock lock=new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1; //1A 2B 3C

    public void printA(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while(number!=1){
                //等待
                condition1.await();
            }
            //操作
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //唤醒指定的线程
            number=2;
            condition2.signal(); // 唤醒2

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while (number!=2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //唤醒3
            number=3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            //业务 判断 -> 执行 -> 通知
            while(number!=3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //唤醒1
            number=1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

结果:实现了三个流程的互斥控制

JUC并发编程入门学习笔记(狂神说)_第6张图片

5、6锁现象

如何判断锁的是谁!锁到底锁的是谁?

锁会锁住:对象、Class

深刻理解我们的锁

问题1:sendMes和call之间停了4秒,下面这段代码运行是先发短信还是先打电话呢?

public class Test1 {
	public static void main(String[] args) {
		Phone phone = new Phone();
		
		new Thread(() -> {
			phone.sendMes();
		},"A").start();

		
		try {
			TimeUnit.SECONDS.sleep(4);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		new Thread(() -> {
			phone.call();
		},"B").start();
		
	}
}
class Phone {
	
	public synchronized void sendMes() {
		System.out.println("发短信");
	}
	public synchronized void call() {
		System.out.println("打电话");
	}
	
}

答案是:JUC并发编程入门学习笔记(狂神说)_第7张图片


问题2 :还是一样,下面这段代码运行是先发短信还是先打电话呢?

public class Test1 {
	public static void main(String[] args) {
		Phone phone = new Phone();
		
		new Thread(() -> {
			try {
				TimeUnit.SECONDS.sleep(4);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			phone.sendMes();
		},"A").start();
		
//		try {
//			TimeUnit.SECONDS.sleep(4);
//		} catch (InterruptedException e) {
//			// TODO Auto-generated catch block
//			e.printStackTrace();
//		}
		
		new Thread(() -> {
			phone.call();
		},"B").start();
		
	}
}
class Phone {
	
	public synchronized void sendMes() {
		System.out.println("发短信");
	}
	public synchronized void call() {
		System.out.println("打电话");
	}
	
}

答案是:JUC并发编程入门学习笔记(狂神说)_第8张图片

为什么呢?

  • 原因:并不是按代码先后顺序执行!是因为synchronized 锁的对象是方法的调用者!对于两个方法用的是同一个锁,问题一的代码,sendMes先拿到锁所以先执行,而问题二的代码因为在sendMes调用之前停了4秒,所以call先拿到锁,call先执行,sendMes阻塞等待call执行完。
  • 一把锁,谁拿到锁谁先执行!另外一个则等待!

问题3创建两个对象,一个调用发短信,一个调用打电话,那么整个顺序是怎么样的呢?

package 锁;

import java.util.concurrent.TimeUnit;

public class Test1 {
	public static void main(String[] args) throws InterruptedException {
		Phone phone1 = new Phone();
		Phone phone2 = new Phone();
		
		new Thread(() -> {
			
			try {
				phone1.sendMes();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		},"A").start();
		
		TimeUnit.SECONDS.sleep(2);
		
		new Thread(() -> {
			phone2.call();
		},"B").start();
		
		
	}
}
class Phone {
	
	public synchronized void call() {
		System.out.println("打电话");
	}
	
	public synchronized void sendMes() throws InterruptedException {
		TimeUnit.SECONDS.sleep(4);
		System.out.println("发短信");
	}
	
	public void hello () {
		System.out.println("hello world!!!");
	}
}

结果:JUC并发编程入门学习笔记(狂神说)_第9张图片

 我们可以看到代码中sendMes方法先获得锁,并且在方法中停了4秒,但是并没有造成call方法阻塞,这是因为是两个不同的对象每个对象都有一把锁


问题4:​​​​​两个对象如果我们把synchronized的方法加上static变成静态方法!那么顺序又是怎么样的呢?

package 锁;

import java.util.concurrent.TimeUnit;

public class Test1 {
	public static void main(String[] args) throws InterruptedException {
		Phone phone1 = new Phone();
		Phone phone2 = new Phone();
		
		new Thread(() -> {
			
			try {
				phone1.sendMes();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		},"A").start();
		
		TimeUnit.SECONDS.sleep(2);
		
		new Thread(() -> {
			phone2.call();
		},"B").start();
		
		
	}
}
class Phone {
	
	public synchronized static void call() {
		System.out.println("打电话");
	}
	
	public synchronized static void sendMes() throws InterruptedException {
		TimeUnit.SECONDS.sleep(4);
		System.out.println("发短信");
	}
}

结果:JUC并发编程入门学习笔记(狂神说)_第10张图片

对于static静态方法来说,对于整个类Class来说只有一份,如果静态static方法使用synchronized锁定,那么这个synchronized锁会锁住整个Class对象!不管多少个对象,对于静态的锁都只有一把锁,谁先拿到这个锁就先执行,其他的进程都需要等待!
 


 问题6:一个对象,如果我们使用一个静态同步方法、一个同步方法、一个对象调用顺序是什么?

package 锁;

import java.util.concurrent.TimeUnit;

public class Test1 {
	public static void main(String[] args) throws InterruptedException {
		Phone phone1 = new Phone();
		//Phone phone2 = new Phone();
		
		new Thread(() -> {
			
			try {
				phone1.sendMes();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		},"A").start();
		
		TimeUnit.SECONDS.sleep(2);
		
		new Thread(() -> {
			phone1.call();
		},"B").start();
	}
}
class Phone {
	
	public synchronized static void call() {
		System.out.println("打电话");
	}
	
	public synchronized  void sendMes() throws InterruptedException {
		TimeUnit.SECONDS.sleep(4);
		System.out.println("发短信");
	}
}

结果:JUC并发编程入门学习笔记(狂神说)_第11张图片

事实证明,打电话先执行了,因为static call方法使用的Class锁,sendMes使用的是对象锁,所以不会造成阻塞


小结:Class锁只有一把,所有静态资源共用,而对象锁是每个对象有一把

6、不安全的集合类

List不安全

我们来看一下List这个集合类:

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

        List arrayList = new ArrayList<>();

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

ArrayList 在并发情况下是不安全的!会造成并发修改异常:

解决方案:

1、切换成Vector就是线程安全的啦!

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

        List arrayList = new Vector();

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

    }
} 
  

2、使用Collections.synchronizedList(new ArrayList<>());

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

        List arrayList = Collections.synchronizedList(new ArrayList<>());

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

    }
} 
  

3、使用JUC中的包:List arrayList = new CopyOnWriteArrayList<>();

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

        List arrayList = new CopyOnWriteArrayList<>();

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

    }
} 
  

CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略

Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;

解决方案还是两种:

  • 使用Collections工具类的synchronized包装的Set类
  • 使用CopyOnWriteArraySet 写入复制的JUC解决方案
//同理:java.util.ConcurrentModificationException
// 解决方案:
public class SetTest {
    public static void main(String[] args) {
//        Set hashSet = Collections.synchronizedSet(new HashSet<>()); //解决方案1
        Set hashSet = new CopyOnWriteArraySet<>();//解决方案2
        for (int i = 1; i < 100; i++) {
            new Thread(()->{
                hashSet.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(hashSet);
            },String.valueOf(i)).start();
        }
    }
}

HashSet底层是什么?

hashSet底层就是一个HashMap

public HashSet() {
        map = new HashMap<>();
}

//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}
//PRESENT是什么? 是一个常量  不会改变的常量  无用的占位
private static final Object PRESENT = new Object();

Map不安全

同样的HashMap基础类也存在并发修改异常

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

解决方案:

  • 使用Collections.synchronizedMap(new HashMap<>());处理
  • 使用ConcurrentHashMap进行并发处理

7、Callable

在这里插入图片描述

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

传统使用线程方式:

public class CallableTest {
    public static void main(String[] args) {
        for (int i = 1; i < 10; i++) {
            new Thread(new MyThread()).start();
        }
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

使用Callable进行多线程操作:

JUC并发编程入门学习笔记(狂神说)_第12张图片

Calleable 泛型T就是call运行方法的返回值类型

但是如何使用呢?

源码分析:

JUC并发编程入门学习笔记(狂神说)_第13张图片

Callable怎么放入到Thread里面呢?

看JDK api文档:

在Runnable里面有一个叫做FutureTask的实现类,我们进去看一下。

在这里插入图片描述

FutureTask中可以接受Callable参数; 

在这里插入图片描述

 这样我们就可以先把Callable 放入到FutureTask中, 如何再把FutureTask 放入到Thread就可以了。

public class CallableDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
//      new Thread(new Runnable()).start();
//      new Thread(new FutureTask<>( Callable)).start();
		for (int j = 1; j < 10; j++) {
			MyThread2 myThread2 = new MyThread2();
			//适配类FutureTask
			FutureTask futureTask = new FutureTask(myThread2);
			//放入Thread使用
			new Thread(futureTask,String.valueOf(j)).start();
			 //获取返回值
			String s = futureTask.get();
	        System.out.println("返回值:"+ s);
		}
		
	}
}

class MyThread2 implements Callable {
	@Override
	public String call() throws Exception {
		// TODO Auto-generated method stub
		 System.out.println("Call:"+Thread.currentThread().getName());
	     return "String"+Thread.currentThread().getName();
	}
	
}

这样我们就可以使用Callable来进行多线程编程了,并且我们发现可以有返回值,并且可以抛出异常。

注意:两点

 运行结果:

JUC并发编程入门学习笔记(狂神说)_第14张图片

 8、常用的辅助类(必会!)

8.1 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()+" Go out");
                countDownLatch.countDown(); //每个线程都数量-1
			},String.valueOf(i)).start();
		}
		 countDownLatch.await();//等待计数器归零,就是执行了6次new Thread 后清零,才继续往下执行  
		 System.out.println("close door");
	}
}

主要方法:

  • countDown 减一操作;
  • await 等待计数器归零。

await等待计数器为0,就唤醒,再继续向下运行。

8.2 CyclickBarrier

在这里插入图片描述

其实就是一个加法计数器;

当线程数达到设置的7时,才触发await(),唤醒,召唤出神龙

public class CyclickBarrierDemo {
	public static void main(String[] args) {
		//主线程
		CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () ->{
			System.out.println("召唤神龙!!!");
		});
		
		for(int i = 0; i <= 7; i++) {
			int finalI = i;
			new Thread(() -> {
				System.out.println(Thread.currentThread().getName()+"收集了第 {"+ finalI+"} 颗龙珠");
				try {
					cyclicBarrier.await();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}).start();
		}
	}
}

8.3 Semaphore

Semaphore:信号量

抢车位:

3个车位 6辆车:

public class SemaphoreDemo {
	public static void main(String[] args) {		
		Semaphore semaphore = new Semaphore(3);
		for (int i = 1; i <= 6; i++) {
			int finalI = i;
			new Thread(() -> {
				try {
					semaphore.acquire();//得到
					System.out.println(Thread.currentThread().getName());
					TimeUnit.SECONDS.sleep(2);
					
				} catch (Exception e) {
					e.printStackTrace();
				}finally {
					semaphore.release();//释放
				}
			},String.valueOf(i)).start(); 
		}
	}
}

原理:

semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!

semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!

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

9、读写锁

先对于不加锁的情况:

如果我们做一个我们自己的cache缓存。分别有写入操作、读取操作;

我们采用五个线程去写入,使用十个线程去读取。

我们来看一下这个的效果,如果我们不加锁的情况!

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache_ReadWriteLock mycache = new MyCache_ReadWriteLock();
        //开启5个线程 写入数据
        for (int i = 1; i <=5 ; i++) {
            int finalI = i;
            new Thread(()->{
                mycache.put(String.valueOf(finalI),String.valueOf(finalI));
            }).start();
        }
        //开启10个线程去读取数据
        for (int i = 1; i <=10 ; i++) {
            int finalI = i;
            new Thread(()->{
                String o = mycache.get(String.valueOf(finalI));
            }).start();
        }
    }
}

class MyCache_ReadWriteLock{
    private volatile Map map=new HashMap<>();

    //使用读写锁
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //普通锁
    private Lock lock=new ReentrantLock();

    public void put(String key,String value){
        //写入
        System.out.println(Thread.currentThread().getName()+" 线程 开始写入");
        map.put(key, value);
        System.out.println(Thread.currentThread().getName()+" 线程 写入OK");
    }

    public String get(String key){
        //得到
        System.out.println(Thread.currentThread().getName()+" 线程 开始读取");
        String o = map.get(key);
        System.out.println(Thread.currentThread().getName()+" 线程 读取OK");
        return o;
    }
}

结果:

Thread-0 线程 开始写入
Thread-4 线程 开始写入  # 插入了其他的线程进行写入
Thread-4 线程 写入OK
Thread-3 线程 开始写入
Thread-1 线程 开始写入
Thread-2 线程 开始写入
Thread-1 线程 写入OK
Thread-3 线程 写入OK
Thread-0 线程 写入OK   # 对于这种情况会出现 数据不一致等情况
Thread-2 线程 写入OK
Thread-5 线程 开始读取
Thread-6 线程 开始读取
Thread-6 线程 读取OK
Thread-7 线程 开始读取
Thread-7 线程 读取OK
Thread-5 线程 读取OK
Thread-8 线程 开始读取
Thread-8 线程 读取OK
Thread-9 线程 开始读取
Thread-9 线程 读取OK
Thread-10 线程 开始读取
Thread-11 线程 开始读取
Thread-12 线程 开始读取
Thread-12 线程 读取OK
Thread-10 线程 读取OK
Thread-14 线程 开始读取
Thread-13 线程 开始读取
Thread-13 线程 读取OK
Thread-11 线程 读取OK
Thread-14 线程 读取OK

Process finished with exit code 0

所以如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。

我们也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。

但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证在这里插入图片描述

public class ReadWriteLockDemo {
	
	/**
	 * 独占锁(写锁),一次只能被一个线程占有
	 * 共享锁(读锁),多个线程可以同时
	 * 
	 *
	 */
	public static void main(String[] args) {
		
		MyCache myCache = new MyCache();	
		for (int i = 0; i <= 5; i++) {
			final int temp = i;
			new Thread(() ->  {
				myCache.put(temp+"",temp+"");
			},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 MyCache {
	
	private ReadWriteLock readwritelock = new ReentrantReadWriteLock();
	private volatile Map map = new HashMap();
		
	//Write
	public void put(String key,Object value) {
		readwritelock.writeLock().lock();
		try {
			System.out.println(Thread.currentThread().getName()+"写入"+key);
			map.put(key, value);
			System.out.println(Thread.currentThread().getName()+"写入完成");
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			readwritelock.writeLock().unlock();
		}
		
		
	}
	
	//Read
	public void get(String key) {
		readwritelock.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName()+"读取"+key);
			Object o = map.get(key);
			System.out.println(Thread.currentThread().getName()+"读取成功");
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally {
			readwritelock.readLock().unlock();		
		}
	}
}

结果:

JUC并发编程入门学习笔记(狂神说)_第15张图片

 10、阻塞队列

在这里插入图片描述

 阻塞队列jdk1.8文档解释:

在这里插入图片描述

BlockingQueue

blockingQueue 是Collection的一个子类;

什么情况我们会使用 阻塞队列呢?

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

整个阻塞队列的家族如下:Queue以下实现的有Deque、AbstaractQueue、BlockingQueue;

BlockingQueue以下有Link链表实现的阻塞队列、也有Array数组实现的阻塞队列

如何使用阻塞队列呢?

操作:添加、移除

但是实际我们要学的有:

四组API

方式 抛出异常 不会抛出异常,有返回值 阻塞 等待 超时 等待
添加 add offer put offer(timenum,timeUnit)
移除 remove poll take poll(timenum,timeUnit)
判断队列首 element peek - -
public class BlockingQueueDemo {

	public static void main(String[] args) {
		test2();
	}
	
	//抛异常
	public static void test() {
		ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
		System.out.println(blockingQueue.add("a"));
		System.out.println(blockingQueue.add("b"));
		System.out.println(blockingQueue.add("c"));
		
		//System.out.println(blockingQueue.add("d"));
		
		System.out.println(blockingQueue.remove());
		System.out.println(blockingQueue.remove());
		System.out.println(blockingQueue.remove());
		System.out.println(blockingQueue.remove());
	}
	
	//不抛异常,返回null
	public static void test2() {
		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"));
		
		//System.out.println(blockingQueue.add("d"));
		
		System.out.println(blockingQueue.poll());
		System.out.println(blockingQueue.poll());
		System.out.println(blockingQueue.poll());
		System.out.println(blockingQueue.poll());
	}
	
	public static void test3() throws InterruptedException {
		ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
		blockingQueue.put("a");
		blockingQueue.put("b");
		blockingQueue.put("c");
		//System.out.println(blockingQueue.add("d"));
		blockingQueue.take();
		blockingQueue.take();
		blockingQueue.take();
		
	}
	
	public static void test4() throws InterruptedException {
		ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
		System.out.println(blockingQueue.offer("a", 2, TimeUnit.SECONDS));
		System.out.println(blockingQueue.offer("b",2, TimeUnit.SECONDS));
		System.out.println(blockingQueue.offer("c",2, TimeUnit.SECONDS));
		System.out.println(blockingQueue.offer("b", 2, TimeUnit.SECONDS));
		//System.out.println(blockingQueue.add("d"));
		
		System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
		System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
		System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
		System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
		
	}

SynchronousQueue同步队列

同步队列 没有容量,也可以视为容量为1的队列

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

put方法 和 take方法

Synchronized 和 其他的BlockingQueue 不一样 它不存储元素;

put了一个元素,就必须从里面先take出来,否则不能再put进去值!

并且SynchronousQueue 的take是使用了lock锁保证线程安全的。

public class SynchronousQueueDemo {
	
	public static void main(String[] args) {
		BlockingQueue blockingQueue = new SynchronousQueue<>();
		
		new Thread(()-> {
			try {
				blockingQueue.put("1");
				System.out.println(Thread.currentThread().getName()+" put 1");
				blockingQueue.put("2");
				System.out.println(Thread.currentThread().getName()+" put 2");
				blockingQueue.put("3");
				System.out.println(Thread.currentThread().getName()+" put 3");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"T1").start();
		
		new Thread(()-> {
			try {
				
				blockingQueue.take();
				System.out.println(Thread.currentThread().getName()+" take 1");			
				blockingQueue.take();
				System.out.println(Thread.currentThread().getName()+" take 2");
				blockingQueue.take();
				System.out.println(Thread.currentThread().getName()+" take 3");
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		},"T2").start();
	}
	
}	

结果:

JUC并发编程入门学习笔记(狂神说)_第16张图片

11、线程池(重点)

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

池化技术

程序的运行,本质:占用系统的资源!我们需要去优化资源的使用 ===> 池化技术

线程池、JDBC的连接池、内存池、对象池 等等。。。。

资源的创建、销毁十分消耗资源

池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。

线程池的好处:

1、降低资源的消耗;

2、提高响应的速度;

3、方便管理;

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

  • ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
  • ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
  • ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//工具类 Executors 三大方法;
public class Demo01 {
    public static void main(String[] args) {

        ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
        ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
        ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的

        //线程池用完必须要关闭线程池
        try {

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

源码分析

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue());
}
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue());
}

本质:三种方法都是开启的ThreadPoolExecutor

7大参数

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

阿里巴巴Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。

在这里插入图片描述

手动创建线程池

public class ThreadsPoolDemo {
	public static void main(String[] args) {
		//Executors.newSingleThreadExecutor();
		//最大线程池如何定义
		//1.cpu密集型,几核就是几个,保持cpu的效率最高
		//2.IO密集型,数量   > 判断程序中十分耗IO的线程的数量(2倍)
		System.out.println(Runtime.getRuntime().availableProcessors());
		
		ExecutorService threadPool = new ThreadPoolExecutor(
				//corePoolSize
				2,
				//maximumPoolSize
				5,
				//keepAliveTime
				3,
				//unit
				TimeUnit.SECONDS,
				//workQueue
				new LinkedBlockingDeque<>(3),
				Executors.defaultThreadFactory(),
				new ThreadPoolExecutor.AbortPolicy()//拒绝策略,满了直接抛异常
		);
		//最大承载 Deque + max
		for (int i = 1; i <=8; i++) {
				threadPool.execute(() -> {
					System.out.println(Thread.currentThread().getName()+"ok");
				});
		}
	}

 业务图

以银行办以业务为例子,总共有5个柜台可以办理业务,这5个就是最大线程数; 但是并不是5个一直都开着,平时常开着的就有2个,这两个叫核心线程; 同时呢有有个顾客排队等待的地方,对应阻塞队列;

当阻塞队列满了的时候,平时不开的线程也启动,达到最大线程,但是当来的数量太多了,大于最大线程+阻塞队列的大小,就会启动拒绝策略

在这里插入图片描述

拒绝策略4种

(1)new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常

超出最大承载,就会抛出异常:队列容量大小+maxPoolSize

(2)new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理

(3)new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉,不会抛出异常。

(4)new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常

小结和拓展

如何去设置线程池的最大大小如何去设置?


CPU密集型和IO密集型!

1、CPU密集型

电脑的核数是几核就选择几;选择maximunPoolSize的大小

我们可以使用代码来来获取逻辑处理器数量。

于是cpu密集型的写法如下:

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

2、I/O密集型:

在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。

12、四大函数式接口(必需掌握)

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

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

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

在这里插入图片描述

 函数型接口可以使用lambda表达式;

代码测试:

Function函数型接口

JUC并发编程入门学习笔记(狂神说)_第17张图片

JUC并发编程入门学习笔记(狂神说)_第18张图片

Predicate断定型接口

JUC并发编程入门学习笔记(狂神说)_第19张图片

JUC并发编程入门学习笔记(狂神说)_第20张图片

 Consummer 消费型接口

JUC并发编程入门学习笔记(狂神说)_第21张图片

JUC并发编程入门学习笔记(狂神说)_第22张图片

 Supplier供给型接口

JUC并发编程入门学习笔记(狂神说)_第23张图片

 JUC并发编程入门学习笔记(狂神说)_第24张图片

13、Stream流式计算

什么是Stream流式计算?

存储+计算

存储:集合、MySQL

计算:流式计算~

=== 链式编程 ===

public class Test {
    public static void main(String[] args) {
        User user1 = new User(1,"a",21);
        User user2 = new User(2,"b",22);
        User user3 = new User(3,"c",23);
        User user4 = new User(4,"d",24);
        User user5 = new User(5,"e",25);
        User user6 = new User(6,"f",26);
        List list = Arrays.asList(user1, user2, user3, user4, user5, user6);

        //计算交给流
        //链式编程!!!!
        list.stream()
                .filter((u)->{ return u.getId()%2==0; })
                .filter((u)->{return u.getAge()>23;})
                .map((u)->{return u.getName().toUpperCase();})
                .sorted((uu1,uu2)->{
                    return uu2.compareTo(uu1);
                })
                .limit(1)
                .forEach(System.out::println);
    }
}

14、ForkJoin

什么是ForkJoin?

ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!

大数据中:MapReduce 核心思想->把大任务拆分为小任务!

在这里插入图片描述

 ForkJoin 特点: 工作窃取!

实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!

在这里插入图片描述

如何使用ForkJoin?

  • 1、通过ForkJoinPool来执行
  • 2、计算任务 execute(ForkJoinTask task)

在这里插入图片描述

  • 3、计算类要去继承ForkJoinTask;

ForkJoin的计算类!

package com.ogj.forkjoin;

import java.util.concurrent.RecursiveTask;

public class ForkJoinDemo extends RecursiveTask {

    private long star;
    private long end;

    //临界值
    private long temp=1000000L;

    public ForkJoinDemo(long star, long end) {
        this.star = star;
        this.end = end;
    }

    /**
     * 计算方法
     * @return Long
     */
    @Override
    protected Long compute() {
        if((end-star)

测试类!

package com.ogj.forkjoin;

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

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

    /**
     * 普通计算
     */
    public static void test1(){
        long star = System.currentTimeMillis();
        long sum = 0L;
        for (long i = 1; i < 20_0000_0000; i++) {
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
        System.out.println(sum);
    }

    /**
     * 使用ForkJoin
     */
    public static void test2() throws ExecutionException, InterruptedException {
        long star = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask task = new ForkJoinDemo(0L, 20_0000_0000L);
        ForkJoinTask submit = forkJoinPool.submit(task);
        Long aLong = submit.get();
        System.out.println(aLong);
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
    }


    /**
     * 使用Stream 并行流
     */
    public static void test3(){
        long star = System.currentTimeMillis();
        //Stream并行流()
        long sum = LongStream.range(0L, 20_0000_0000L).parallel().reduce(0, Long::sum);
        System.out.println(sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+"时间:"+(end-star));
    }
}

.parallel().reduce(0, Long::sum)使用一个并行流去计算整个计算,提高效率。

在这里插入图片描述

reduce方法的优点:

在这里插入图片描述


15、异步回调

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

其实就是前端 --> 发送ajax异步请求给后端

 在这里插入图片描述

 但是我们平时都使用CompletableFuture


(1)没有返回值的runAsync异步回调

public static void main(String[] args) throws ExecutionException, InterruptedException 
{
        // 发起 一个 请求

        System.out.println(System.currentTimeMillis());
        System.out.println("---------------------");
        CompletableFuture future = CompletableFuture.runAsync(()->{
            //发起一个异步任务
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+".....");
        });
        System.out.println(System.currentTimeMillis());
        System.out.println("------------------------------");
        //输出执行结果
        System.out.println(future.get());  //获取执行结果
 }

 (2)有返回值的异步回调supplyAsync

//有返回值的异步回调
CompletableFuture completableFuture=CompletableFuture.supplyAsync(()->{
    System.out.println(Thread.currentThread().getName());
    try {
        TimeUnit.SECONDS.sleep(2);
        int i=1/0;
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
    //success 回调
    System.out.println("t=>" + t); //正常的返回结果
    System.out.println("u=>" + u); //抛出异常的 错误信息
}).exceptionally((e) -> {
    //error回调
    System.out.println(e.getMessage());
    return 404;
}).get());

whenComplete: 有两个参数,一个是t 一个是u

T:是代表的 正常返回的结果

U:是代表的 抛出异常的错误信息

如果发生了异常,get可以获取到exceptionally返回的值;

16.深入理解CAS

什么是CAS?

大厂必须深入研究底层!!!!修内功!操作系统、计算机网络原理、组成原理、数据结构

public class casDemo {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        //因为期望值是2020  实际值却变成了2021  所以会修改失败
        //CAS 是CPU的并发原语
        atomicInteger.getAndIncrement(); //++操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

在这里插入图片描述

Unsafe类

在这里插入图片描述

 在这里插入图片描述

总结:

CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。

缺点:

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

CAS:ABA问题?(狸猫换太子)

在这里插入图片描述

线程1:期望值是1,要变成2;

线程2:两个操作:

  • 1、期望值是1,变成3
  • 2、期望是3,变成1

所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;

17、原子引用

解决ABA问题,对应的思想:就是使用了乐观锁~

带版本号的 原子操作!

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

在这里插入图片描述

 所以如果遇到,使用大于128的时候,使用原子引用的时候,如果超过了这个值,那么就不会进行版本上升!

public class CASDemo2 {
	
	//如果泛型是一个包装类,注意对象的引用问题
	static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(1, 1);
	
	public static void main(String[] args) {
		new Thread(() -> {
			int stamp = atomicStampedReference.getStamp();
			System.out.println("a1->"+stamp);
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
			
			System.out.println("a2->"+atomicStampedReference.getStamp());
			
			System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
			
			System.out.println("a3->"+atomicStampedReference.getStamp());
		},"a").start();
		
		new Thread(() -> {
			int stamp = atomicStampedReference.getStamp();
			System.out.println("b1->"+stamp);
			try {
				TimeUnit.SECONDS.sleep(2);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			atomicStampedReference.compareAndSet(1, 6, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
			
			System.out.println("b2->"+atomicStampedReference.getStamp());
			
			System.out.println(atomicStampedReference.compareAndSet(6, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
			
			System.out.println("b3->"+atomicStampedReference.getStamp());
		},"b").start();
	}
}

结果:

JUC并发编程入门学习笔记(狂神说)_第25张图片

18. 各种锁的理解

1、公平锁、非公平锁

公平锁:非常公平;不能插队的,必须先来后到;

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

非公平锁:非常不公平,允许插队的,可以改变顺序。

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2、可重入锁

可重入锁(递归锁)

在这里插入图片描述

 Synchronized锁

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

}

class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"=> sms");
        call();//这里也有一把锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=> call");
    }
}

lock锁

//lock
public class Demo02 {

    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }

}
class Phone2{

    Lock lock=new ReentrantLock();

    public void sms(){
        lock.lock(); //细节:这个是两把锁,两个钥匙
        //lock锁必须配对,否则就会死锁在里面
        try {
            System.out.println(Thread.currentThread().getName()+"=> sms");
            call();//这里也有一把锁
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "=> call");
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            lock.unlock();
        }
    }
}
  • lock锁必须配对,相当于lock和 unlock 必须数量相同;
  • 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;

3.自旋锁

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

自我设计自旋锁:

public class SpinlockDemo {

    //int 0
    //thread null
    AtomicReference atomicReference=new AtomicReference<>();

    //加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"===> mylock");

        //自旋锁
        while (!atomicReference.compareAndSet(null,thread)){
            System.out.println(Thread.currentThread().getName()+" ==> 自旋中~");
        }
    }


    //解锁
    public void myunlock(){
        Thread thread=Thread.currentThread();
        System.out.println(thread.getName()+"===> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }

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


        //使用CAS实现自旋锁
        SpinlockDemo spinlockDemo=new SpinlockDemo();
        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myunlock();
            }
        },"t1").start();

        TimeUnit.SECONDS.sleep(1);


        new Thread(()->{
            spinlockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinlockDemo.myunlock();
            }
        },"t2").start();
    }
}

运行结果:

t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。。。。

4.死锁

死锁是什么?

在这里插入图片描述

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

package com.ogj.lock;

import java.util.concurrent.TimeUnit;

public class DeadLock {
    public static void main(String[] args) {
        String lockA= "lockA";
        String lockB= "lockB";

        new Thread(new MyThread(lockA,lockB),"t1").start();
        new Thread(new MyThread(lockB,lockA),"t2").start();
    }
}

class MyThread implements Runnable{

    private String lockA;
    private String lockB;

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

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

解决问题

1、使用jps定位进程号,jdk的bin目录下: 有一个jps

命令:jps -l

在这里插入图片描述

2、使用jstack 进程进程号 找到死锁信息

在这里插入图片描述

 一般情况信息在最后:

在这里插入图片描述

面试,工作中!排查问题!

1、日志

2、堆栈信息

参考:

https://blog.csdn.net/qq_41617848/article/details/107619810?

你可能感兴趣的:(java,juc,多线程,后端,java,面试)