JUC编程入门(高并发)

JUC编程

与JUC编程相关的包

在这里插入图片描述

一.线程和进程

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

java默认有几个线程?

mian线程和GC线程(垃圾回收)

对于java而言:Thread、Runnable、Callable

一般是他们来开启进程

java真的能开启线程么

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 */
        }
    }
}
//调用start0   native是本地方法,需要去调用C++操作硬件的,java实现不了
private native void start0();

并行、并发

并发编程:并行、并发

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

​ ·CPU一核,模拟出来多条线程,快速交替

并行(多个人一起行走)

​ ·CPU多核,多个线程可以同时进行

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

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

线程的几个状态
public enum State {
    //新建
    NEW,
    //运行
    RUNNABLE,
    //阻塞
    BLOCKED,
    //等待
    WAITING,
    //超时等待
    TIMED_WAITING,
    //终止
    TERMINATED;
}
wait/sleep区别

1.来自不同的类

wait=》Object

sleep=》Thread

2.关于锁的释放

wait回释放锁的,而sleep是不会释放的

3.使用的范围不同

wait:必须在同步代码块中

sleep:可以在任何地方使用

4.是否需要捕获异常

wait不需要捕获异常

sleep必须要捕获异常(因为他可能会有超时等待)

二.Lock锁

传统的Synchronized
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 number = 50;
    //买票的方式
    public synchronized void sale(){
        if (number>0)
        {
            System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,还剩"+number+"张");
        }
    }
}

如果我们的买票方法没有synchronized修饰,则会出乱,没有顺序(synchronized也是非公平锁)

Lock接口

在这里插入图片描述
在这里插入图片描述

公平锁:可以先来后到:必须排队

非公平锁:不公平,可以插队(默认使用非公平锁

public class SaleTicketDemo02 {
    public static void main(String[] args) {
        //并发,多线程操作同一个资源类
        Ticket2 ticket = new Ticket2();
        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();
    }
}
//lock3部曲
//1.new ReentrantLock()
//2.lock.lock()加锁
//3.finally { //解锁 lock.unlock(); }
//资源类  OOP
class Ticket2{
    //属性,方法
    private int number = 50;
    Lock lock = new ReentrantLock();
    //买票的方式
    public void sale(){
        //加锁
        lock.lock();
        try {
            if (number>0)
            {
                System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,还剩"+number+"张");
            }
        }catch (Exception e)
        {
            e.printStackTrace();
        }finally {
            //解锁
            lock.unlock();
        }

    }
}

相当于手动挡的synchronized

Synchronized和Lock区别

1.Synchroized是一个内置的Java关键字,Lock是一个类

2.Synchroized无法判断读取锁的状态,Lock可以判断是否获取到锁

3.Synchroized会自动释放锁,lock必须手动释放锁,如果不释放锁,就会死锁

4.Synchroized 线程1(获得锁:阻塞)、线程2(等待锁,傻等),而lock锁不会傻等(lock.trylock()尝试获取锁)

5.Synchroized 可重入锁,不可中断的,非公平锁;

lock锁,也是可重入锁,可以判断锁状态,可以自己设置是非公平锁还是公平锁

6.Synchroized 适合锁少量的代码同步问题,Lock锁适合锁大量的同步代码

什么是锁,如何判断锁的是谁?

三.生产者和消费者问题

synchronized版本
package com.hkd.pc;

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/13 11:50
 * @Description 线程之间的通信问题:生产者和消费者问题     等待唤醒,通知唤醒
 * @pojectname 线程交替执行  A    B   操作同一个变量  num = 0
 *                  A  num+1       B num-1
 */
public class A {
    public static void main(String[] args) {
        Date date = new Date();
        new Thread(()->{
            for (int i=0;i<10;i++)
            {
                try {
                    date.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i=0;i<10;i++)
            {
                try {
                    date.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }
}
//判断等待,通知,业务
class Date{
    public int number = 0;
    //+1
    public synchronized void increment() throws InterruptedException {

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

问题:如果存在A B C D四个线程呢,线程还安全么

虚假唤醒问题
在这里插入图片描述

if改为while判断

即可解决虚假唤醒问题

JUC版本

在这里插入图片描述

  • class BoundedBuffer {
       final Lock lock = new ReentrantLock();
       final Condition notFull  = lock.newCondition(); 
       final Condition notEmpty = lock.newCondition(); 
    
       final Object[] items = new Object[100];
       int putptr, takeptr, count;
    
       public void put(Object x) throws InterruptedException {
         lock.lock(); try {
           while (count == items.length)
             notFull.await();
           items[putptr] = x;
           if (++putptr == items.length) putptr = 0;
           ++count;
           notEmpty.signal();
         } finally { lock.unlock(); }
       }
    
       public Object take() throws InterruptedException {
         lock.lock(); try {
           while (count == 0)
             notEmpty.await();
           Object x = items[takeptr];
           if (++takeptr == items.length) takeptr = 0;
           --count;
           notFull.signal();
           return x;
         } finally { lock.unlock(); }
       }
     } 
    

代码实现:

package com.hkd.pc;

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

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/13 12:14
 * @Description TODO
 * @pojectname 线程相关
 */
public class B {
    public static void main(String[] args) {
        Date date = new Date();
        new Thread(()->{
            for (int i=0;i<10;i++)
            {
                try {
                    date.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

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

//判断等待,通知,业务
class Date1{
    public int number = 0;
    //+1
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number!=0) {
                //等待
                condition.await();//等待
            }
            number++;
            //通知其他线程,我+1完毕了
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void decrement() throws InterruptedException {
        lock.lock();
        try {
            while (number==0) {
                //等待
                condition.await();//等待
            }
            number--;
            //通知其他线程,我-1完毕了
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

任何一个新技术,绝不是仅仅只是覆盖了原来的技术,而是优势和补充

问题:

在这里插入图片描述
不是ABCD的顺序

Condition精准的通知和唤醒技术

有序性

代码测试

package com.hkd.pc;

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

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/13 19:12
 * @Description TODO
 * @pojectname 线程相关
 */
public class C {
    public static void main(String[] args) {
        Data3 data = new Data3();
        //A执行完  调用B,B执行完调用C ,C执行完调用A
        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();

    }
}
//资源类 lock锁
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;//1  A   2B    3C
    public void printA(){
        lock.lock();
        try {
            //业务  判断  执行
            while (number!=1)
            {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"=>AAA");
            //唤醒,唤醒指定的人
            number=2;
            condition2.signal();
        } 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()+"=>AAA");
            //唤醒,唤醒指定的人
            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()+"=>AAA");
            //唤醒,唤醒指定的人
            number=1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

保证了我们的有序性

四.八锁现象

对象、Class

情况一

synchronized关键字

锁的对象是方法的调用者

package com.hkd.lock8;

import java.util.concurrent.TimeUnit;

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/13 19:28
 * @Description TODO
 * //1.标准情况下:先打印是发短信还是打电话?--------》发短信
 * 2.我们发短信业务中暂停3S,现在先打印是发短信还是打电话?-----》发短信
 * 因为sendSms和call用的是synchronized关键字  锁的对象是方法的调用者
 * 两个方法是一个对象,用的同一把锁,谁先拿到谁先执行
 * @pojectname 线程相关
 */
public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(()->{phone.sendSms();},"A").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{phone.call();},"B").start();
    }
}

class Phone{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信snedSms");
    }
    public synchronized void call(){
        System.out.println("call");
    }
}

增加一个普通方法呢?

   Phone2 phone = new Phone2();
   new Thread(()->{phone.sendSms();},"A").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{phone.hello();},"B").start();
public void hello(){
    System.out.println("hello  hello");
}

我们发现1S过后hello方法先执行

因为普通方法不受锁的影响

情况二

两个对象,两个同步方法

 Phone2 phone1 = new Phone2();
 Phone2 phone2 = new Phone2();
 new Thread(()->{phone1.sendSms();},"A").start();
        TimeUnit.SECONDS.sleep(1);
 new Thread(()->{phone2.call();},"B").start();
class Phone2{
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信snedSms");
    }
    public synchronized void call(){
        System.out.println("call");
    }
}

call先打印,这个时候就跟延迟有关了,因为锁的对象不一样,谁快先调用谁

情况三

方法变成静态同步方法(static修饰)

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Phone3 phone = new Phone3();
        new Thread(()->{phone.sendSms();},"A").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{phone.call();},"B").start();
    }
}

class Phone3{
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信snedSms");
    }
    public static synchronized void call(){
        System.out.println("call");
    }
}

我们发现发短信先打印

原因:

**1.**synchronized关键字锁的对象是方法的调用者,同一个对象,谁先拿到谁执行

**2.**static : 静态方法 类一加载就有了 是一个Class 模板

Phone3只有唯一的一个class对象 -–-–> Class phone3Class = Phone3.class;,因此,我们这个地方锁的的class,全局唯一,用的肯定是同一个锁

如:

Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
    phone1.sendSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
    phone2.call();},"B").start();

还是发短信先打印,也就印证了我们的Static修饰的同步方法,锁的就是Class,一定是谁先拿到谁先执行

情况四

一个对象+静态同步方法+普通同步方法

public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Phone4 phone = new Phone4();
        new Thread(()->{
            phone.sendSms();},"A").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            phone.call();},"B").start();
    }
}

class Phone4{
    //静态同步方法
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信snedSms");
    }
    //普通同步方法
    public  synchronized void call(){
        System.out.println("call");
    }
}

打电话先输出

原因:

锁的对象不同,前者锁的是Class类模板,后者锁的调用者

由于前者有延迟,锁对象不一样,后者不需要等解锁

两个对象+静态同步方法+普通同步方法

Phone4 phone = new Phone4();
Phone4 phone1 = new Phone4();
new Thread(()->{
    phone.sendSms();},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
    phone1.call();},"B").start();

谁没有延迟(谁的低),谁先执行,

锁的对象不同,前者锁的是Class类模板,后者锁的调用者

由于前者有延迟,锁对象不一样,后者不需要等解锁

总结:

一个是new出来的对象 ,是一个具体的实例,可以多个同一类型的实例,那么锁就有可能不一样了

一个是Class类模板,锁的是这个类型的对象模板,Class模板全局唯一,锁的对象一定一样

五.集合类不安全

List不安全

代码

public class ListTest {
    public static void main(String[] args) {
        List<String> 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();
        }
    }
}

错误:

Exception in thread “0” Exception in thread “4” Exception in thread “2” java.util.ConcurrentModificationException

并发修改异常

我们发现并发下List是不安全的

解决方案
1.使用Vector<>()
List<String> list = new Vector<>();

为什么安全呢?

我们的Vector源码中,官方在添加元素的时候给我们写了synchronized修饰add方法(比ArrayList早出来)

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}
2.List顶层老大Collections工具类
List<String> list = Collections.synchronizedList(new ArrayList<>());

把我们的List变成线程安全

3.CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
//CopyOnWriteArrayList源码
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}
private transient volatile Object[] array;

CopyOnWrite 写入时赋值

COW计算机程序设计领域的一种优化策略

多个线程调用的时候,list,读取的时候是固定的,写入的时候(覆盖操作)在写入的时候避免覆盖,造成数据问题

优点:

我们的Vector的add方法是synchronized来修饰了,效率比较低

我们再来看看CopyOnWriteArrayList的add方法

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

我们可以看出,CopyOnWriteArrayList是用lock锁,效率高

Set不安全

代码:

public class SetTest {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        for (int i=0;i<30;i++)
        {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

同理错误类型为:java.util.ConcurrentModificationException

解决方案
1.Collections工具类
Set<String> set = Collections.synchronizedSet(new HashSet<>());
2.CopyOnWriteSet
Set<String> set = new CopyOnWriteArraySet<>();
HashSet底层源码

HashSet就是一个HashMap

public HashSet() {
    map = new HashMap<>();
}
//HashSet的add方法
//set本质就是map的key,key是唯一的,所以set是无序的
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//常量
HashMap不安全
//map是这样用的么,
//默认等价于什么
Map<String,String> map = new HashMap<>();
Map<String,String> map = new HashMap<>(16,0.75);

Map源码

	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16  位运算16

    static final int MAXIMUM_CAPACITY = 1 << 30;


    static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子

//初始容量
public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

/**
 * Constructs an empty <tt>HashMap</tt> with the default initial capacity
 * (16) and the default load factor (0.75).
 */
//加载因子
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

代码

public class MapTest {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();
        for(int i=0;i<=60;i++)
        {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

也会有并发修改错误

解决方案
1.Collections工具类
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
2.ConcurrentHashMap
Map<String,String> map = new ConcurrentHashMap<>();

//ConcurrentHashMap源码
public ConcurrentHashMap() {
}
public ConcurrentHashMap(int initialCapacity) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException();
        int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
                   MAXIMUM_CAPACITY :
                   tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
        this.sizeCtl = cap;
    }

六.Callable

1.可以有返回值

2.可以抛出异常

3.方法不同,run()/start

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

函数式接口

我们发现函数泛型就是我们返回值的类型,泛型写那种类型,我们就返回那种类型

那么我们如何启动Callable呢?

我们看源码发现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2dwA682L-1605609859700)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201114184939059.png)]

Thread的构造函数重载都是Runable类型

那么Callable怎么跟Thread搭上关系

package com.hkd.callable;

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

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/14 18:45
 * @Description TODO
 * @pojectname 线程相关
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //怎么启动callable
        //new Thread(new Runnable()).start();
        // new Thread(new FutureTask<>(Callable)).start();;
        new Thread().start();
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();//结果会被缓存,效率高
        Object o = futureTask.get();
        System.out.println(o);
    }
}

class MyThread implements Callable<String>{


    @Override
    public String call() throws Exception {
        return "1234";
    }
}

细节:

1.有缓存的

2.结果可能需要等待,会阻塞

七.JUC常用的辅助类

1.CountDownLatch
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        //总数是6,必须要执行任务的时候,再使用
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i=1;i<=6;i++)
        {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"走了");
                //倒计时完毕  减一
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        //等待计数器归零然后在向下执行
        countDownLatch.await();
        System.out.println("关门");
    }
}

原理:

countDownLatch.countDown(); :数量减一

countDownLatch.await();:等待计数器归零,然后在向下执行

每次有线程调用,他就会减一,当计数器为0的时候,await就会被唤醒,然后执行

2.CyclicBarrier

加法计数器

一个只是计数,一个是计数完,调用线程

public class CyclicBarrierTest {
    //集齐七颗龙珠,召唤龙珠
    //召唤龙珠的线程
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier =new CyclicBarrier(7,()->{
            System.out.println("召唤神龙");
        });
        for(int i=1 ;i<=7;i++ )
        {
            final int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集第"+temp+"颗龙珠");
                try {
                    cyclicBarrier.await();//等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

我们会在循环的时候不停的调用线程收集龙珠,直到7颗龙珠,也就是7个线程调用完,才会执行下面的操作

如果我们

new CyclicBarrier(8,()->{
System.out.println(“召唤神龙”);
});

然而我们的for还是7,那么他就会一直等待,收集满8个才会向下执行,这个时候就卡死到这了

3.Semaphore

Semaphore:信号量

6个汽车,三个车位 123进 456等 3走了可能4要进来

这个int可以理解为线程数量(停车位)

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);
                    System.out.println(Thread.currentThread().getName()+"离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();

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

原理:

**acquire()😗*获得,假如已经满了,其他的就会等待,等到释放为止

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

作用:

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

八.读写锁

也叫作: 独占锁:写锁 一次只能被一个线程占有

​ 共享锁:读锁 一次可以被多个线程占用

读的时候可以被多线程同时读

写的时候,只能有一个线程去写

代码
public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for (int i=1;i<=5;i++)
        {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }
        for (int i=1;i<=5;i++)
        {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}
/**
 *自定义缓存
 */
class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();
    //存 写
    public void put(String key, Object value){
        System.out.println(Thread.currentThread().getName()+"写入数据"+key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"写入完毕");
    }
    //取 读
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"读取数据"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取数据OK"+key);
    }
}

问题

在这里插入图片描述

解决办法

将我们的自定义缓存加读写锁

class MyCacheLock{
    private volatile Map<String,Object> map = new HashMap<>();
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    //存 写
    public void put(String key, Object value){
        lock.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 {
            lock.writeLock().unlock();
        }
    }
    //取 读
    public void get(String key){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取数据"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取数据OK"+key);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}
小结

读——读 可以共存

读——写 不可以共存的

写——写 不能共存的

九.阻塞队列

阻塞队列:

在这里插入图片描述

在这里插入图片描述

BlockingQueue

在这里插入图片描述

在这里插入图片描述

使用场景

多线程并发处理,线程池

四组API

方式抛出异常不会抛出异常阻塞等待超时等待
添加add()offer()put()offer(,)
移除remove()poll()take()poll(,)
判断首尾element()pick()--
1.抛出异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D3V0DQxc-1605609859710)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201114204944870.png)]

public boolean add(E e) {
    return super.add(e);
}

源码中队列的add方法是返回一个boolean值,添加成功就为true,错误为false

错误类型:java.lang.IllegalStateException: Queue full

说明队列已满

public E remove() {
    E x = poll();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

源码remove是不用传任何参数的。返回值是刚才我们弹出的元素

public E element() {
    E x = peek();
    if (x != null)
        return x;
    else
        throw new NoSuchElementException();
}

正常情况下:

public static void test1(){
    //队列大小
    ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
    System.out.println(arrayBlockingQueue.add("a"));
    System.out.println(arrayBlockingQueue.add("b"));
    System.out.println(arrayBlockingQueue.add("c"));
    System.out.println("===========");
    System.out.println(arrayBlockingQueue.remove());
    System.out.println(arrayBlockingQueue.remove());
    System.out.println(arrayBlockingQueue.remove());
}

这时候队列已经没有元素的,如果我们再去取,就会报错

同样的没有元素的情况下,判断队首元素也会是这个错误

Exception in thread “main” java.util.NoSuchElementException:没有元素了

2.不会抛出异常

队满返回false,队空取元素以及判断队首为null。

public boolean offer(E e) {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        if (count == items.length)
            return false;
        else {
            enqueue(e);
            return true;
        }
    } finally {
        lock.unlock();
    }
}
public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}
public E peek() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return itemAt(takeIndex); // null when queue is empty
    } finally {
        lock.unlock();
    }
}

不同点:

我们可以看出,相对于抛出错误来说,不抛错误的方法中都加锁了

相同点:

添加元素方法都有返回值

成功为true

3.阻塞等待
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();
        enqueue(e);
    } finally {
        lock.unlock();
    }
}

这个方法是没有返回值的,我们可以发现,在加锁的基础上,我们的这个方法调用了await()方法,也就是说,队列满的情况下,我们会一直等,程序会卡死

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}

同理取出元素也是如此

4.超时等待
arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS);
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        enqueue(e);
        return true;
    } finally {
        lock.unlock();
    }
}

原理:

添加一个元素,如果队满,就等待参数规定的时间,如果还是满,就返回false

同理,取出也是如此

超出我们的时间取不出来就会爆出null,程序结束

arrayBlockingQueue.poll(2,TimeUnit.SECONDS);

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock()        
        }
    }

SynchronousQueue同步队列

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

即最多只等放一个元素

put(),take()

public class SyschronousQueneDemo {
    public static void main(String[] args) {
        BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"放PUT 1");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName()+"放PUT 2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName()+"放PUT 3");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"T2").start();
    }
}

十.线程池

三大方法,七大参数,四种拒绝策略

池化技术

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

创建or销毁(关闭)非常消耗资源

解决办法就是池化技术:eg 线程池,内存池,连接池,对象池

事先准备好一些资源,有人要用,就来这个池中拿,用完之后

好处

1.降低资源消耗

2.提高响应的速度

3.方便管理

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

三大方法

阿里巴巴开发手册规定

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明: Executors 返回的线程池对象 的弊端 如下
1 FixedThreadPool 和 SingleThread Pool
允许的请求队列长度为 Integer.MAX_VALUE,可 能会堆积大量的请求,从而导致 OOM。
2 CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量 为 Integer.MAX_VALUE 可能会创建大量的线程,从而导致 OOM。

1.单个线程池

Executors.newSingleThreadExecutor()

        //单个线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        try {
            for (int i=0;i<10;i++)
            {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }

即使我们通过for调用再多的线程方法,始终用的都是线程池中的一个线程在工作

2.固定大小的线程池

Executors.newFixedThreadPool(5)

//创建一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
try {
    for (int i=0;i<10;i++)
    {
        executorService.execute(()->{
            System.out.println(Thread.currentThread().getName()+"OK");
        });
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    executorService.shutdown();
}

我们创建线程池的时候,给定的参数就是线程池中线程存在数量的最大值

即使我们通过for循环创建调用多次,始终都是5个线程在工作

3.可伸缩的线程池

Executors.newCachedThreadPool();

//        //可伸缩的线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        try {
            for (int i=0;i<100;i++)
            {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }

    }

字面意思,我们可以通过for来控制,for中调用线程次数多,那线程池中线程的个数就会多,线程池创建线程个数,跟for循环次数有关,但是不一定,因为还跟CPU有关,比如for是1000个,我们的线程不一定是1000,有个能100都不到

七大参数

三大方法的源码分析

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

我们发现这里的源码都是

new ThreadPoolExecutor

他的源码:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
//调用了this方法
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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

corePoolSize:核心线程池大小

maximumPoolSize:最大核心线程池大小

keepAliveTime:超时多久没人调用就会释放

TimeUnit unit:超时单位

BlockingQueue workQueue:

阻塞队列

**ThreadFactory threadFactory:**线程工程,一般不用动

RejectedExecutionHandler handler:拒绝策略

看完这7个参数,我们回头看下我们的三大方法的源码

发现我们的前两个方法是基本相同的,固定大小的线程池方法,只是传递了一个参数,用来代替单个线程池中的那个1

而最后一个默认的核心线程数是0,但是最大核心线程数为

Integer.MAX_VALUE,约为21亿,这就是为什么消耗资源

手动创建线程池

在这里插入图片描述

keepAliveTime:超时多久没人调用就会释放,在这里的意思就是:345号窗口没人了,等了1个小时也没人来,那我就关了,只留我的核心线程

代码
package com.hkd.pool;

import java.util.concurrent.*;

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/14 23:39
 * @Description TODO
 * @pojectname 线程相关
 */
public class Demo {
    public static void main(String[] args) {
//        Executors.newSingleThreadExecutor();
        //自定义线程池
        ExecutorService threadPoolExecutor = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                //new ThreadPoolExecutor.AbortPolicy()//银行满了,还有人进来,不处理这个人了,抛出异常
                //new ThreadPoolExecutor.CallerRunsPolicy()//哪来的去哪里
               // new ThreadPoolExecutor.DiscardPolicy()//队列满了,丢掉任务,不会抛出异常
                new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试第一个线程竞争,如果竞争成功则会正常进行,失败,丢掉任务,也不会爆出异常
        );
        try {
            for (int i=1;i<=9;i++)
            {
                threadPoolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"OK");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPoolExecutor.shutdown();
        }
    }
}
四种拒绝策略

在这里插入图片描述

默认的拒绝策略

private static final RejectedExecutionHandler defaultHandler =
    new AbortPolicy();
AbortPolicy()

银行满了,还有人进来,不处理这个人了,抛出异常

java.util.concurrent.RejectedExecutionException:线程池满了,报错

CallerRunsPolicy()

哪来的去哪里,这里我们是main函数,所以他会回去让main线程代理执行他这个任务

DiscardPolicy

队列满了,丢掉任务,不会抛出异常

DiscardOldestPolicy

队列满了,尝试第一个线程竞争,如果竞争成功则会正常进行,失败,丢掉任务,也不会爆出异常

最大承载数

是我们new LinkedBlockingDeque<>(3)这个阻塞队列的大小+maximumPoolSize

上面的例子就是3+5 = 8

手写的代码分析

如果我们的for中只是调用一个threadPoolExecutor.execute那么,线程池中就只有一个线程工作

i=3时

线程池中有两个线程在工作,也就是我们的核心线程池数corePoolSize

直到i=5,我们的线程池中还是只有两个线程在工作

i=6时,超出我们的阻塞队列和核心线程数之和,就要启动最大核心线程数中的线程了,这个时候,我们工作的线程数变成了3个

同理i=7时 工作的线程数变成了4个

i=8时,达到了我们最大核心线程数5个,

在大,就跟拒绝策略有关了,参照上面的4种拒绝服务

CPU密集型和IO密集型

我们最大核心线程数设置为多少合适呢?

我们在用ThreadPoolExecutor创建线程池的时候,我们的最大核心线程数规定有以下两种

CPU密集型

根据我们的电脑(服务器)中处理器是几核的,规定最大核心线程数就为几,保持我们的CPU效率最高不能写死,要动态获取核数

Runtime.getRuntime().availableProcessors()

IO密集型

定义的最大核心线程数 **>**程序中十分耗IO的线程数

一般我们按照2倍规定

十一.四大函数式接口

只有一个方法的接口

例如foreach(参数),他的参数就是一个消费者函数接口

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();

Function函数型接口

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

有一个输入参数,有一个输出

只要是函数式接口,就可以用Lambda表达式简化

public static void main(String[] args) {
    Function<String,String> function = (str)->{return str;};
    System.out.println(function.apply("sfsa"));
}

Predicate断定型接口

只有一个输入参数,输出类型始终为boolean类型

public interface Predicate<T> {

    boolean test(T t);
public static void main(String[] args) {
    Predicate<String> predicate = (str)->{
        return str.isEmpty();
    };
    System.out.println(predicate.test("dsda"));
}

Consumer消费型接口

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

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
public static void main(String[] args) {
    Consumer<String> consumer =(str)->{
        System.out.println(str);
    };
    consumer.accept("sadsa");
}

打印字符串

Supplier供给型接口

没有参数,只有返回值

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}
public static void main(String[] args) {
    Supplier<Integer> supplier = ()->{
//      System.out.println("get");
        return 1024;
    };
    System.out.println(supplier.get());
}

十二.Strean流式计算

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-03fWcO0A-1605609859715)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201116161315974.png)]

我们用一到题来理解流式计算

题目要求

现有五个用户!筛选

  • 1、ID必须是偶数
  • 2、年龄必须大于23岁
  • 3.用户名转为大写字母
  • 4、用户名字母倒者排序
  • 5、只输出一个用户!
public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4= new User(4,"d",24);
        User u5 = new User(6,"e",25);
        List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
        list.stream()
                .filter(u->{return u.getId()%2==0;})//获得id为偶数
                .filter(u->{return u.getAge()>23;})//年龄大于23的用户
                .map(u->{return u.getName().toUpperCase();})//转换大写
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})//逆序
                .limit(1)//分页
                .forEach(System.out::println);

    }
}

十三.ForkJoin

什么是ForkJoin?====》分支合并

在这里插入图片描述

ForkJoin在jdk1.7之后推出

ForkJoin特点

工作窃取

在这里插入图片描述

B执行完了回去偷A的任务,提高效率,当然这里维护的是双端队列

使用步骤

1.forkjoinpool通过它来执行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kr1Q5Hp4-1605609859718)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201116163752230.png)]

2.计算任务

创建我们的ForkJoinTask

在这里插入图片描述

测试类

package com.hkd.forkjoin;

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

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/16 16:47
 * @Description TODO
 * @pojectname 线程相关
 */
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//            test1();//499999999500000000求和时间为7200
        test2();//求和时间为6129
        test3();//500000000500000000求和时间为231

    }

    public static void test1(){
        Long sum=0L;
        long start = System.currentTimeMillis();
        for (Long i=1L;i<10_0000_0000;i++)
        {
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println(sum+"求和时间为"+(end-start));

    }
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> forkJoinDemo = new ForkJoinDemo(0L,10_0000_0000L);
//        forkJoinPool.execute(forkJoinDemo);//执行  没有结果
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo);//提交   有结果
        submit.get();
        long end = System.currentTimeMillis();
        System.out.println("求和时间为"+(end-start));

    }
    public static void test3() {
        long start = System.currentTimeMillis();
        long sum;
        sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println(sum + "求和时间为" + (end - start));
    }
}

计算任务

public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    //临界值
    private Long temp = 10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }
    //计算方法
    @Override
    protected Long compute() {
            if ((end-start)<temp)
            {
                Long sum =0L;
                     for (Long i=start;i<=end;i++)
                    {
                            sum+=i;
                    }
                return sum;
            }else
            {
                long middle = (start+end)/2;
                ForkJoinDemo forkJoin = new ForkJoinDemo(start,middle);
                forkJoin.fork();//拆分任务,把任务亚入线程队列
                //分支合并计算
                ForkJoinDemo forkJoin2 = new ForkJoinDemo(middle + 1, end);
                forkJoin2.fork();
                return  forkJoin.join()+forkJoin2.join();
            }

    }
}

十四.异步回调

类似于我们客户端与服务器的异步通信Ajax

没有返回值的异步回调

public static void main(String[] args) throws ExecutionException, InterruptedException {
    //没有返回值的runAsync异步回调
    CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"runAsync=>void");
    });
    System.out.println("11111");
    completableFuture.get();
}

我们虽然一开始就执行了异步回调,但是我们没有立马获取,而是走的主线程的输出,其实这个时候我们已经获得我们的异步回调中的信息了,只是没输出,如果我们的异步回调没有设置延迟的话,他在11111输出后就会立马输出

有返回值的异步回调

public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
            CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
                System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
                return 1024;
            });
        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t);
            System.out.println("u=>" + u);
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233;
        }).get());
    }
}

在这里插入图片描述

n这个参数是什么呢,我们在线程中,写一个错误语句来看

在这里插入图片描述

可以看出,我们的t返回的是详细的错误信息

whenComplete源码

public CompletableFuture<T> whenComplete(
    BiConsumer<? super T, ? super Throwable> action) {
    return uniWhenCompleteStage(null, action);
}

它调用了一个BiConsumer,

@FunctionalInterface
public interface BiConsumer<T, U> {

    /**
     * Performs this operation on the given arguments.
     *
     * @param t the first input argument
     * @param u the second input argument
     */
    void accept(T t, U u);

还是一个消费型接口,只不过这次传参是两个

十五.JMM

什么是JMM

JMM是一个java内存模型,是一个概念模型,约定

JMM同步的约定

1.线程解锁前,必须把共享变量立刻刷回主存

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

3.加锁和解锁是同一把锁

八种操作

在这里插入图片描述

问题:

线程B修改了flag的值,但是A不能及时可见

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

我们那一个简单的例子

public class JMMDemo {
    private static int num=0;

    public static void main(String[] args) {
        new Thread(()->{
            while (num==0)
            {}
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println(num);

    }
}

我们出现的问题就是,我们的线程一直在循环,但是我们的主线程已经修改了num=1,但是我们的另一个线程不能及时的读取最新的消息,导致程序一直在运行,怎么解决呢?

引入Volatile关键字

十六.Volatile

请你谈谈Volatile的理解

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

1.保证可见性

上面的JMM例子说明我们的线程对主内存的变化是不知道的

修改:

private volatile static int num=0;

我们发现线程不在一直循环了,不加volatile会导致程序死循环

2.不保证原子性

原子性:不可分割

public class VDemo1 {
    private volatile static int num =0;

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

我们发现加volatile与不加都不是2W这个值,这也就说明,我们的线程有些还是被占用了

当然如果我们加lock和synchronzeid是可以解决这个问题,但是不加这个怎么解决呢

我们的add()中的num++并不是原子性操作

在这里插入图片描述

使用原子类来,解决原子性问题

private volatile static AtomicInteger num = new AtomicInteger();


public  static void add(){
    num.getAndIncrement();//AtomicInteger+1方法并不是简单的+1方法  底层原理是CAS
}

原子类底层都直接跟操作系统挂钩

是在内存中修改值!

Unsafe是一个很特殊的存在,在CAS中详细介绍

3.禁止指令重排

什么是指令重排

计算机程序并不是按照我们写的程序来执行的

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

eg
int x = 1;  //1
int y = 2;	//2
x = x + 5;	//3
y = x * x;	//4
我们所期望的顺序是  1234 
    但是执行的时候可能是 	2134  1324
    但不会是  4123

可能造成的结果:abxy默认都是0

线程A线程B
x=ay=b
b=1a=2

正常的结果是 x=0; y=0但是由于指令重排

线程A线程B
b=1a=2
x=ay=b

指令重排结果就成了 x = 2; y =1

volatile可以避免指令重排

内存屏障。CPU指令:作用

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

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

在这里插入图片描述

内存屏障那个地方用的多呢?=====>单例模式

十七.单例模式

饿汉式

public class Hungry {
    //可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];
    //构造器私有,别人没办法来new,保证唯一
    private Hungry(){}
    private final static Hungry HUNGRY = new Hungry();
    public static Hungry getInstance(){
        return HUNGRY;
    }
}

匿名内部类

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

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

懒汉式

package com.hkd.single;

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/16 20:11
 * @Description 懒汉式模式
 * @pojectname 线程相关
 */
public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"OK");
    }


    private static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if (lazyMan == null)
        {
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
    //单线程确实OK
    //多线程
    public static void main(String[] args) {
        for (int i =0;i<10;i++)
        {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }


}

我们发现有的时候1个线程在运行,有的时候变多了,我们的解决办法就是双重检测锁模式的懒汉式单例

DCL懒汉式

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

问题:

lazyMan = new LazyMan();不是一个原子性操作

/**
 * 1.分配内存空间
 * 2.执行构造方法,初始化对象
 * 3.把这个对象指向这个空间
 *
 * 底层有可能就会发生指令重排
 * 我们期望的123
 * 有可能发生的132 A线程没有问题
 * 但是如果有线程B,线程B就认为不是null了
 *          直接走return
 *          但是lazyMan还没有完成构造
 */

改进

使用volatile关键字

private volatile static LazyMan lazyMan;
反射破坏单例怎么解决
public static void main(String[] args) throws Exception {
    LazyMan instance = LazyMan.getInstance();
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);//无视私有构造器
    LazyMan lazyMan = declaredConstructor.newInstance();
    System.out.println(instance);
    System.out.println(lazyMan);
}

如果我们的单例没被破坏,那么新建的应该是一个对象

但是我们运行发现,不是同一个对象,说明我们的单例模式被破坏了

第一次改进

我们上边的代码是一个使用自己的私有构造器,一个是通过反射的构造器,解决办法

改进我们的私有构造器

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

那么问题又来了,那要是两个对象都用反射来创建呢?

public static void main(String[] args) throws Exception {
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);//无视私有构造器
    LazyMan lazyMan = declaredConstructor.newInstance();
    LazyMan lazyMan1 = declaredConstructor.newInstance();
    System.out.println(lazyMan);
    System.out.println(lazyMan1);
}

我们发现我们改进的私有构造器又不行了

第二次改进

利用我们的标志位

private LazyMan() {
    synchronized (LazyMan.class)
    {
        if (flag == false)
        {
            flag = true;
        }else {
            throw new RuntimeException("不要试图使用反射来破坏");
        }
    }
}

但是我们又可以通过反射获取到私有字段,改变其可见性,在第二个对象创建前,再把其值改为false又可以破坏我们的单例模式了

public static void main(String[] args) throws Exception {
    Field flag = LazyMan.class.getDeclaredField("flag");
    flag.setAccessible(true);

    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);//无视私有构造器
    LazyMan lazyMan = declaredConstructor.newInstance();
    flag.set(lazyMan,false);
    LazyMan lazyMan1 = declaredConstructor.newInstance();
    System.out.println(lazyMan);
    System.out.println(lazyMan1);
}

怎么从根本解决呢?

源码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jzCYjPHJ-1605609859724)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201116204624852.png)]

枚举

枚举本身也是一个Class类

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class Test
{
    public static void main(String[] args) {
        EnumSingle enumSingle1 = EnumSingle.INSTANCE;
        EnumSingle enumSingle = EnumSingle.INSTANCE;

        System.out.println(enumSingle1);
        System.out.println(enumSingle);
    }
}

结果一样,我们想破坏它这种单例模式

编译文件

package com.hkd.single;

public enum EnumSingle {
    INSTANCE;

    private EnumSingle() {
    }

    public EnumSingle getInstance() {
        return INSTANCE;
    }
}

我们可以看到,反编译文件中是有无参构造函数的

但是我们通过反射的无参构造来创建对象发现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AwO45kBF-1605609859725)(C:\Users\王庆华\AppData\Roaming\Typora\typora-user-images\image-20201116205318642.png)]

没有无参构造器??IDEA骗了我们?但是我们反编译也发现有一个无参构造器,他也骗了我们?

我们再去找个反编译的工具jad,反编译发现

在这里插入图片描述

那我们改变我们反射的方式

Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);

然后我们终于发现了

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.hkd.single.Test.main(EnumSingle.java:25)

终于得到了我们想要的错误

十八.深入理解CAS

什么是CAS
在这里插入图片描述

Unsafe类

atomicInteger.getAndIncrement();
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
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 CasDemo {
    //compareAndSet比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
  	  atomicInteger.compareAndSet(2020,2021);
        //如果我期望的值达到了   那么就更新
        atomicInteger.getAndIncrement();
System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2020, 2021));

    }
}
比较并交换
public final boolean compareAndSet(int expect, int update) {
      return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
   } 

缺点:

1.底层是自旋锁,耗时

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

3.ABA问题

CAS:ABA问题

在这里插入图片描述

public class CasDemo {
    //compareAndSet比较并交换
    public static void main(String[] args) {
        //对于我们平时写的SQL操作来说:乐观锁!!
            //
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        //========捣乱线程=====
        atomicInteger.compareAndSet(2020,2021);
        System.out.println(atomicInteger.get());
        //=====我再改回去
        atomicInteger.compareAndSet(2021,2020);
        System.out.println(atomicInteger.get());
        //========期望的线程========
        atomicInteger.compareAndSet(2020, 6666);
        System.out.println(atomicInteger.get());
    }
}

我想知道谁动过了!!!!

那么就要引入知识:原子引用

十九.原子引用

对应乐观锁

package com.hkd.cas;

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

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/16 21:06
 * @Description TODO
 * @pojectname 线程相关
 */
public class CasDemo {
    //compareAndSet比较并交换
    public static void main(String[] args) {
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//获得版本号
            System.out.println("a获得的"+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("b获得的"+atomicStampedReference.getStamp());


            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 66, stamp, stamp + 1));
            System.out.println("b2获得的"+atomicStampedReference.getStamp());
        },"b").start();
    }
}

原理跟我们的乐观锁原理相同

大坑

【强制】所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。 说明:对于Integer var = ? 在-128至127范围内的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。

这是阿里巴巴开发手册规定的

也就是我们的泛型写的是Integer,Integer在缓存池中有可能存在复用问题

//initialStamp时间戳:相当于版本号
public AtomicStampedReference(V initialRef, int initialStamp) {
    pair = Pair.of(initialRef, initialStamp);
}
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,
                             int newStamp) {
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}

在这里插入图片描述

二十.锁的理解

1.公平锁、非公平锁

公平锁:不可以插队,先来后到

非公平锁:可以插队,超队现象,人家等不及啊,就会插你队,默认的机制

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

传入一个true参数就可以改变我们的锁类型

2.可重入锁

递归锁

在这里插入图片描述

Synchronized

//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()+"s s m");
        call();
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"c a l l ");
    }
}

正常情况下,我们A线程调用call 的时候,出了sms方法,按理说应该解锁(线程B就拿到锁了),但是无论怎么执行,A都要先执行完,也就是说,A线程调用完call方法才会释放锁

Lock

package com.hkd.lock;

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

/**
 * @author 王庆华
 * @version 1.0
 * @date 2020/11/16 22:05
 * @Description TODO
 * @pojectname 线程相关
 */
public class Demo2 {
    public static void main(String[] args) {
        Phone1 phone = new Phone1();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();

    }

}
class Phone1{
    Lock lock = new ReentrantLock();
    public void sms(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"s s m");
            call();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public  void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"c a l l ");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

本质一样,但是细节不怎么一样

我们先进入的sms,加锁后,进call的时候有拿到了一把锁,解锁也是,先解了call 的锁,然后解锁sms的锁

3.自旋锁

spinlock

手写

public class SpinLockDemo {
    //int 0
    //Thread null
    AtomicReference 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 TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();
        SpinLockDemo lock = new SpinLockDemo();
        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.myUnLock();
            }
        },"T1").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.myUnLock();
            }
        },"T2").start();
    }
}

我们的t1t2同时拿到,但是必须等ti解锁后t2才能解锁

4.死锁

什么是死锁

在这里插入图片描述

死锁的例子

public class Deadlockdemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new MyThread(lockA,lockB),"A线程").start();
        new Thread(new MyThread(lockB,lockA),"B线程").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()+"我现在的锁是===》"+lockA+"想要获得Get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB)
            {
                System.out.println(Thread.currentThread().getName()+"我现在的锁是===》"+lockB+"想要获得Get"+lockA);
            }
        }
    }
}

在这里插入图片描述

解决问题

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

2.使用jstack 进程号查看堆栈信息

ock();
try {
System.out.println(Thread.currentThread().getName()+"c a l l ");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}


**本质一样,但是细节不怎么一样**

我们先进入的sms,加锁后,进call的时候有拿到了一把锁,解锁也是,先解了call 的锁,然后解锁sms的锁

## 3.自旋锁

**spinlock**

手写

```java
public class SpinLockDemo {
    //int 0
    //Thread null
    AtomicReference 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 TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();
        SpinLockDemo lock = new SpinLockDemo();
        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.myUnLock();
            }
        },"T1").start();
        TimeUnit.SECONDS.sleep(1);
        new Thread(()->{
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.myUnLock();
            }
        },"T2").start();
    }
}

我们的t1t2同时拿到,但是必须等ti解锁后t2才能解锁

4.死锁

什么是死锁

[外链图片转存中…(img-H1RSyuPM-1605609859732)]

死锁的例子

public class Deadlockdemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new MyThread(lockA,lockB),"A线程").start();
        new Thread(new MyThread(lockB,lockA),"B线程").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()+"我现在的锁是===》"+lockA+"想要获得Get"+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB)
            {
                System.out.println(Thread.currentThread().getName()+"我现在的锁是===》"+lockB+"想要获得Get"+lockA);
            }
        }
    }
}

[外链图片转存中…(img-UdR7Kegx-1605609859733)]

解决问题

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

2.使用jstack 进程号查看堆栈信息

在这里插入图片描述
内容借鉴B站狂神说的教学视频哦,想学习的小伙伴可以关注下

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会写代码的花城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值