JUC并发编程

1、什么是JUC

java.util.Concurrent(并发) 工具包、包、分类

image-20201012203132530

业务:普通的线程代码 Thread

Runnable 没有返回值、效率相比入 Callable 相对较低!

image-20201012203207240

image-20201012203217386

2、线程和进程

概念

  • 进程:一个程序,QQ.exe Music.exe 程序的集合;一个进程往往可以包含多个线程,至少包含一个!

  • 线程:例如开了一个进程 Typora,写字,自动保存(线程负责的)

Java默认有2个线程:mian(主线程)、GC(垃圾回收站)

对于Java而言:Thread、Runnable、Callable

java无法自己开启线程

Java 真的可以开启线程吗? 开不了,因为使用的是本地方法,底层的C++ ,Java 无法直接操作硬件

// 对应源码
public synchronized void start() { 
    /*** This method is not invoked for the main method thread or "system" 
    * group threads created/set up by the VM. Any new functionality added 
    * to this method in the future may have to also be added to the VM. 
    *
    * A zero status value corresponds to state "NEW".
    */ 
    if (threadStatus != 0) throw new IllegalThreadStateException(); 
    /* Notify the group that this thread is about to be started 
    * so that it can be added to the group's list of threads
    * and the group's unstarted count can be decremented. 
    */
    group.add(this); 

    boolean started = false;
    try { 
        // 调用下方的本地方法
        start0();
        started = true; 
    }  finally { 
        try { 
            if (!started) { 
                group.threadStartFailed(this); 
             } 
        }  catch (Throwable ignore) {  
            /* do nothing. If start0 threw a Throwable then
                it will be passed up the call stack */ 
        } 
    }  
} 

// 本地方法,底层的C++ ,Java 无法直接操作硬件 
private native void start0();

并发、并行

并发编程:并发、并行

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

  • CPU 一核 ,模拟出来多条线程,天下武功,唯快不破,快速交替

并行(多个线程同时执行)

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

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

线程有6个状态

public enum State { 

    // 新创建
    NEW,

    // 运行
    RUNNABLE,

    // 阻塞
    BLOCKED,

    // 等待
    WAITING,

    // 超时等待
    TIMED_WAITING,

    // 终止
    TERMINATED;
} 

wait/sleep 区别

  1. 来自不同的类:

    wait => Object

    sleep => Thread

  2. 关于锁的释放:

    wait:会释放锁

    sleep:抱着锁睡觉,不会释放

  3. 使用的范围是不同的:

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

    sleep:可以在任何地方使用

  4. 是否需要捕获异常:

    wait:不需要捕获异常

    sleep:必须捕获异常

3、Lock锁

传统的Synchronizad

以卖票为例子

package com.xj.demo;

public class SaleTicketDemo1 { 
    public static void main(String[] args) { 
        // 并发:多线程操作同一个资源类,把资源丢进线程中
        Ticket ticket = new Ticket();

        // 这里使用 jdk1.8 lambda表达式 (参数)->{  代码 } 
        new Thread(()->{ 
            for (int i = 1; i < 40 ; i++) { 
                ticket.sale();
            } 
        } , "A").start();

        new Thread(()->{ 
            for (int i = 1; i < 40 ; i++) { 
                ticket.sale();
            } 
        } , "B").start();

        new Thread(()->{ 
            for (int i = 1; i < 40 ; i++) { 
                ticket.sale();
            } 
        } , "C").start();
    } 
} 

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

    // 卖票的方式
    public synchronized void sale(){ 
        if(number > 0){ 
            System.out.println(Thread.currentThread().getName() + "卖出了"
                    + (50 - number) + "张票,剩余" + (--number) + "张票");
        } 
    } 
} 

运行结果:所有线程依次执行卖票操作,直到票被卖完,如果不加锁的话,数据将会产生错误!

image-20201012210915816

注意:这里使用 jdk1.8 lambda表达式 (参数)->{ 代码 },需要配置好jdk版本,不然设别不了

image-20201012210432541

image-20201012210634541

Lock 接口

查看jdk文档的基本介绍

image-20201012211113890

实现类如下,一般直接使用ReentrantLock

image-20201012211130352

源码部分

image-20201012211149916

  • 公平锁:十分公平:可以先来后到
  • 非公平锁:十分不公平:可以插队 (默认)

同样以卖票为例子

package com.xj.demo;

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

public class SaleTicketDemo2 { 
    public static void main(String[] args) { 
        // 并发:多线程操作同一个资源类,把资源丢进线程中
        Ticket ticket = new Ticket();

        // 这里使用 jdk1.8 lambda表达式 (参数)->{  代码 } 
        new Thread(()->{ for (int i = 1; i < 40 ; i++)ticket.sale();} , "A").start();
        new Thread(()->{ for (int i = 1; i < 40 ; i++)ticket.sale();} , "B").start();
        new Thread(()->{ for (int i = 1; i < 40 ; i++)ticket.sale();} , "C").start();
    } 
} 

// 资源类
// Lock三部曲
// 1、 new ReentrantLock();
// 2、 lock.lock(); // 加锁
// 3、 finally=> 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() + "卖出了"
                        + (50 - number) + "张票,剩余" + (--number) + "张票");
            } 
        } catch (Exception e){ 
            e.printStackTrace();
        } finally { 
            // 解锁
            lock.unlock();
        } 
    } 
} 

运行效果还是一样!

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、生产者和消费者问题

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

1、Synchronized 版

示例代码(两个线程模式)

package com.xj.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 < 10; i++) { 
                try { 
                    data.increment();
                }  catch (InterruptedException e) { 
                    e.printStackTrace();
                } 
            } 
        } , "A").start();
        new Thread(()->{ 
            for (int i = 0; i < 10; i++) { 
                try { 
                    data.decrement();
                }  catch (InterruptedException e) { 
                    e.printStackTrace();
                } 
            } 
        } , "B").start();
    } 
} 

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

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

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

如果是处理A B C D 4 个线程的情况!

直接使用上面代码测试,增加两个线程

image-20201013124342234

则会发现运行结果是错误的!

image-20201013124415023

错误原因

使用if只进行一次判断,所以会存在虚假唤醒的情况!

image-20201013124440776

解决方法

if 改为 while 判断

package com.xj.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 < 10; i++) { 
                try { 
                    data.increment();
                }  catch (InterruptedException e) { 
                    e.printStackTrace();
                } 
            } 
        } , "A").start();
        new Thread(()->{ 
            for (int i = 0; i < 10; i++) { 
                try { 
                    data.decrement();
                }  catch (InterruptedException e) { 
                    e.printStackTrace();
                } 
            } 
        } , "B").start();
        new Thread(()->{ 
            for (int i = 0; i < 10; i++) { 
                try { 
                    data.increment();
                }  catch (InterruptedException e) { 
                    e.printStackTrace();
                } 
            } 
        } , "C").start();
        new Thread(()->{ 
            for (int i = 0; i < 10; i++) { 
                try { 
                    data.decrement();
                }  catch (InterruptedException e) { 
                    e.printStackTrace();
                } 
            } 
        } , "D").start();
    } 
} 

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

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

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

此时运行的结果正确了!

2、JUC版

JUC没有wait()和notify(),而是使用了Conditionawait()signal()进行代替

image-20201013124818171

对应官方文档源码

image-20201013125020307

代码实现

package com.xj.pc;

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

public class B { 
    public static void main(String[] args) { 
        Data2 data = new Data2();
        new Thread(()->{ 
            for (int i = 0; i < 10; i++) { 
                data.increment();
            } 
        } , "A").start();

        new Thread(()->{ 
            for (int i = 0; i < 10; i++) { 
                data.decrement();
            } 
        } , "B").start();

        new Thread(()->{ 
            for (int i = 0; i < 10; i++) { 
                data.increment();
            } 
        } , "C").start();

        new Thread(()->{ 
            for (int i = 0; i < 10; i++) { 
                data.decrement();
            } 
        } , "D").start();
    } 
} 

// 资源类
class Data2{ 
    private int number = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //condition.await(); // 等待
    // condition.signalAll(); // 唤醒全部
    // +1
    public void increment(){ 
        lock.lock();
        try { 
            // 业务代码
            while(number != 0){ 
                // 等待
                condition.await();
            } 
            number++;
            System.out.println(Thread.currentThread().getName() + "-->" + number);
            // 通知其他线程,这里加一完毕
            condition.signalAll();
        }  catch (InterruptedException e) { 
            e.printStackTrace();
        }  finally { 
            lock.unlock();
        } 
    } 

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

运行结果一致!除此之外,condition还可以指定线程运行,让ABCD依次运行

Condition 精准的通知和唤醒线程

package com.xj.pc;

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

public class C { 
    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 int number = 1; // 1A 2B 3C
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

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

    public void printB(){ 
        lock.lock();
        try { 
            // 业务,判断-> 执行-> 通知
            while(number != 2){ 
                // 等待
                condition2.await();
            } 
            System.out.println(Thread.currentThread().getName() + "-->BBB");
            // 唤醒,唤醒指定的人,C
            number = 3;
            condition3.signal();
        }  catch (InterruptedException e) { 
            e.printStackTrace();
        }  finally { 
            lock.unlock();
        } 
    } 

    public void printC(){ 
        lock.lock();
        try { 
            // 业务,判断-> 执行-> 通知
            while(number != 3){ 
                // 等待
                condition3.await();
            } 
            System.out.println(Thread.currentThread().getName() + "-->CCC");
            // 唤醒,唤醒指定的人,A
            number = 1;
            condition1.signal();
        }  catch (InterruptedException e) { 
            e.printStackTrace();
        }  finally { 
            lock.unlock();
        } 
    } 
} 

运行结果:ABC线程依次进行

image-20201013131443867

5、8锁现象

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

代码演示

synchronized 锁

  • synchronized 修饰的同步方法自带锁,所以两个同步线程不可并发
package com.xj.lock8;

import java.util.concurrent.TimeUnit;

/*** 8锁,就是关于锁的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.send();
        } , "A").start();

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

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

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

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

普通方法和两把锁测试

  • 普通方法没有锁!不是同步方法,不受锁的影响!
  • 两把锁的时候,两个同步方法可以并发!
package com.xj.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 3、 增加了一个普通方法后!先执行发短信还是Hello? 普通方法    // 先输出Hello
 * 4、 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
 */
public class Test2 { 
    public static void main(String[] args) { 
        // 两个对象,两个调用者,两把锁!
        Phone2 phone = new Phone2();
        Phone2 phone2 = new Phone2();

        new Thread(()->{ 
            phone.send();
        } , "A").start();

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

        // 执行普通方法,不被上锁
//        new Thread(()->{ 
//            phone.hello();
//        } ).start();

        // 使用不同对象执行
        new Thread(()->{ 
            phone2.call();
        } , "B").start();
    } 
} 

class Phone2{ 
    // synchronized 锁的对象是方法的调用者!
    public synchronized void send(){ 
        try { 
            TimeUnit.SECONDS.sleep(4);
        }  catch (InterruptedException e) { 
            e.printStackTrace();
        } 
        System.out.println("发短信");
    } 

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

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

使用static修饰synchronized

这个时候synchronized 锁的是整个Class,所以两把锁还是一样!不可以并发!

package com.xj.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 5、增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?
 * 6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?
 */
public class Test3 { 
    public static void main(String[] args) { 
        // 两个对象,两个调用者,两把锁!
        Phone3 phone = new Phone3();
        Phone3 phone3 = new Phone3();

        new Thread(()->{ 
            phone.send();
        } , "A").start();

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

        // 使用不同对象执行
        new Thread(()->{ 
            phone3.call();
        } , "B").start();
    } 
} 

// Phone3唯一的一个 Class 对象
class Phone3{ 
    // synchronized 锁的对象是方法的调用者!
    // static 静态方法
    // 类一加载就有了!锁的是Class
    public static synchronized void send(){ 
        try { 
            TimeUnit.SECONDS.sleep(4);
        }  catch (InterruptedException e) { 
            e.printStackTrace();
        } 
        System.out.println("发短信");
    } 

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

静态同步方法 和 普通同步方法 的测试

  • 静态的同步方法 锁的是 Class 类模板
  • 普通的同步方法 锁的调用者

因为锁的类型不同,所以一个对象和两个对象的效果相同,互不影响可以并发。

package com.xj.lock8;

import java.util.concurrent.TimeUnit;

/**
 * 1、1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?     1/打电话   2/发短信
 * 2、1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?     1/打电话   2/发短信
 */
public class Test4 { 
    public static void main(String[] args) { 
        // 两个对象的Class类模板只有一个,static,锁的是Class
        Phone4 phone = new Phone4();
        Phone4 phone4 = new Phone4();

        new Thread(()->{ 
            phone.send();
        } , "A").start();

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

        // 使用不同对象执行
        new Thread(()->{ 
            phone4.call();
        } , "B").start();
    } 
} 

class Phone4{ 
    // 静态的同步方法 锁的是 Class 类模板
    public static synchronized void send(){ 
        try { 
            TimeUnit.SECONDS.sleep(4);
        }  catch (InterruptedException e) { 
            e.printStackTrace();
        } 
        System.out.println("发短信");
    } 

    // 普通的同步方法 锁的调用者
    public synchronized void call(){ 
        System.out.println("打电话");
    } 
} 

小结

  • 普通同步方法锁的是一个具体对象

  • static静态同步方法锁的是一个唯一的类模板class

6、集合类不安全

所有的单线程代码都是安全的,而在多并发的时候,很多集合则是不安全的!

1、CopyOnWriteArrayList

CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略;多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)。在写入的时候应避免覆盖,造成数据问题!

单线程 List

单线程的时候,以下程序可以正常运行

public class ListTest { 
    public static void main(String[] args) { 
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) { 
            list.add(UUID.randomUUID().toString().substring(0,5));
        } 
        list.forEach(System.out::println);
    } 
} 

多线程 List

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

在并发操作List集合的时候会报出下面的错误!java.util.ConcurrentModificationException 并发修改异常!

image-20201019212243901

解决方法

  1. 使用List<String> list = new Vector<>();

    因为Vector的add 方法加了 synchronized

    image-20201019213314414

  2. 使用List<String> list = Collections.synchronizedList(new ArrayList<>());

    同样也是加了synchronized

    image-20201019213625359

  3. 使用 List<String> list = new CopyOnWriteArrayList<>();

    而CopyOnWriteArrayList 是使用 lock 锁!

    image-20201019213718721

public class ListTest { 
    public static void main(String[] args) { 
        /**
         * 解决方案;
         * 1、List<String> list = new Vector<>();
         * 2、List<String> list = Collections.synchronizedList(new ArrayList<> ());
         * 3、List<String> list = new CopyOnWriteArrayList<>();
         */
        // CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略;
        // 多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)
        // 在写入的时候避免覆盖,造成数据问题!
        // 读写分离

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

可以通过查看add 方法的源码,判断解决方法的原因

image-20201019213458057

2、CopyOnWriteArraySet

Set和List其实也是Collection的同级子类

image-20201019214315084

Set 不安全

使用多线程并发操作 Set集合同样也会报错!ConcurrentModificationException

public class SetTest { 
    public static void main(String[] args) { 
        Set<String> set = new HashSet<>();
        // 如果测试的线程不是很多,有时并不会报错
        for (int i = 0; i < 20; i++) { 
            new Thread(()->{ 
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            } , String.valueOf(i)).start();
        } 
    } 
} 

image-20201019214842189

解决方法

  1. 使用集合工具类Collections.synchronizedSet,将set转为安全的

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

    Set<String> set = new CopyOnWriteArraySet<>();

HashSet 的底层

点击hashset的源码查看

// hashset 本质上就是 hashmap
public HashSet() { 
    map = new HashMap<>();
} 

// 而hashset的add方法则是使用了map.put
public boolean add(E e) { 
    return map.put(e, PRESENT)==null;
} 

// 其中的PRESENT只是一个不变的值
private static final Object PRESENT = new Object();

3、ConcurrentHashMap

Map 相关知识

  • 工作中一般不使用HashMap
  • 默认等价于 new HashMap<>(16, 0.75)
  • 分别是 初始化容量、加载因子

image-20201019220606307

Map 不安全

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

image-20201019220927336

解决方法

  1. 使用工具类Collections.synchronizedMap

    Map<String, Object> map = Collections.synchronizedMap(new HashMap<>());
  2. 注意map使用的是ConcurrentHashMap

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

关于ConcurrentHashMap的底层原理可以查看jdk8文档

image-20201019222900065

4、Callable

image-20201020193410467

  • 可以有返回值
  • 可以抛出异常
  • 方法不同,run() 换为 call()

原理

  1. 通过jdk8文档索引Runnable查看关系

    image-20201020194833337

  2. 点进RunnableFuture 类进行查看

    image-20201020195046643

  3. 再查看已知实现类 FutureTask

    image-20201020195137839

    可以看到 FutureTask 可用于包装CallableRunnable对象

使用方法

package com.xj.Callable;

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 { 
        // new Thread(new Runnable()).start();     // 直接使用Runnable的方法
        // new Thread(new FutureTask<V>()).start();
        // new Thread(new FutureTask<V>( Callable )).start();   // FutureTask作为适配类

        Mythread mythread = new Mythread();
        FutureTask futureTask = new FutureTask(mythread);    // 适配类

        new Thread(futureTask, "A").start();    // 执行的时候不会直接输出返回值,结果会被缓存提高效率
        // 获取结果,但使用get方法可能会造成线程阻塞,故放到最后或者使用异步通信处理
        String result = (String) futureTask.get();
        System.out.println(result);
    } 
} 

// 创建线程类,实现Callable<T>
// 参数 T 与返回值类型一致
class Mythread implements Callable<String> { 

    @Override
    public String call() throws Exception { 
        System.out.println("Call()");
        return "CallableTest";
    } 
} 

image-20201020195724337

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

1、CountDownLatch

减法计数器

image-20201020201751390

使用方法

  • new CountDownLatch(6) 定义数值大小
  • countDownLatch.countDown() 数值减一
  • countDownLatch.await() 计数器等待

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

执行

public class CountDownLatchTest { 
    public static void main(String[] args) throws InterruptedException { 
        // 设置总数是6,必须要执行任务的时候,可以使用来指定次数
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; i < 6; i++) { 
            new Thread(()->{ 
                System.out.println(Thread.currentThread().getName() + " Got Out");
                // 计数器减一
                countDownLatch.countDown();
            } , String.valueOf(i)).start();
        } 

        // 等待执行计数器归零,才继续执行,注意不是使用wait()
        countDownLatch.await();
        System.out.println("close door");
    } 
} 

image-20201020203248796

2、CyclicBarrier

加法计数器

image-20201020203558430

使用方法

package com.xj.add;

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

public class CyclicBarrierTest { 
    public static void main(String[] args) { 
        /**
         * 集齐7颗龙珠召唤神龙
         */
        // 召唤龙珠的线程,当有7个线程在等待的时候才开始执行
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{ 
            System.out.println("召唤神龙成功!");;
        } );

        for (int i = 1; i <= 7; i++) { 
            final int temp = i;
            // 注意: 在lambda表达式中不能直接操作到 i ,因为这相当于先创建了一个类,所以需要使用final(变量的作用域问题)
            new Thread(()->{ 
                System.out.println(Thread.currentThread().getName() + " 收集了" + temp + "个龙珠!");
                try { 
                    cyclicBarrier.await();  // 等待
                }  catch (InterruptedException e) { 
                    e.printStackTrace();
                }  catch (BrokenBarrierException e) { 
                    e.printStackTrace();
                } 
            } ).start();
        } 
    } 
} 

image-20201020204214574

当在等待的线程数量足够的时候才开始执行

image-20201020204820749

3、Semaphore

Semaphore :信号量

image-20201020205026872

相当于抢车位,需要等待前面线程执行完毕之后,后面的线程才可以补充上去!

使用方法

  • acquire 获得权限
  • release 释放权限
package com.xj.add;

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

public class SemaphoreTest { 
    public static void main(String[] args) { 
        // 可执行的线程数量(停车位),限流!
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 6; i++) { 
            new Thread(()->{ 
                try { 
                    // 获得权限
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + " 获得了车位");
                    // 停车2秒钟
                    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,然后唤醒等待的线程!

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

image-20201020210337920

9、读写锁ReadWriteLock

也是锁的一种,但将读写操作区分开来,读的时候可以多线程读取,写的时候只能单个线程进行!

image-20201020212155169

不适用读写锁的时候

package com.xj.rw;

import java.util.HashMap;
import java.util.Map;

public class ReadWriteLockTest { 
    public static void main(String[] args) { 
        MyCache cache = new MyCache();

        // 写入
        for (int i = 0; i < 6; i++) { 
            final int temp = i;
            new Thread(()->{ 
                cache.put(temp+"", temp);
            } , String.valueOf(i)).start();
        } 

        // 读取
        for (int i = 0; i < 5; i++) { 
            final int temp = i;
            new Thread(()->{ 
                cache.get(temp+"");
            } , String.valueOf(i)).start();
        } 
    } 
} 

/**
 * 自定义缓存类
 */
class MyCache{ 
    private volatile Map<String, Object> map = new HashMap<>();

    // 写操作
    public void put(String key, Object value){ 
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入OK");
    } 

    // 读操作
    public void get(String key){ 
        System.out.println(Thread.currentThread().getName() + "读取" + key);
        Object value = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取" + value + "OK");
    } 
} 

发现在写数据完成之前总是会被插队写数据!

image-20201020213353524

使用读写锁

package com.xj.rw;

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

/**
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(读锁) 多个线程可以同时占有
 * ReadWriteLock
 * 读-读 可以共存!
 * 读-写 不能共存!
 * 写-写 不能共存!
 */
public class ReadWriteLockTest { 
    public static void main(String[] args) { 
        MyCacheLock cache = new MyCacheLock();

        // 写入
        for (int i = 0; i < 6; i++) { 
            final int temp = i;
            new Thread(()->{ 
                cache.put(temp+"", temp);
            } , String.valueOf(i)).start();
        } 

        // 读取
        for (int i = 0; i < 5; i++) { 
            final int temp = i;
            new Thread(()->{ 
                cache.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){ 
        try { 
            // 加写锁
            readWriteLock.writeLock().lock();
            // 执行业务代码
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入OK");
        }  catch (Exception e) { 
            e.printStackTrace();
        } finally { 
            // 释放锁
            readWriteLock.writeLock().unlock();
        } 
    } 

    // 读取
    public void get(String key){ 
        try { 
            // 加读锁
            readWriteLock.readLock().lock();
            // 执行业务代码
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取" + value + "OK");
        }  catch (Exception e) { 
            e.printStackTrace();
        } finally { 
            // 释放锁
            readWriteLock.readLock().unlock();
        } 
    } 
} 
  • 创建读写锁 ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

  • 加写锁readWriteLock.writeLock().lock();

  • 释放写锁readWriteLock.writeLock().unlock();

  • 加读锁readWriteLock.readLock().lock();

  • 释放读锁readWriteLock.readLock().unlock();

从而实现写操作的时候只有一个线程执行,读取的时候可以多线程读取

image-20201020214423306

10、阻塞队列

后续更新~