JUC并发编程

1、什么是JUC

java.util 工具包、包、分类

业务:普通的线程代码 Thread

Runnable:没有返回值,效率相比于Callable相对较低。

2、线程和进程

1、线程、进程

进程:一个程序。QQ.exe Music.exe 程序的集合

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

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

java真的可以开启线程吗?开不了的,调用本地方法start0(),Java无法直接操作硬件。

2、并发、并行

并发编程:并发、并行

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

  • CPU一核,模拟出来多条线程。

并行(多个人一起行走)

  • 多核,多个线程可以同时执行。
public class Test1 {
    public static void main(String[] args) {
        // 获取CPU的核数
        // CPU密集型、IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

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

所有的公司都很看重!!!

3、线程有几个状态

   public enum State {
        // 新生
        NEW,
		
        // 运行状态
        RUNNABLE,

        // 阻塞
        BLOCKED,

        // 等待、死死的等待
        WAITING,

        // 超时等待
        TIMED_WAITING,

        // 终止
        TERMINATED;
    }

4、wait、sleep

  1. 来自不同的类

    wait=>Object

    sleep=>Thread

  2. 关于锁的释放

    wait会释放锁,sleep不会释放锁。

  3. 使用的范围是不同的

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

    sleep可以在任何地方睡

  4. 是否需要捕获异常。

    wait:不需要捕获异常。

    sleep:必须要捕获异常。

3、Lock(重点)

1、传统 Synchronized

package com.he.demo01;

/**
 *  真正的多线程开发,公司中的开发,降低耦合性
 *  线程就是一个单独的资源类,没有任何附属的
 *  1、属性、方法
 */
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        // 并发:多个线程操作同一个资源类,把资源类丢入线程就可以了
        Ticket ticket = new Ticket();

        //@FunctionalInterface 函数式接口,jdk1.8之后,可以简化lambda表达式 () -> {}
        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();
    }

}

/**
 *  资源类
 */
class Ticket{
    // 属性、方法
    private int number = 50;

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

2、Lock接口

公平锁:十分公平,先来后到。

非公平锁:非常不公平,可以插队。(默认)

package com.he.demo01;

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

/**
 *  真正的多线程开发,公司中的开发,降低耦合性
 *  线程就是一个单独的资源类,没有任何附属的
 *  1、属性、方法
 */
public class SaleTicketDemo02 {
    public static void main(String[] args) {
        // 并发:多个线程操作同一个资源类,把资源类丢入线程就可以了
        Ticket2 ticket = new Ticket2();

        //@FunctionalInterface 函数式接口,jdk1.8之后,可以简化lambda表达式 () -> {}
        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();
    }

}

/**
 *  资源类
 *  Lock三部曲
 *  1、new ReentrantLock()
 *  2、lock.lock();加锁
 *  3、lock.unlock();解锁
 */
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();// 解锁
        }
    }
}

3、Synchronized 和 Lock 的区别

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

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

3、Synchronized 是全自动的,会自动释放锁,Lock必须手动释放锁,如果不释放锁,就会产生死锁

4、Synchronized 线程1(获得锁,阻塞)、线程2(等待,傻傻的等),Lock就不一定会等待下去。

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

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

4、锁是什么?如何判断锁的是谁?

4、生产者和消费者问题

面试重点:单例模式、排序算法、生产者和消费者、死锁。

1、生产者和消费者问题Synchronized版

package com.he.pc;

/**
 *  线程之间的通信问题:生产者和消费者问题!  等待唤醒  通知唤醒
 *  线程交替执行  A   B 操作同一个变量  num=0
 *  A  num+1
 *  B  num-1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(() -> {
            for (int i = 0; i < 60; 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++;
        // 通知其他线程,我+1完毕了
        this.notifyAll();
        System.out.println(Thread.currentThread().getName() + "=>" + number);
    }

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

2、问题存在,A、B、C、D4个线程!

线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒,虽然这在实践中很少会发生,但应用程序必须通过测试应该使县城被唤醒的条件来防范,并且如果条件不满足则继续等待,换句话说,等待应该总是出现在循环当中,就像这样

synchronized(Obj) {
    while(<condition does not hold>)
        obj.wait(timeout);
}

if 改为 while 判断就可以

package com.he.pc;

/**
 *  线程之间的通信问题:生产者和消费者问题!  等待唤醒  通知唤醒
 *  线程交替执行  A   B 操作同一个变量  num=0
 *  A  num+1
 *  B  num-1
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(() -> {
            for (int i = 0; i < 60; 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){
            // 等待
            this.wait();
        }
        number++;
        // 通知其他线程,我+1完毕了
        this.notifyAll();
        System.out.println(Thread.currentThread().getName() + "=>" + number);
    }

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

3、JUC版的生产者和消费者

在这里插入图片描述

代码实现:

package com.he.pc;

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

/**
 *  线程之间的通信问题:生产者和消费者问题!  等待唤醒  通知唤醒
 *  线程交替执行  A   B 操作同一个变量  num=0
 *  A  num+1
 *  B  num-1
 */
public class B {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

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

    }
}

// 判断等待,业务,通知
class Data2{// 数字 资源类

    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    // +1
    public void increment() throws InterruptedException {
        lock.lock();
        try {
            while (number != 0){
                // 等待
                condition.await();
            }
            number++;
            // 通知其他线程,我+1完毕了
            condition.signalAll();
            System.out.println(Thread.currentThread().getName() + "=>" + number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    //-1
    public void decrement() throws InterruptedException {
        lock.lock();
        try {

            while (number == 0){
                // 等待
                condition.await();
            }
            number--;
            // 通知其他线程,我-1完毕了。
            condition.signalAll();
            System.out.println(Thread.currentThread().getName() + "=>" + number);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

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

4、Condition 精准的通知和唤醒线程

代码测试:

package com.he.pc;

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

/**
 *  线程之间的通信问题:生产者和消费者问题!  等待唤醒  通知唤醒
 *  线程交替执行  A   B 操作同一个变量  num=0
 *  A  num+1
 *  B  num-1
 */
public class C {
    public static void main(String[] args) {
        Data3 data3 = new Data3();

        new Thread(() -> {
            for (int i = 0; i < 60; i++) {
                data3.printA();
            }
        },"A").start();
        new Thread(() -> {            for (int i = 0; i < 60; i++) {
            data3.printB();
        }},"B").start();
        new Thread(() -> {            for (int i = 0; i < 60; 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;


    public void printA() {
        lock.lock();

        try {
            while (number != 1) {
                condition1.await();
            }
            System.out.println("A");
            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("B");
            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("C");
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

5、8锁现象

如何判断锁的是谁?

对象、Class

package com.he.lock8;

import java.util.concurrent.TimeUnit;

/**
 *  8锁,就是关于锁的八个问题
 *  1、标准情况下,两个线程先打印发短息还是打电话? 1 发短信  2 打电话
 *  2、sendSms延迟4秒,两个线程先打印发短信还是打电话? 1 发短息 2打电话
 *
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() ->{
            phone.sendSms();
        },"A").start();

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

class Phone{
    /**
     *  synchronized 锁的对象是方法的调用者!
     *  两个方法用的是同一个锁,谁先拿到谁执行!
     */
    public synchronized void sendSms(){
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendSms");
    }
    public synchronized void call(){
        System.out.println("call");
    }

}
package com.he.lock8;

import java.util.concurrent.TimeUnit;

/**
 *  3、加一个普通方法,先执行发短信还是hello? hello
 *  4、两个对象,两个同步方法,发短息还是打电话? 打电话
 */
public class Test2 {
    public static void main(String[] args) {
        //  两个对象
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        new Thread(() ->{
            phone1.sendSms();
        },"A").start();

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

        new Thread(() ->{
            phone2.call();
        },"B").start();
    }
}

class Phone2{
    /**
     *  synchronized 锁的对象是方法的调用者!
     *  两个方法用的是同一个锁,谁先拿到谁执行!
     */
    public synchronized void sendSms(){
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendSms");
    }
    public synchronized void call(){
        System.out.println("call");
    }

    // 这里没有锁:不是同步方法
    public void hello() {
        System.out.println("hello");
    }

}
package com.he.lock8;

import java.util.concurrent.TimeUnit;

/**
 *  5、增加两个静态的同步方法,只有一个对象,先打印发短信还是打电话?  发短信
 *  6、两个对象,增加两个静态的同步方法,先打印发短信还是打电话?    发短信
 */
public class Test3{
    public static void main(String[] args) {
        //  两个对象的class类模板只有一个,所以锁只有一个
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        new Thread(() ->{
            phone1.sendSms();
        },"A").start();

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

        new Thread(() ->{
            phone2.call();
        },"B").start();
    }
}

class Phone3{
    /**
     *  synchronized 锁的对象是方法的调用者!
     *  static 静态方法 类一加载就有了 锁的是Class,class只有一个,全局唯一
     */
    public static synchronized void sendSms(){
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendSms");
    }
    public static synchronized void call(){
        System.out.println("call");
    }

    // 这里没有锁:不是同步方法
    public void hello() {
        System.out.println("hello");
    }

}
package com.he.lock8;

import java.util.concurrent.TimeUnit;

/**
 *  1、1个静态的同步方法,一个普通的同步方法,一个对象,先打印发短信还是打电话?  打电话
 *  2、一个静态的同步方法,一个普通的同步方法,两个对象,先打印发短信还是打电话? 打电话
 */
public class Test4 {
    public static void main(String[] args) {
        //  两个对象的class类模板只有一个,所以锁只有一个
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        new Thread(() ->{
            phone1.sendSms();
        },"A").start();

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

        new Thread(() ->{
            phone2.call();
        },"B").start();
    }
}

class Phone4{
    // 静态同步方法,锁的是Class
    public static synchronized void sendSms(){
        // 捕获
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendSms");
    }
    // 普通同步方法,锁的是对象
    public synchronized void call(){
        System.out.println("call");
    }

}

小结:

new:this具体的一个手机

static:Class唯一的一个模板

6、集合类不安全

1、List不安全

package com.he.unsafe;

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

/**
 *  java.util.ConcurrentModificationException  并发修改异常
 */
public class ListTest {
    public static void main(String[] args) {
        // 并发下 ArrayList 不安全
        /**
         *  解决方案:
         *  1、List<String> list = new Vector<>();
         *  2、List<String> list = Collections.synchronizedList(new ArrayList<>());
         *  3、List<String> list = new CopyOnWriteArrayList<>();
         */
        // CopyOnWrite 写入时复制  COW  计算机程序设计领域的一种优化策略
        // 多个线程调用的时候,List,读取的时候,固定的,写入(覆盖)
        // 在写入的时候避免覆盖 造成数据问题。/
        // CopyOnWriteArrayList 比vector牛逼在哪里?
        // COW用的是lock锁  vector用的是synchronized效率较低
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

2、Set不安全

package com.he.unsafe;

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

/**
 *  ConcurrentModificationException
 */
public class SetTest {
    public static void main(String[] args) {
        /**
         *  同理,两种解决方案
         *  1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
         *  2、Set<String> set = new CopyOnWriteArraySet<>();
         */
//        Set<String> set = new HashSet<>();
//        Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

HashSet的底层是什么?

HashSet的底层就是HashMap

在这里插入图片描述

在这里插入图片描述

3、HashMap不安全

回顾Map的基本操作

package com.he.unsafe;

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

/**
 * ConcurrentModificationException
 */
public class MapTest {
    public static void main(String[] args) {
        // 工作中是这样用的吗? 不是,工作中不用 HashMap
        // 默认等价于什么? new HashMap<>(16,0.75)
//        Map<String,String> map = new HashMap<>();
        //唯一的家庭作业   ConcurrentHashMap原理
        java.util.Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,4));
                System.out.println(map);
            },String.valueOf(i)).start();
        }

        //加载因子
    }
}

7、Callable(简单)

在这里插入图片描述

与Runnable不同的是:

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

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

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        MyThread myThread = new MyThread();

        // 适配类
        FutureTask futureTask = new FutureTask(myThread);
        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();  // 只会打印一次  执行call()方法  因为结果会被缓存。

        Integer o = (Integer) futureTask.get();// 这个get方法可能会产生阻塞
        System.out.println(o);

    }
}

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {

        System.out.println("执行call()方法");
        return 1024;
    }
}

细节:

  1. 有缓存
  2. 结果可能需要等待,会阻塞。

8、常用的辅助类

1、CountDownLatch

在这里插入图片描述

package com.he.add;

import java.util.concurrent.CountDownLatch;
// 计数器
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 总数是6,必须要执行任务的时候在使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "Go out");
                countDownLatch.countDown();// 数量-1
            },String.valueOf(i)).start();
        }

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

        System.out.println("关门");
    }
}

原理:

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

countDownLatch.await(); // 等待计数器归零,再往下执行。

每次有现成调用counntDown() 数量-1,假设计数器归零,countDownLatch() 就会被唤醒,继续执行。

2、CyclicBarrier

在这里插入图片描述

加法计数器

package com.he.add;

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

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /**
         *  集齐7颗龙珠召唤神龙
         */
        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();
        }
    }
}

3、Semaphore

Semaphore:信号量

在这里插入图片描述

抢车位:

6车–3个停车位

package com.he.add;

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

public class SemaphoreDemo {
    public static void main(String[] args) {
        // 控制线程数量
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                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();
        }
    }
}

原理:

semaphore.acquire();:获得,假设已经满了,等待,等待被释放为止。

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

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

9、读写锁

在这里插入图片描述

package com.he.rw;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCacheLock myCacheLock = new MyCacheLock();
        for (int i = 1; i <= 5; i++) {
            final int temp = i;
            new Thread(() -> {
                myCacheLock.put(temp +"",temp + "");
            },String.valueOf(i)).start();
        }
        for (int i = 6; i <= 10; i++) {
            final int temp = i;
            new Thread(() -> {
                myCacheLock.get(temp +"");
            },String.valueOf(i)).start();
        }
    }
}


/**
 *  自定义缓存
 */
class MyCacheLock{
    private volatile Map<String,Object> map = new HashMap<>();
    // 读写锁,更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

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

    public void get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取成功");
    }
}




/**
 *  自定义缓存
 */
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);
        map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取成功");
    }
}

10、阻塞队列

阻塞

队列

在这里插入图片描述

阻塞队列:

在这里插入图片描述

BlockingQueue不是新的东西

什么情况下会使用阻塞队列:多线程并发处理、线程池!

在这里插入图片描述

在这里插入图片描述

学会使用队列

添加、移除

四组API

方式抛出异常不会抛出异常,有返回值阻塞 等待超时等待
添加add()offer()put()offer()
移除remove()poll()take()poll()
检测队首元素element()peek()//
  1. 抛出异常
  2. 不会抛出异常
  3. 阻塞等待
  4. 超时等待
package com.he.bq;

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

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

    }
    /**
     *  抛出异常
     */
    public static void test1() {
        // 队列的大小
        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());
    }

    /**
     * 有返回值没有异常
     */
    public static void test2() {
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        System.out.println(blockingQueue.offer("d"));// false  不会抛出异常

        System.out.println("========================");

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());// null  不会抛出异常

    }

    /**
     *  一直等待,一直阻塞
     */
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);

        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        // blockingQueue.put("d"); 程序不会结束,一直等待,一直阻塞

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        // System.out.println(blockingQueue.take()); 程序不会结束,一直等待,一直阻塞


    }

    /**
     *  超时等待
     * @throws InterruptedException
     */
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue<Object> 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("c",2, TimeUnit.SECONDS));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
    }
}

1、SynchronousQueue同步队列

没有容量

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

put、take

package com.he.bq;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

/**
 *  同步队列
 *  和其他的BlockingQueue 不一样,SynchronousQueue 不存储元素
 *  put了一个元素,必须从里面先take取出来,否则不能再put进去
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<String>();

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

        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "get 1");
                System.out.println(blockingQueue.take());
                System.out.println(Thread.currentThread().getName() + "get 2");
                System.out.println(blockingQueue.take());
                System.out.println(Thread.currentThread().getName() + "get 3");
                System.out.println(blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        },"T2").start();
    }
}

11、线程池(重点)

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

1、池化技术

程序的运行:本质:占用系统资源!优化资源的使用=>池化技术

线程池、连接池、内存池、对象池…

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

线程池的好处:

  1. 降低资源消耗
  2. 提高响应速度。
  3. 方便管理

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

2、线程池:三大方法

在这里插入图片描述

package com.he.pool;

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

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

        try {
            for (int i = 0; i < 10; i++) {
                // 使用了线程池之后,要使用线程池来创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

3、线程池:七大参数

源码分析

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

// 本质:ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,// 核心线程池大小
                          int maximumPoolSize,//最大核心线程池大小
                          long keepAliveTime,//超时了,没有人调用,就会释放
                          TimeUnit unit,// 超时的单位
                          BlockingQueue<Runnable> workQueue,//阻塞队列
                          ThreadFactory threadFactory,// 线程工厂,创建线程的,一般不用动
                          RejectedExecutionHandler handler// 拒绝策略) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ?
        null :
    AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

手动创建一个线程池

package com.he.pool;

import java.util.concurrent.*;

// Executors 工具类、三大方法

/**
 *  new ThreadPoolExecutor.AbortPolicy());// 银行满了,但是还有人进来,就不处理这个人的了,抛出异常
 *  new ThreadPoolExecutor.CallerRunsPolicy());// 哪来的去哪里!这个案例由main线程执行
 *  new ThreadPoolExecutor.DiscardPolicy()); // 队列满了,就会丢掉任务,不会抛出异常
 *  new ThreadPoolExecutor.DiscardOldestPolicy()); // 队列满了,尝试和最早的竞争,如果竞争失败就没了,如果成功就会执行,不会抛出异常
 */
public class Demo01 {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                // 自定义线程池
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()); // 队列满了,尝试和最早的竞争,如果竞争失败就没了,如果成功就会执行,不会抛出异常

        try {
            // 最大承载
            for (int i = 0; i < 10; i++) {
                // 使用了线程池之后,要使用线程池来创建线程
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

4、四种拒绝策略

在这里插入图片描述

/**
 *  new ThreadPoolExecutor.AbortPolicy());// 银行满了,但是还有人进来,就不处理这个人的了,抛出异常
 *  new ThreadPoolExecutor.CallerRunsPolicy());// 哪来的去哪里!这个案例由main线程执行
 *  new ThreadPoolExecutor.DiscardPolicy()); // 队列满了,就会丢掉任务,不会抛出异常
 *  new ThreadPoolExecutor.DiscardOldestPolicy()); // 队列满了,尝试和最早的竞争,如果竞争失败就没了,如果成功就会执行,不会抛出异常
 */

5、小结和拓展

// 最大线程该如何设置
// 1、CPU密集型 几核,就是几,可以保持CPU效率最高!
// 2、IO密集型  判断你的程序中十分耗io的线程,>
//      程序  15个大型任务,io十分占用资源!

了解io密集型,cpu密集型

ExecutorService threadPool = new ThreadPoolExecutor(
                // 自定义线程池
                2,
                Runtime.getRuntime().availableProcessors(),
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy()); // 队列满了,尝试和最早的竞争,如果竞争失败就没了,如果成功就会执行,不会抛出异常

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

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

1、函数式接口

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

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

在这里插入图片描述

2、Function函数式接口

package com.he.function;

import java.util.function.Function;

/**
 *  Function 函数型接口,有一个输入参数,有一个输出
 *  只要是函数式接口,我们就可以使用Lambda表达式简化
 */
public class Demo01 {
    public static void main(String[] args) {
//        Function function = new Function<String,String>() {
//            @Override
//            public String apply(String str) {
//                return str;
//            }
//        };

        Function<String,String> function = (str) -> {return str;};
        System.out.println(function.apply("asd"));
    }
}

3、Predicate:断定型接口:有一个参数,返回值只能是布尔值

package com.he.function;

import java.util.function.Predicate;

/**
 *  断定型接口:有一个输入参数,返回值只能是布尔值!!!
 */
public class Demo02 {
    public static void main(String[] args) {
        // 判断字符串是否为空
//        Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String str) {
//                return str.isEmpty();
//            }
//        };

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

4、Consumer:消费型接口,只有输入,没有输出

package com.he.function;

import java.util.function.Consumer;

/**
 *  Consumer  消费型接口,只有输入,没有输出。
 */
public class Demo03 {
    public static void main(String[] args) {
//        Consumer<String> consumer = new Consumer<String>() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        };

        Consumer<String> consumer = (str)-> {
            System.out.println(str);
        };

        consumer.accept("asd");
    }
}

5、Supplier:供给型接口 没有输入,只有输出

package com.he.function;

import java.util.function.Supplier;

/**
 *  Supplier 供给型接口 没有参数,只有返回值。
 */
public class Demo04 {
    public static void main(String[] args) {
//        Supplier<Integer> supplier = new Supplier<Integer>() {
//            @Override
//            public Integer get() {
//                System.out.println("get");
//                return 1024;
//            }
//        };
        Supplier<Integer> supplier = () -> {
            System.out.println("get");
            return 1024;
        };

        System.out.println(supplier.get());
    }
}

13、Stream流式计算

1、什么是Stream流式计算

集合、数据库应该是用来存储数据的。

计算都应该交给流来做。

在这里插入图片描述

package com.he.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *  题目要求:一分钟完成此题,只能用一行代码
 *  现在又5个用户,筛选:
 *  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);

        // 计算交给Stream流
        list.stream()
                .filter((user -> {return (user.getId() % 2 == 0);}))
                .filter((user -> {return (user.getAge() > 23);}))
                .map((user -> {return (user.getName().toUpperCase());}))
                .sorted((user1,user2) -> {return user2.compareTo(user1);})
                .limit(1).forEach(System.out::println);

    }
}

14、ForkJoin

1、什么是ForkJoin

DorkJoin在JDK1.7中首次出现,并行执行任务!提高效率,大数据量

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

在这里插入图片描述

2、ForkJoin特点:工作窃取

这个里面维护的都是双端队列。

在这里插入图片描述

3、ForkJoin的操作

ForkJoinDemo,java

package com.he.forkjoin;

import java.util.concurrent.RecursiveTask;

/**
 *  求和计算的任务
 *  3000  6000  9000
 *  如何使用forkJoin
 *  1、forkJoinPool 通过它来执行
 *  2、计算任务 forkJoinPool.execute(ForkJoinTask task)
 */
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) < 10000) {
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long middle = (start + end)/2;
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork();// 拆分任务,把任务压入线程队列
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            task2.fork();// 拆分任务,把任务压入线程队列

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

Test.java

package com.he.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();// 277
//        test2();//3
        test3();
    }
    public static void test1() {
        long sum = 0l;
        long start = System.currentTimeMillis();

        for (int i = 0; i <= 10_0000_0000; i++) {
            sum += i;
        }

        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + "时间:" + (end - start));
    }
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinDemo task = new ForkJoinDemo(0L,10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + "时间:" + (end - start));
    }
    // Stream并行流
    public static void test3() {
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum=" + sum + "时间:" + (end - start));
    }
}

15、异步回调

Futuer 设计的初衷,是对将来某个事件的结果进行建模

在这里插入图片描述

package com.he.future;

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

/**
 *  异步调用:Ajax
 *  异步执行
 *  成功回调
 *  失败回调
 */
public class Demo {
    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("111111111111");
//
//        completableFuture.get(); // 获取阻塞执行结果


        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "supplyAsync=》Integer");
            int i = 1/0;
            return 1024;
        });

        System.out.println(completableFuture.whenComplete((t, u) -> {
            System.out.println("t=>" + t);// 正常的返回结果
            System.out.println("u=>" + u);// 错误信息: u=>java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 404; // 可以获取到错误的返回结果
        }));
    }

}

16、JMM

请你谈谈 Volatile 的理解

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

  1. 保证可见性
  2. 不保证原子性
  3. 防止指令重排序

什么是JMM

JMM:Java内存模型,不存在的东西,概念,约定

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

  1. 线程解锁前,必须把共享变量立刻刷回主存。
  2. 线程加锁前,必须读取主存中的最新值到工作内存当中!
  3. 加锁和解锁是同一把锁

线程 工作内存主内存

8中操作:

在这里插入图片描述

在这里插入图片描述

每个操作都为原子操作

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
  • write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

对八种操作的规则

  1. 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  2. 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  3. 不允许一个线程将没有assign的数据从工作内存同步回主内存
  4. 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
  5. 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  6. 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  7. 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  8. 对一个变量进行unlock操作之前,必须把此变量同步回主内存

问题:程序不知道主内存中的值已经修改过了

17、Volatile

1、保证可见性

package com.he.tvolatile;

import java.util.concurrent.TimeUnit;

public class VolatileDemo {

    // 不加 volatile 程序就会死循环
    private static volatile int num = 0;

    public static void main(String[] args) {

        new Thread(() -> {
            while (num == 0) {
            }
        }).start();

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

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

2、不保证原子性

原子性:不可分割

线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败

package com.he.tvolatile;

/**
 *  volatile 不保证原子性
 */
public class VolatileDemo02 {

    private volatile static int num = 0;

    public static void add() {
        num++;
    }

    public static void main(String[] args) {
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

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

如果不加 lock 和 synchronized,怎么保证原子性。

在这里插入图片描述

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

在这里插入图片描述

package com.he.tvolatile;

import java.util.concurrent.atomic.AtomicInteger;

/**
 *  volatile 不保证原子性
 */
public class VolatileDemo02 {

    private volatile static AtomicInteger num = new AtomicInteger();

    public static void add() {
        //num++; // 不是原子性操作
        num.getAndIncrement(); // AtomicInteger + 1方法,CAS
    }

    public static void main(String[] args) {
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int i1 = 0; i1 < 1000; i1++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

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

这些类的底层都直接和操作系统挂钩。在内存中修改值。Unsafe是一个很特殊的存在。

3、指令重排

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

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

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

可能造成影响的结果:a、b、x、y这四个值默认都是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实现了可见性)。

在这里插入图片描述

18、彻底玩转单例模式

饿汉式、DCL懒汉式

19、深入理解CAS

1、什么是CAS

​ 在计算机科学中,比较和交换(Conmpare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。 这是作为单个原子操作完成的。 原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。 操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成(摘自维基本科)

我的理解:

  1. 线程1读取出指定内存地址的数据,加载到寄存器,此时读取出来的原值不仅将作为要被计算的值,还会作为比较值。

  2. 当线程1对读取出来的值执行计算之后,将要把新值重新存入内存之前…会先使用比较值与内存中的原值进行比较—如果两者相等,则表示没有其它线程对当前内存的值进行修改,那么此时就可以把线程1计算后的值存入该内存地址中;如果两者不相等,则说明有其他线程在线程1执行期间抢先对该内存地址的数据进行了修改,此时线程1的执行就算失败了。

大厂必须要深入研究底层!

package com.he.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class SACDemo {


    // CAS   Compare And Swap 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        // 期望、更新
        // public final boolean compareAndSet(int expect, int update)
        // 如果我期望的值达到了,那么就更新,否则就不更新  CAS是CPU的并发原语

        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger);
        System.out.println(atomicInteger.compareAndSet(2020, 2025));
        System.out.println(atomicInteger);
    }
}

2、Unsafe类

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

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

缺点:

  1. 循环会耗时
  2. 一次性只能保证一个共享变量的原子性
  3. 会存在ABA问题

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

  1. 线程1读取出指定内存地址的数据A,加载到寄存器,此时读取出来的原值不仅将作为要被计算的值A,还会作为比较值A。

  2. 此时线程1的cpu被线程2抢占了,线程2也从同样的内存地址中读取了同样的数据A,线程2还比线程1先执行完,线程2产生了新数据B,并且遵守了CAS原理把新数据B存入该内存地址。 (这个时候内存的值由A被该为B)

  3. 还没完,线程2执行完之后,线程1又没抢过其它线程,此时cpu被线程3抢占,之后步骤和第(2)步一样,线程3从同样的内存地址中读取了数据B,线程3还比线程1先执行完,线程3产生了新数据A(没错,与一开始的A相等,但值相等并不意味着此A就是彼A,已经被替换了),并且遵守了CAS原理把新数据A存入该内存地址。(这个时候内存的值由B又变为A)

  4. 这个时候线程1执行完了,要遵守CAS原理存入数据,然后比较值A是原来的A(简称原A),而执行内存地址中的A是被替换过的了,但原A的值与内存中的A值是相等的,根据CAS,线程1会把新的执行结果存入该内存地址。。。

在实际业务中,两个数的值相等,但这两个数并不是同一个数。。。

解决办法,就是加个版本号,多一步比较一版本号就行了。

在这里插入图片描述

20、原子引用

解决办法,就是加个版本号,多一步比较一版本号就行了。对应的思想就是乐观锁

带版本号的原子操作!

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

在这里插入图片描述

package com.he.cas;

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

public class SACDemo {

    // Integer
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1,1);

    // CAS   Compare And Swap 比较并交换
    public static void main(String[] args) {
//        AtomicInteger atomicInteger = new AtomicInteger(2020);

        // 对于我们平时写的SQL:乐观锁!

        // 期望、更新
        // public final boolean compareAndSet(int expect, int update)
        // 如果我期望的值达到了,那么就更新,否则就不更新  CAS是CPU的并发原语

        //=========================捣乱的线程===============================
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1=>" + stamp);

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

            System.out.println(atomicStampedReference.compareAndSet(1, 2,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a2=>" + atomicStampedReference.getStamp());

            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));

            System.out.println("a3=>" + atomicStampedReference.getStamp());

        },"a").start();


        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("b1=>" + stamp);

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

            System.out.println(atomicStampedReference.compareAndSet(1, 3,
                    stamp, stamp + 1));

            System.out.println("b2=>" + atomicStampedReference.getStamp());

        },"b").start();
    }
}

21、各种锁的理解

1、公平锁、非公平锁

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

非公平锁:非常不公平,可以插队(默认都是非公平锁)

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

2、可重入锁

可重入锁(递归锁)

在这里插入图片描述

1、Synchronized

package com.he.lock;

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

2、Lock

package com.he.lock;

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

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.lock(); Lock.unlock()
        // 锁必须配对,否则就会死在里面
        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();
        }
    }
}

3、自旋锁

在这里插入图片描述

我们来自定义一个锁测试

SpinlockDemo.java

package com.he.lock;

import java.util.concurrent.atomic.AtomicReference;

public class SpinlockDemo {

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

    // 加锁
    public void myLock() {
        Thread thread = Thread.currentThread();

        while (!atomicReference.compareAndSet(null,thread)) {

        }
        System.out.println(Thread.currentThread().getName() + "=>myLock");
    }

    public void myUnlock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "=>myUnlock");

        atomicReference.compareAndSet(thread,null);
    }
}

TestSpinLock.java

package com.he.lock;

import java.util.concurrent.TimeUnit;

public class TestSpinLock {
    public static void main(String[] args) {
        SpinlockDemo lock = new SpinlockDemo();

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

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

        new Thread(() -> {
            lock.myLock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnlock();
            }
        },"T2").start();
    }
}

在这里插入图片描述

4、死锁

1、死锁是什么?

在这里插入图片描述

package com.he.lock;

import java.util.concurrent.TimeUnit;

public class DeadLockDemo {

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

2、解决问题

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

    在这里插入图片描述

  2. 使用jstack 进程号查看死锁

    在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值