JUC 并发编程


JUC = java.until.concurrent

普通的线程代码:Thread

Runable 没有返回值、效率相对 Callable 较低

基本概念

进程:一个程序,QQ.exe、Music.exe 等程序的集合

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

线程:

一个进程中的各个功能是由线程负责实现的。如:自动保存,写字等

java 默认有两个线程,main 和 GC

对于java 而言:Thread、Runable、Callable

// Thread.State;
// 线程状态种类:6种
public enum State {
        NEW,            // 新生
    
        RUNNABLE,       // 运行
    
        BLOCKED,        // 阻塞
    
        WAITING,        // 等待
     
        TIMED_WAITING,  // 超时等待
    
        TERMINATED;     // 终止
    }

Java 真的可以开启线程吗?

开不了!

// 通过 new Thread().start()  ctrl 进入 start() 方法源码
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();   // 通过 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 */
        }
    }
}

private native void start0();   // start0 为本地方法(native)底层为 c++

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

  • cpu 一核,模拟出多条线程,快速交替执行

充分利用 CPU 资源

并行:多个线程同时执行

  • cpu 多核,多个线程可以同时执行,线程池
public static void main(String[] args) {
    // 获取 CPU 核数
    System.out.println(Runtime.getRuntime().availableProcessors());
}

线程和进程

wait 和 sleep 的区别:

  1. 类型区别

    wait => Object

    sleep => Thread

  2. 释放区别

    wait 会释放锁

    sleep 不会释放

  3. 使用范围

    wait 只能在同步代码块中

    sleep 可以在任何地方使用

java 线程实现方式:

import sun.awt.windows.ThemeReader;

/**
 * 真正的线程开发
 * 线程就是一个单独的资源类,没有任何附属操作
 */
public class SaleTickDemo1 {
    public static void main(String[] args) {
        // 并发多线程操作同一个类, 把资源丢入线程
        Ticket ticket = new Ticket();
        // @FunctionalInterface 匿名内部类的方式实现,函数式接口
         new Thread(new Runnable() {
             @Override
             public void run() {
                 for (int i=1;i<10;i++){
                     ticket.sale();
                 }
             }
         },"A").start();
//          lambda 表达式简化代码
        new Thread(()->{
            for (int i=1;i<10;i++){
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for (int i=1;i<10;i++){
                ticket.sale();
            }
        },"C").start();
    }
}
class Ticket{
    private int number = 50;
    public void sale(){
        if (number > 0){
            number--;
            System.out.println(Thread.currentThread().getName()+"卖出了"+1+"票,剩余:"+number);
        }
    }
}

// 输出可能会乱序

Lock 锁

synchronized

class Ticket{
    private int number = 50;
    // synchronized 本质:队列,锁
    public synchronized void sale(){
        if (number > 0){
            number--;
            System.out.println(Thread.currentThread().getName()+"卖出了"+1+"票,剩余:"+number);
        }
    }
}

Lock

接口,所有已知实现类:

  • ReentrantLock 可重入锁(常用)
  • ReentrantReadWriteLock.ReadLock() 可重入读写锁,读锁
  • ReentrantReadWriteLock.WriteLock() 可重入读写锁,写锁
// 创建锁
Lock lock = new ReentrantLock();
================ReentrantLock 构造方法=======================
    public ReentrantLock() {
        // 默认为非公平锁
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
        // 公平锁和非公平锁
        sync = fair ? new FairSync() : new NonfairSync();
    }
=============================================================
class Ticket2{
    private int number = 50;
    Lock lock = new ReentrantLock();
    public void sale(){
==========================官方文档加锁固定代码===========================
        lock.lock(); // 枷锁
        try {
            // 业务代码
            if (number > 0){
                number--;
                System.out.println(Thread.currentThread().getName()+"卖出了"+1+"票,剩余:"+number);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();  // 解锁
        }
=========================================================================
    }
}

公平锁:先来后到,排队进行

非公平锁:可以插队

synchronized 和 Lock 区别:

Lock 需要三步:新建锁,加锁,解锁

synchronized 自动完成

  1. synchronized 是java 内置关键字,Lock 是一个java 类
  2. synchronized 无法获取锁的状态,Lock 可以判断是否获取到了锁
  3. synchronized 或自动释放锁,Lock 必须手动释放,如果不释放,会造成死锁
  4. synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock 锁可以通过 tryLock() 方法尝试获取锁
  5. synchronized 默认为可重入锁,不可以中断,非公平锁;Lock 可重入锁,可以判断锁,非公平/公平(可以自己选择)
  6. synchronized 适合锁少量的代码同步问题,Lock 灵活度高,适合锁大量代码同步问题

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

生产者和消费者

package PC;

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

        new Thread(()->{
            for (int i = 0;i<10;i++){
                try {
                    data.inCrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(()->{
            for (int i = 0;i<10;i++){
                try {
                    data.deCrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
    }
}

// 资源类
class Data{
    private int number = 0;

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

    public synchronized void deCrement() throws InterruptedException {
        if (number == 0){ // 只有不为零时工作
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        // 通知其他线程,我-1完了
        this.notifyAll();
    }
}

**问题:**大于两个线程出现虚假唤醒问题(多个线程跳过等待过程直接执行),解决方法:if 改成 while 循环

package PC;

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

        new Thread(()->{
            for (int i = 0;i<10;i++){
                try {
                    data.inCrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(()->{
            for (int i = 0;i<10;i++){
                try {
                    data.deCrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

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

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

// 资源类
class Data{
    private int number = 0;

    public synchronized void inCrement() throws InterruptedException {
        while (number!=0){ // 只有为零时工作
            // 等待
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        // 通知其他线程,我+1完了
        this.notifyAll();
    }

    public synchronized void deCrement() throws InterruptedException {
        while (number == 0){ // 只有不为零时工作
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"->"+number);
        // 通知其他线程,我-1完了
        this.notifyAll();
    }
}

JUC 中生产者和消费者

在 synchronized 中使用 wait、notify

在 Lock 中使用 condition:await、signal

Lock 取代了 synchronized 方法和语句的使用,Conditon 取代了对象监视器方法的使用

package PC;

import javax.swing.*;
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 Test2 {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{
            for (int i = 0;i<10;i++){
                try {
                    data.inCrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(()->{
            for (int i = 0;i<10;i++){
                try {
                    data.deCrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

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

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

// 资源类
class Data2{
    private int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

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

    }

    public void deCrement() throws InterruptedException {
        lock.lock();
        try {
            while (number == 0){ // 只有不为零时工作
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            // 通知其他线程,我-1完了
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

Condition 精准通知

线程 A 完成工作通知 B,B 完成工作通知 C,C 完成工作通知 A

package PC;

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

/**
 * 线程之间的通信问题:生产者和消费者问题!等待唤醒和通知唤醒
 * A 执行完通知 B 执行完通知 C
 */
public class Test3 {
    public static void main(String[] args) {
        Data3 data = new Data3();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printA();
            }
        }, "A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printB();
            }
        }, "B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.printC();
            }
        }, "C").start();
    }
}

// 资源类
class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    private int number = 1; // 1A 2B 3C

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

8 锁现象

为什么总是先执行 sendMsg() 方法:

package Lock8;

import java.util.concurrent.TimeUnit;

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

        new Thread(()->{
            phone.sendMsg();
        }).start();

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

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

class Phone{
    public synchronized void sendMsg(){
        // 加上延时仍然先执行 sendMsg
//        try {
//            TimeUnit.SECONDS.sleep(1);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }
        System.out.println("sendMsg");
    }

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

错误回答:顺序,先写的代码先执行

synchronized 锁的对象时方法的调用者(这里是phone)!!两个方法用的是同一个锁,谁先拿到谁先执行。

// B 线程调用普通方法,先执行 hello,因为没有锁
package Lock8;

import java.util.concurrent.TimeUnit;

/**
 * 增加一个普通方法 hello
 * 先执行 hello 还是 sendMsg ?
 */
public class Test2 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();

        new Thread(()->{
            phone.sendMsg();
        }).start();

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

        new Thread(()->{
            phone.hello();
        }).start();
    }
}

class Phone2{
    public synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

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

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

添加 static 关键字:

package Lock8;

import java.util.concurrent.TimeUnit;

/**
 * 增加两个静态同步方法
 */
public class Test3 {
    public static void main(String[] args) {
        Phone3 phone = new Phone3();
        Phone3 phone2 = new Phone3();
        // 由于 static 方法,即使是两个对象,锁的对象也只有一个(class)。所以先输出 sendMsg

        new Thread(()->{
            phone.sendMsg();
        }).start();

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

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

class Phone3{
    // static 静态方法,类一加载就有了!锁的对象是 Class,Class 只有一个
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg");
    }

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

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

集合类不安全

List 不安全

public class ListTest1 {
    public static void main(String[] args) {
        // 并发下 ArrayList 不安全
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,7));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

输出报错:

Exception in thread “4” java.util.ConcurrentModificationException

并发修改异常

解决方案:

List list = new ArrayList<>(); => List list = new Vector<>();
Vector 是线程安全的,Vector 的方法由 synchronized 修饰,但 Vector 的出现时间不 ArrayList 要早,说明 ArrayList 有一定优势!

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

Collections 是一个集合类,里面包含很多线程安全的方法(List,Set,Map)

List list = new CopyOnWriteArrayList<>();

CopyOnWrite 写入时复制:简称 COW,计算机程序设计领域的一种优化方案

多个线程调用的时候 ,读取的时候 list 固定的,写入的时候复制一份,在完成写入后插入数据。避免脏数据产生造成的问题。

CopyOnWrite 对比 Vector :

Vector 读写操作都用了 synchronized 修饰,效率较低。

CopyOnWrite 只在写入时加锁 lock,效率较高。

Set 不安全

List 和 Set 没有本质区别,都是 Collection 的子类

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,7));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

输出报错:

Exception in thread “7” java.util.ConcurrentModificationException

并发修改异常

解决方案:

Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set set = new CopyOnWriteArraySet<>();

**HashSet 底层:**HashMap

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

// add 方法
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

private static final Object PRESENT = new Object();
使用 map 的 key 不能重复,对应的 value 一个不变常量

HashMap 不安全

源码:HashMap 初始化常量
static final int MAXIMUM_CAPACITY = 1 << 30;    // 最大存储容量,位运算

static final float DEFAULT_LOAD_FACTOR = 0.75f;  // 默认加载因子,表示容量超过 75% 时自动扩容
==============================================================
public class MapTest {
    public static void main(String[] args) {
        // hashmap 是这样用的吗,等价于什么
        // Map<String, String> map = new HashMap<>(16, 0.75);
        Map<String, String> map = new HashMap<>();
        // 加载因子,初始化容量
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

输出报错:

Exception in thread “1” java.util.ConcurrentModificationException

解决方案:

Map<String, String> map = new ConcurrentHashMap<>();

ConcurrentHashMap 是 HashTable 的替代

Callable

  1. 可以有返回值
  2. 可以抛出异常
  3. 方法不同,Runable 是 run 方法,Callable 是 call 方法
public interface Callable<V> {
    V call() throws Exception;      // 泛型为返回值类型
}

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        new Thread().start(); // 怎么启动 Callable?
        // new Thread(new Runnable() {}).start();
        // new Thread(new FutureTask<>( Callable)).start();
        // 所以可以通过 FutureTask 启动 Callable

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

        new Thread(futureTask, "A").start();
        // 获取返回值:
        String res = (String) futureTask.get();  // 可能会产生阻塞,可以放在代码最后,或者异步操作
        System.out.println(res);    // 只打印一次
    }
}

class MyThread implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("call");
        return "123";
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPerK8cN-1613985012029)(https://cdn.nlark.com/yuque/0/2021/jpeg/12539997/1613984866374-13c18640-dfc2-42fb-8cf3-da50929c3ada.jpeg)]

常用辅助类

CountDownLatch

线程减法计数器,当线程达到一定数量时,唤醒程序

保证指定线程任务执行完成

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 总数为 6
        CountDownLatch countDownLatch = new CountDownLatch(6);  // 输入线程数
        /*
        public CountDownLatch(int count) {
            if (count < 0) throw new IllegalArgumentException("count < 0");
            this.sync = new Sync(count);
        }
         */
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Go out");
                countDownLatch.countDown(); // 数量减一
            },String.valueOf(i)).start();
        }

        countDownLatch.await(); // 等待计数器归零,然后向下执行
        // 如果不用 await() 方法,close door 可能会先打印
        System.out.println("close door");
    }
}

每次有线程调用 countDownLatch.countDown() 计数器减一,当计数器为 0 时,countDownLatch.await() 被唤醒,继续向下执行

CyclicBarrier

线程加法计数器,当线程达到一定数量时,触发指定线程工作

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

Semaphore

信号量,指定资源数量(线程数量),只有当有资源时才能执行

public class SemaphoreTest {
    public static void main(String[] args) {
        // 资源(信号量)数量
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire(); // 获取资源
                    Random random = new Random();
                    System.out.println(Thread.currentThread().getName()+"获得资源");
                    TimeUnit.SECONDS.sleep(random.nextInt(5));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release(); // 释放资源
                    System.out.println(Thread.currentThread().getName()+"释放资源");
                }
            }, String.valueOf(i)).start();
        }
    }
}
// 输出:
1获得资源
2获得资源
3获得资源
1释放资源
4获得资源
2释放资源
6获得资源
3释放资源
5获得资源
5释放资源
4释放资源
6释放资源

读写锁

public class ReadWriteDemo {
    public static void main(String[] args) {
        MyCache2 myCache = new MyCache2();
        // 写入操作
        for (int i = 0; i < 5; i++) {
            // lambada 表达式里无法访问外部变量
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"", temp+"");
            }, String.valueOf(i)).start();
        }
        // 读取操作
        for (int i = 5; i < 10; i++) {
            // lambada 表达式里无法访问外部变量
            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()+"写入");
        map.put(key, value);
        System.out.println(Thread.currentThread().getName()+"写入完成");
    }

    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"读取");
        map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取完成");
    }
}
/**
 * 自定义缓存,加锁
 */
class MyCache2{
    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()+"写入");
            map.put(key, value);
            System.out.println(Thread.currentThread().getName()+"写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }

    // 都可以进行读操作,必须加锁,防止还没写入完成就被读取,产生脏数据
    public void get(String key){
        readWriteLock.readLock().lock();

        try {
            System.out.println(Thread.currentThread().getName()+"读取");
            map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}

写锁(独占锁)只能一个线程独自占用

读锁(共享锁)多个线程可以同时占用

读写共存关系:

  • 读-读:可以共存
  • 读-写:不可以共存
  • 写-写:不可以共存

阻塞队列

队列:先进先出

  • 写:如果队列满了,会阻塞
  • 读:如果队列空了,会阻塞

线程池和多线程并发情况下常用,BlockingQueue

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V1h6RgHY-1613985012031)(https://cdn.nlark.com/yuque/0/2021/jpeg/12539997/1613984865833-2d9f0a90-73f4-49d5-9985-9715191abae6.jpeg)]

四组 API

方式抛出异常不抛出异常阻塞等待超时等待
添加addofferputoffer(…)
移除removepolltakepoll(…)
判断队列首elementpeek
public class Test {
    public static void main(String[] args) {
        test1();
    }
    /**
     * 抛出异常
     */
    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.element());    // 返回首元素
        // 队列满时抛出异常,Queue full
        // 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 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.peek()); // 返回首元素
        // 不抛异常,返回 false
        // System.out.println(blockingQueue.offer("d"));
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        // 不抛异常,返回 null
        // System.out.println(blockingQueue.poll());
    }
    
    /**
     * 阻塞等待
     */
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        // 队列满了,一直阻塞,不退出
        // blockingQueue.put("d");
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        // 队列空了,一直阻塞,不退出
        // System.out.println(blockingQueue.take());
    }
    public static void test4() throws InterruptedException {
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);
        
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        // 等待两秒 超时退出
        blockingQueue.offer("d", 2, TimeUnit.SECONDS);
        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll();
        // 等待两秒 超时退出
        blockingQueue.poll(2, TimeUnit.SECONDS);
    }
}

同步队列

SynchronousQueue

/**
 * 同步队列
 * 和其他 BlockingQueue 不同,SynchronousQueue 不存储元素
 * put 了一个元素,必须从队列里先 take 出来,否者无法再 put 进去
 * 用 offer 无法取值
 */
public class SyncQueueTest {
    public static void main(String[] args) {
        SynchronousQueue<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();
            }
        }).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();
            }
        }).start();
    }
}

线程池

池化技术:程序的运行,本质为占用系统资源,为了优化资源的使用,有了池化技术

线程池,连接池,内存池,对象池

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

默认大小 2

优点:

  • 降低资源消耗
  • 提高响应速度(不用销毁和创建)
  • 方便管理
  • 线程复用,可以控制最大并发数

线程:

三大方法

public class Demo1 {
    public static void main(String[] args) {
        // 单一线程池
        // ExecutorService threadPool = Executors.newSingleThreadExecutor();
        // 指定线程数量
//        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        // 可伸缩线程池(如果循环十次,则最多可以有十个线程)
        ExecutorService threadPool = Executors.newCachedThreadPool();
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); // 关闭线程池
        }
    }
}

七大参数

三大方法构造函数:

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

阿里云文档:线程池不允许使用 Excutors 去创建,而是通过 ThreadPoolExecutor 的方式,这样吃力的方式让写的同学更加明确线程池运行规则,避免耗尽资源的风险。

手动创建线程池

拒绝策略(最大线程数量和阻塞队列都满了时触发)

  • AbortPolicy (默认)

不处理,直接抛出异常 java.util.concurrent.RejectedExecutionException

  • CallerRunsPolicy

哪来的去哪里,如果是main 线程中出现的,就有 main 线程执行

  • DiscardPolicy

丢弃任务,不抛出异常

  • DiscardOldestPolicy

尝试与第一个队列竞争,不会抛出异常

public class Demo1 {
    public static void main(String[] args) {
        // 自定义线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        try {
            // 最大承载 LinkedBlockingDeque + MaxPoolSize
            for (int i = 0; i < 9; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); // 关闭线程池
        }
    }
}

最大线程如何定义?

  1. CPU 密集型 如果 CPU 为 4 核 8 线程,则最高线程数为 8 线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6EyUZJpX-1613985012033)(https://cdn.nlark.com/yuque/0/2021/png/12539997/1613982697889-af99784c-2d43-4445-952f-bc8ea76b0a6b.png)]

Runtime.getRuntime().availableProcessors() 可获取最大线程数,不应该写死,防止运维时服务器性能不够时出错。

  1. IO 密集型 判断程序中十分耗 IO 的线程数量,留出相应空间

四大函数式接口

lambda 表达式,链式编程,函数式接口,Stream 流计算

函数式接口:只有一个方法的接口(java 中非常多)

可以简化编程模型,可以用 lambda 简化代码

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Function

public class Demo01 {
    public static void main(String[] args) {
//        Function<String, String> f = new Function<String, String>() {
//            @Override
//            public String apply(String s) {
//                return s;
//            }
//        };
        // 简化后
        Function<String, String> f = (str)->{return str;};
        f.apply("123");
        /* 源码
        public interface Function<T, R> {
            T 为传入类型,R 为返回类型
            R apply(T t);
        }*/
    }
}

Predicate

/**
 * 断定式函数:有一个输入参数,返回值只能是 布尔值
 */
public class Demo02 {
    public static void main(String[] args) {
//        Predicate<String> predicate = new Predicate<String>(){
//            @Override
//            public boolean test(String s) {
//                return false;
//            }
//        };
        // 简化
        Predicate<String> predicate = (str)->{return false;};
        predicate.test("123");
    }
}

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("s");
        };
        consumer.accept("123");
    }
}

Supplier

/**
 * 供给型接口:没有参数输入,只有返回值
 */
public class Demo04 {
    public static void main(String[] args) {
//        Supplier<String> supplier = new Supplier<String>() {
//            @Override
//            public String get() {
//                return "1231";
//            }
//        };

        Supplier<String> supplier = ()->{return "123";};
        supplier.get();
    }
}

Stream 流式计算

大数据:存储 + 计算

计算都应该交给流来操作!

/**
 * 题目要求:一分钟内完成此题,只能用一行代码实现
 * 现在有 5 个用户,筛选
 * 1. ID 必须是偶数
 * 2. 年龄大于 23 岁
 * 3. 用户名转为大写字母
 * 4. 用户名字母倒着排序
 * 5. 只输出一个用户
 */
public class Test {
    public static void main(String[] args) {
        User user1 = new User(1, "a", 21);
        User user2 = new User(2, "b", 22);
        User user3 = new User(3, "c", 23);
        User user4 = new User(4, "d", 24);
        User user5 = new User(6, "e", 25);
        // 集合存储数据
        List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
        // 流计算,链式编程
        list.stream()
                .filter(u->{return u.getId()%2==0;})
                .filter(u->{return u.getAge()>23;})
                .map(u->{return u.getName().toUpperCase();})
                .sorted((u1,u2)->{return u2.compareTo(u1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

ForkJoin

并行执行任务!提高效率,大数据量

将大任务拆分成小任务,最后将小任务结果和并

特点:工作窃取

里面是双端队列,当一个线程执行完全部任务时,会执行别的线程还未执行的任务

举例:大数计算:

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

    public Forkjoin(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 mid = (start + end)/2; // 取中间值
            Forkjoin forkjoin = new Forkjoin(start, mid);
            forkjoin.fork();    // 将任务压入线程队列
            Forkjoin forkjoin1 = new Forkjoin(mid + 1, end);
            forkjoin1.fork();
            return forkjoin.join() + forkjoin1.join();
        }
    }
}

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

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

    // Forkjoin 计算
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new Forkjoin(0L, 100_0000_0000L);
        // submit 有返回值,execute 没有返回值
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+",时间:"+(end-start));
    }

    // 并行流计算
    public static void test3(){
        long start = System.currentTimeMillis();
        long sum = LongStream.range(0L, 100_0000_0001L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+",时间:"+(end-start));
    }
}

输出:
sum=-5340232216128654848,时间:5069
sum=-5340232216128654848,时间:1598
sum=-5340232216128654848,时间:1255

异步回调

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

一般用 CompletableFuture

/**
 * 异步执行请求:
 * 异步执行
 * 成功回调
 * 失败回调
 */
public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 没有返回值的调用
//        CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
//            System.out.println("11111");
//        });
//        completableFuture.get();
        // 有返回值的调用
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"supplyAsync->Integer");
            int i = 10/0;
            return 1024;
        });

        completableFuture.whenComplete((t, u)->{    // 成功返回
            System.out.println("t->"+t);    // 正常的返回结果
            System.out.println("u->"+u);    // 错误的返回信息,没有错误为 null
        }).exceptionally((e)->{     // 错误返回
            System.out.println(e.getMessage());
            return 0;
        });
    }
}

JMM

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

关于 JMM 的一些约定:

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fXqKz1Sk-1614071229152)(https://cdn.nlark.com/yuque/0/2021/jpeg/12539997/1614060189370-e4153ec0-6c3b-4b29-94af-af01dec69831.jpeg)]

8 种操作

public class Demo01 {
    private static 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);
        // 修改后线程并没有停止, 线程并不知道主线程的值已经修改!!!
    }
}

解决方案:

private static ``volatile`` int num = 0;

Volatile

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

  1. 保证可见性

如上述代码所示,volatile 可以保证变量在线程中的可见性,修改后立马让各线程知道

  1. 不保证原子性

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

public class Demo2 {
    // private volatile static int num = 0;
    private volatile static AtomicInteger num = new AtomicInteger();
    private static void add(){
        //num++;    // 不是原子性操作
        num.getAndIncrement();  // 通过 CAS 操作加一
    }
    public static void main(String[] args) {
        for (int i = 1; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while(Thread.activeCount()>2){  // 如果线程数量大于2,就让线程礼让
            // 基本上出不来循环,因为 java 默认两个线程 main 和 gcc
            Thread.yield();
            /**
             * Thread.yield( )方法:
             * 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
             *
             * Java线程中有一个Thread.yield( )方法,很多人翻译成线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行。
             *
             * 打个比方:现在有很多人在排队上厕所,好不容易轮到这个人上厕所了,突然这个人说:“我要和大家来个竞赛,看谁先抢到厕所!”,然后所有的人在同一起跑线冲向厕所,有可能是别人抢到了,也有可能他自己有抢到了。我们还知道线程有个优先级的问题,那么手里有优先权的这些人就一定能抢到厕所的位置吗? 不一定的,他们只是概率上大些,也有可能没特权的抢到了。
             */
        }
        System.out.println(Thread.currentThread().getName()+" "+ num);
    }
}
// 输出不一定是理论值 20000
可以通过加锁 或者 synchronized 解决

如何不通过锁操作解决这个问题?

通过原子类:

private volatile static AtomicInteger num = new AtomicInteger();

直接在内存中修改值。unsafe 这个类是一个很特殊的存在

  1. 禁止指令重排

什么是指令重排?

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

源代码->编译器优化重排->指令并行可能重排->内存系统坑你重排->执行

int x = 1; //1
int x = 2; //2
x = x + 5; //3
y = x + x; //4
期望执行顺序:1234
可能执行顺序:2134 1324
不可能执行顺序:4123

volatile 避免指令重排:

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

  1. 保证特定的操作执行顺序
  2. 可以保证某些变量的内存可见性(利用这些实现了可见性)

img

深入理解 CAS

什么是 CAS

CAS compare and swap 比较并交换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BF4w1jFL-1614071229157)(https://cdn.nlark.com/yuque/0/2021/png/12539997/1614067693337-9beaf4f9-7767-4062-9639-8a561fce3e77.png)]

image.png

CAS 比较当前工作内存中的值和主存中的值,如果这个值是期望的,则执行,如果不是就一直循环(自旋锁)

缺点:

  1. 循环耗时
  2. 一次性只能保证过一个共享变量的原子性
  3. ABA 问题
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        /*
        public final boolean compareAndSet(int expect, int update) {
            return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
        }*/
        // 如果我期望的值达到了(2020)就更新(2021),否者不更新
        // CAS 是 CPU 并发原语
        // -------------------捣乱的线程------------------------------
        System.out.println(atomicInteger.compareAndSet(2020,2021));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2021,2020));
        System.out.println(atomicInteger.get());
        // -------------------期望的线程------------------------------
        System.out.println(atomicInteger.compareAndSet(2020,6666));
        System.out.println(atomicInteger.get());
    }
}

线程 A 修改了资源值后又修改了回去,对线程 B 来说并不知道。希望能让操作透明怎么办?

原子引用

带版本号的原子操作(类似乐观锁)

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

解决 ABA 问题

public class CASDemo {
    public static void main(String[] args) {
        // 正常 Integer 应该是一个对象
        AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(123, 1);

        new Thread(()->{
            // 获取版本号
            int stamp = atomicInteger.getStamp();
            System.out.println("a->"+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicInteger.compareAndSet(123,6,
                    atomicInteger.getStamp(),atomicInteger.getStamp()+1);

            System.out.println("a2->"+atomicInteger.getStamp());

            System.out.println(atomicInteger.compareAndSet(6,123,
                    atomicInteger.getStamp(),atomicInteger.getStamp()+1));

            System.out.println("a3->"+atomicInteger.getStamp());

        }, "A").start();
        new Thread(()->{
            // 获取版本号
            int stamp = atomicInteger.getStamp();
            System.out.println("b->"+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicInteger.compareAndSet(123,3,
                    atomicInteger.getStamp(),atomicInteger.getStamp()+1));

            System.out.println("b2->"+atomicInteger.getStamp());
        }, "B").start();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值