Java 并发编程学习笔记

文章目录

      • 1、什么是高并发
        • 垂直扩展(公司的事)
        • 水平扩展(程序猿派上用场啦)
      • 2、进程和线程
        • Java 中实现多线程有几种方式?
        • JUC
      • 3、sleep 和 wait 的区别
      • 4、synchronized 锁定的是谁?
      • 5、Lock 锁
      • 6、死锁
      • 7、生产者消费者模式
      • 8、ConcurrentModificationException 异常
      • 9、JUC工具类
        • 1、CountDownLatch:减法计数器
        • 2、CyclicBarrier:加法计数器
        • 3、Semaphore:计数信号量
      • 10、读写锁
      • 11、线程池
      • 12、ForkJoin 框架

Java 并发编程的目的就是充分利用计算机的资源,把计算机的性能发挥到最大。

1、什么是高并发

并发 concurrency 和并行 parallelism 的区别

并发是指多个线程操作同一个资源,不是同时操作,而是交替操作,单核 CPU,只不过因为速度太快,看起来是同时执行(张三、李四,共用一口锅炒菜,交替执行)

并行才是真正的同时执行,多核 CPU,每个线程使用一个独立的 CPU 的资源来运行。(张三、李四,一人一口锅,一起炒菜)

并发编程是指使系统允许多个任务在重叠的时间段内执行的设计结构。(不一定是同时执行)

高并发是指我们设计的程序,可以支持海量任务的同时执行(任务的执行在时间段上有重叠的情况)

  • QPS:每秒响应的请求数,QPS 并不是并发数。
  • 并发数:是指在某一时间段内的请求数。
  • 吞吐量:单位时间内处理的请求数,由QPS 和并发数决定。
  • 平均响应时间:系统对一个请求作出响应的平均时间,QPS = 并发数/平均响应时间
  • 并发用户数:系统可以承载的最大用户数量。

互联网项目架构中,如何提高系统的并发能力?

  • 垂直扩展
  • 水平扩展

垂直扩展(公司的事)

提升单机的处理能力

1、增强单机的硬件性能(硬件):增加 CPU 的核数,硬盘扩容,内存升级。

2、提升系统的架构性能(软件):使用 Cache 来提高效率,异步请求增加单个服务的吞吐量,使用 NoSQL 来提升数据的访问性能。

水平扩展(程序猿派上用场啦)

集群、分布式都是水平扩展的方案

集群:多个人做同一件事情(3 个厨师同时炒菜)

分布式:把一件复杂的事情,拆分成几个简单的步骤,分别找不同的人去完成 (1 洗菜、2 切菜、3 炒菜)

1、站点层扩展:Nginx 方向代理,高并发系统,一个 Tomcat 带不起来,就找十个 Tomcat 去带

Java 并发编程学习笔记_第1张图片

2、服务层扩展:通过 RPC 框架实现远程调用,Dubbo、Spring Boot/Spring Cloud,将业务逻辑拆分到不同的 RPC Client,各自完成不同的业务,如果某些业务的并发量很大,就增加新的 RPC Client,理论上实现无限高并发。

3、数据层扩展:一台数据库拆成多台,主从复制、读写分离、分表分库。

2、进程和线程

进程就是计算机正在运行的一个独立的应用程序,进程是一个动态的概念,必须是运行状态,如果一个应用程序没有启动,那就不是进程。

线程就是组成进程的基本单位,可以完成特定的功能,一个进程是由一个或多个线程组成。

进程和线程的区别在于运行时是否拥有独立的内存空间,每个进程所拥有的空间都是独立的,互不干扰,多个线程是共享内存空间的,但是每个线程的执行是相互独立。

线程必须依赖于进程才能执行,单独线程是无法执行的,由进程来控制多个线程的执行。

Java 默认有几个线程?
Java 默认有两个线程:Main 和 GC

Java 本身是无法启动线程的

private native void start0();

Java 无法操作底层硬件,只能通过调用本地方法,C++ 编写的动态函数库,由 C++ 去操作底层启动线程,Java 只是间接调用。

Java 中实现多线程有几种方式?

1.继承 Thread

2.实现 Runnable

3.实现 Callable

Thread 是线程对象,Runnable 是任务

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        //获取Callable的返回值
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class MyCallable implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println("callable");
        return "hello";
    }
}

Java 并发编程学习笔记_第2张图片
Callable 与 Runnable 的区别:

  • Callable 的 call 方法有返回值,Runnable 的 run 方法没有返回值。
  • Callable 的 call 方法可以抛出异常,Runnable 的 run 方法不能抛出异常。
  • 在外部通过 FutureTask 的 get 方法异步获取执行结果,FutureTask 是一个可以控制的异步任务,是对 Runnable 实现的一种继承和扩展。

get 方法可能会产生阻塞,一般放在代码的最后。

Callable 有缓存。

JUC

java.util.concurrent,JDK 提供的并发编程工具包,是由一组类和接口组成,可以更好的支持高并发任务。

  • 耦合:资源和任务写在了一起。 如图:
    Java 并发编程学习笔记_第3张图片
    不推荐这种写法(虽然实现Runnable接口,但这是继承Thread的写法,因为资源写在run()中,没有彰显出实现Runnable接口的好处)
public class Test {
    public static void main(String[] args) {
        Account account = new Account();
        new Thread(account,"A").start();
        new Thread(account,"B").start();
    }
}
/**
 * 统计程序的访问量
 */
class Account implements Runnable{
    private static int num;
    @Override
    public synchronized void run() {
        num++;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
    }
}
  • 解耦:资源和任务分开。 如图:

Java 并发编程学习笔记_第4张图片
推荐这种实现多线程的写法:(实现Runnable接口方法的好处就在这:解耦

public class Test3 {
    public static void main(String[] args) {
        Account2 account2 = new Account2();
        //lambda表达式
        new Thread(()->{
            account2.count();
        },"A").start();

        new Thread(()->{
            account2.count();
        },"B").start();
    }
}

class Account2{
    private static int num;
    public synchronized void count(){
        num++;
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是第"+num+"位访客");
    }
}

实际开发中不会直接使用 sleep 休眠,而是使用 JUC 的方式:

TimeUnit.SECONDS.sleep(1)

优点:让休眠时间更加准确,毫秒+纳秒
实际上该方法底层调用的还是 Thread.sleep 方法:Thread.sleep(ms, ns);

规则:

  • 当纳秒大于 500 时,毫秒就加 1,当纳秒小于 500 时,不改变毫秒的值
  • 当毫秒为 0 的时候,只要纳秒不为 0 ,就将毫秒设置为 1

3、sleep 和 wait 的区别

1、来自于不同的类,sleep 是 Thread 类的方法,wait 是 Object 类的方法。

2、sleep 是让当前的线程对象暂停执行任务,操作的是线程对象。wait 是让正在访问当前对象的线程休眠,不是针对线程对象,而是针对线程对象要访问的资源。但是 wait 有一个前提,当前线程对象必须拥有该资源,所以 wait 方法只能在同步方法或者同步代码块,否则会抛出异常。

3、wait 释放锁,sleep 不释放锁

wait 的使用

import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        A a = new A();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                a.test(i);
            }
        }).start();
    }
}
/**
 * 资源
 */
class A{
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i+"------------------");
    }
}

如何解除阻塞?

1、指定 wait 的时间

this.wait(3000);

2、通过调用 notify 方法唤醒线程

import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        A a = new A();
        //任务线程
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                a.test(i);
            }
        }).start();
        //唤醒线程
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(7);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            a.test2();
        }).start();
    }
}
/**
 * 资源
 */
class A{
    public synchronized void test(int i){
        if(i == 5){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i+"------------------");
    }
    public synchronized void test2(){
        this.notify();
    }
}

notify 随机唤醒一个 wait 线程

notifyAll 唤醒所有 wait 线程

4、synchronized 锁定的是谁?

如果 synchronized 修饰非静态方法(实例方法),则锁定的是方法的调用者,只需要判断有几个对象,如果是一个对象,那么一定需要排队(线程同步),如果是多个对象,不需要排队

import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            data.func1();
        }).start();
        new Thread(()->{
            data.func2();
        }).start();
    }
}
class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }
    public synchronized void func2(){
        System.out.println("2...");
    }
}
import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        Data data1 = new Data();
        Data data2 = new Data();
        new Thread(()->{
            data1.func1();
        }).start();
        new Thread(()->{
            data2.func2();
        }).start();
    }
}

class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }

    public synchronized void func2(){
        System.out.println("2...");
    }
}
import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            data.func1();
        }).start();
        new Thread(()->{
            data.func3();
        }).start();
    }
}
class Data{
    public synchronized void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }
    public synchronized void func2(){
        System.out.println("2...");
    }
    public void func3(){
        System.out.println("3...");
    }
}

如果 synchronized 修饰的是静态方法,则锁定的是类,无论多少个对象调用,都会同步

import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        Data2 data1 = new Data2();
        Data2 data2 = new Data2();
        new Thread(()->{
            data1.func1();
        }).start();
        new Thread(()->{
            data2.func2();
        }).start();
    }
}
class Data2{
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }
    public synchronized static void func2(){
        System.out.println("2...");
    }
}
/*
2...
1...
*/

如果 synchronized 静态方法和实例方法同时存在,静态方法锁类,实例方法锁对象


import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            data.func1();
        }).start();
        new Thread(()->{
            data.func2();
        }).start();
    }
}
class Data2{
    public synchronized static void func1(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("1...");
    }
    public synchronized void func2(){
        System.out.println("2...");
    }
}
/*
2...
1...
*/

如果 synchronized 修饰的是代码块,则锁定的是传入的对象,能否实现线程同步,就看锁定的对象是否为同一个对象

import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        for (int i = 0; i < 5; i++) {
            Integer num = Integer.valueOf(128);
            new Thread(()->{
                data3.func(num);
            }).start();
        }
    }
}
/**
 * 1、输出 start
 * 2s
 * 2、输出 end
 */
class Data3{
    public void func(Integer num){
        synchronized (num){
            System.out.println(num+"start...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("end...");
        }
    }
}

Integer 的值 -128 - 127 之间,会使用包装类常量池,如果超出这个范围,不会使用包装类常量池。

import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        User user = new User();
        new Thread(()->{
            user.count();
        },"A").start();
        new Thread(()->{
            user.count();
        },"B").start();
    }
}
class User{
    private Integer num = 0;
    private Integer id = 0;
    public void count(){
//        synchronized (num){
        synchronized (id){
            num++;
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"是第"+num+"位访问");
        }
    }
}

5、Lock 锁

Lock 是对 synchronized 的升级,是一个 JUC 接口,常用的实现类是 ReentrantLock(重入锁)。

synchronized 通过 JVM 实现锁机制,ReentrantLock 通过 JDK 实现锁机制。

重入锁指可以给同一个资源添加多把锁,解锁的方式与 synchronized,synchronized 线程执行完毕之后自动释放锁,ReentrantLock 需要手动释放锁。

Synchronized

import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{

            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"B").start();
    }
}
class Ticket{
    private Integer saleNum = 0;
    private Integer lastNum = 30;
    public synchronized void sale(){
        if(lastNum > 0){
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            saleNum++;
            lastNum--;
            System.out.println(Thread.currentThread().getName()+"卖出了第"+saleNum+"张票,剩余"+lastNum+"张票");
        }
    }
}

Lock

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        },"B").start();
    }
}
class Ticket{
    private Integer saleNum = 0;
    private Integer lastNum = 30;
    Lock lock = new ReentrantLock();
    public void sale(){
        //上锁
        lock.lock();
        if(lastNum > 0){
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            saleNum++;
            lastNum--;
            System.out.println(Thread.currentThread().getName()+"卖出了第"+saleNum+"张票,剩余"+lastNum+"张票");
        }
        //解锁
        lock.unlock();
    }
}

synchronized 和 Lock 的区别

1、synchronized 是关键字,Lock 是接口。
2、synchronized 通过 JVM 实现锁机制,Lock 通过 JDK 实现锁机制。
3、synchronized 自动上锁,自动解锁,Lock 手动上锁,手动解锁。
4、synchronized 无法判断是否可以获得锁,Lock 可以判断是否拿到了锁。
5、synchronized 拿不到锁就会一直等待,Lock 不一定会一直等下去。
6、synchronizde 是非公平锁,Lock 可以设置是否为公平锁。

公平锁:排队,当锁没有被占用时,当前线程需要判断队列中是否有其他等待线程。

非公平锁:插队,当锁没有被占用时,当前线程可以直接占用,不需要判断队列中是否有其他等待线程。

ReentrantLock 除了可重入之外,还有一个可中断的特点:允许在某个线程等待时,主动去中断线程,不需要获取锁,但是会抛出异常。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        lock.lock();
        Thread thread = new Thread(()->{
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }            System.out.println(Thread.currentThread().getName()+"interrupted");
        });
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
    }
}

Java 并发编程学习笔记_第5张图片
ReentrantLock 还具备限时性的特点,可以判断某个线程在一定时间内能否获取到锁,tryLock 返回一个 boolean 的值,true 表示可以拿到锁,false 表示拿不到锁。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
    public static void main(String[] args) {
        TimeLock timeLock = new TimeLock();
        new Thread(()->{
            timeLock.getLock();
        },"A").start();
        new Thread(()->{
            timeLock.getLock();
        },"B").start();
    }
}
class TimeLock{
    private ReentrantLock lock = new ReentrantLock();
    public void getLock(){
        try {
            //3秒之内能否获取到锁
            if(lock.tryLock(3, TimeUnit.SECONDS)){
                System.out.println(Thread.currentThread().getName()+"拿到了锁");
                TimeUnit.SECONDS.sleep(5);
            }else{
                System.out.println(Thread.currentThread().getName()+"没有拿到锁");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //判断当前线程是否持有锁
            if(lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }
}

6、死锁

import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        DeadLock deadLock1 = new DeadLock(1);
        DeadLock deadLock2 = new DeadLock(2);
        new Thread(()->{
            deadLock1.lock();
        },"张三").start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            deadLock2.lock();
        },"李四").start();
    }
}
class Data{
}
class DeadLock{
    private int num;
    private static Data data1 = new Data();
    private static Data data2 = new Data();
    public DeadLock(int num) {
        this.num = num;
    }
    public void lock(){
        if(num == 1){
            System.out.println(Thread.currentThread().getName()+"获取到data1,等待获取data2");
            synchronized (data1){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (data2){
                    System.out.println(Thread.currentThread().getName()+"用餐完毕");
                }
            }
        }
        if(num == 2){
            System.out.println(Thread.currentThread().getName()+"获取到data2,等待获取data1");
            synchronized (data2){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (data1){
                    System.out.println(Thread.currentThread().getName()+"用餐完毕");
                }
            }
        }
    }
}

7、生产者消费者模式

在一个生产环境中,生产者和消费者在同一时间段内共享同一块缓冲区,生产者负责向缓冲区中添加数据,消费者负责从缓冲区中取出数据,生产者消费者模式本质是为了实现线程通信。

import java.util.concurrent.TimeUnit;
public class ProCon {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                data.increment();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                data.decrement();
            }
        },"B").start();
    }
}
class Data{
    private Integer num = 0;
    /**
     * 加一
     */
    public synchronized void increment(){
        while (num!=0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num++;
        this.notify();
        System.out.println(Thread.currentThread().getName()+"--->"+num);
    }
    /**
     * 减一
     */
    public synchronized void decrement(){
        //当num==0,不要再减
        while(num == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        num--;
        this.notify();
        System.out.println(Thread.currentThread().getName()+"--->"+num);
    }
}

判断的时候必须使用 while 循环,而不能使用 if,因为使用 if 会存在线程虚假唤醒的问题,虚假唤醒是指 wait 会在除了 notify 以外的情况被唤醒,而此时是不应该被唤醒的,使用 while 可以多次检测,避免虚假唤醒的问题。

Lock

import java.lang.management.LockInfo;
import java.time.LocalDateTime;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                data.increment();
            }
        },"A").start();

        new Thread(()->{
            for (int i = 0; i < 30; i++) {
                data.decrement();
            }
        },"B").start();
    }
}
class Data{
    private Integer num = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    /**
     * 加一
     */
    public void increment(){
        try {
            lock.lock();
            while (num!=0){
                condition.await();
            }
            num++;
            condition.signal();
            System.out.println(Thread.currentThread().getName()+"--->"+num);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    /**
     * 减一
     */
    public void decrement(){
        try {
            lock.lock();
            while(num == 0){
                condition.await();
            }
            num--;
            condition.signal();
            System.out.println(Thread.currentThread().getName()+"--->"+num);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

Condition 接口也提供了类似 Object 的监视方法,与 Lock 配合可以实现等待/通知模式,Condition 对象依赖于 Lock 对象的。

8、ConcurrentModificationException 异常

并发修改异常

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

ArrayList 线程不安全

如何解决?(三种解决方法)

1、将 ArrayList 替换成线程安全的 Vector。

ArrayList add 非线程安全的

Java 并发编程学习笔记_第6张图片

Vector add 线程安全
Java 并发编程学习笔记_第7张图片
2、Collections.synchronizedList

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

3、JUC CopyOnWriteArrayList

CopyOnWrite 写时复制

public class Test {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }
    }
}

Java 并发编程学习笔记_第8张图片

Java 并发编程学习笔记_第9张图片

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
public class Test {
    public static void main(String[] args) {
        /**
         * List
         */
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                list.add("a");
                System.out.println(list);
            }).start();
        }

        /**
         * Set
         */
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            new Thread(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                set.add(String.valueOf(temp));
                System.out.println(set);
            }).start();
        }

        /**
         * Map
         */
        Map<String,String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            try {
                TimeUnit.MILLISECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }           map.put(UUID.randomUUID().toString().substring(0,3),UUID.randomUUID().toString().substring(0,3));
            System.out.println(map);
        }
    }
}

9、JUC工具类

1、CountDownLatch:减法计数器

public class CountDownLatchTest {
    public static void main(String[] args) {
        //优先执行,执行完毕之后,才能执行 main
        //1、实例化计数器,100
        CountDownLatch countDownLatch = new CountDownLatch(100);
        new Thread(()->{
            for (int i = 0; i < 90; i++) {
                System.out.println("++++++++++Thread");
                countDownLatch.countDown();
            }
        }).start();
        for (int i = 0; i < 10; i++) {
            countDownLatch.countDown();
        }
        //2、调用 await 方法
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 100; i++) {
            System.out.println("main--------------");
        }
    }
}

countDown() 计数器减一
await() 计数器停止,唤醒其他线程
new CountDownLatch(100)、coutDown()、await() 必须配合起来使用,创建对象时赋的值是多少,coutDown() 就要执行多少次,否则计数器不会清零,计数器就不会停止后,其他线程无法唤醒。

2、CyclicBarrier:加法计数器

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
    public static void main(String[] args) {
        //定义一个计数器,当计数器的值累加到10,输出"放行"
        CyclicBarrier cyclicBarrier = new CyclicBarrier(30,()->{
            System.out.println("放行");
        });
        for (int i = 1; i <= 90; i++) {
            final int temp = i;
            new Thread(()->{
                System.out.println("-->"+temp);
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

await() 执行 CyclicBarrier 的业务,CyclicBarrier 中的 Runnable 执行一次之后,计数器清零,等待下一次执行。

3、Semaphore:计数信号量

实际开发中主要用来做限流操作,即限制可以访问某些资源的线程数量。

  • 初始化
  • 获取许可
  • 释放许可
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreTest {
    public static void main(String[] args) {
        //同时只能进5个人
        Semaphore semaphore = new Semaphore(5);
        for (int i = 0; i < 15; i++) {
            new Thread(()->{
                try {
                    //获得许可
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"进店购物");
                    TimeUnit.SECONDS.sleep(5);
                    System.out.println(Thread.currentThread().getName()+"出店");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //释放许可
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}

acquire():等待获取许可,如果没有名额,则等待。
release():释放许可,并且唤醒等待的线程。

10、读写锁

接口 ReadWriteLock,实现类 ReentrantReadWriteLock,可以多线程同时读,但是同一时间内只能有一个线程写。

读写锁也是线程同步,只不过粒度更细,分别给读操作和写操作上不同的锁。

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
    public static void main(String[] args) {
        Cache cache = new Cache();
        //5个线程写
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                cache.write(temp,String.valueOf(temp));
            }).start();
        }
        //5个线程读
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            new Thread(()->{
                cache.read(temp);
            }).start();
        }
    }
}
class Cache{
    private Map<Integer,String> map = new HashMap<>();
    ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    /**
     * 写
     */
    public void write(Integer key,String value){
        //加写入锁
        readWriteLock.writeLock().lock();
        System.out.println(key+"开始写入...");
        map.put(key,value);
        System.out.println(key+"写入完毕...");
        //释放写入锁
        readWriteLock.writeLock().unlock();
    }
    /**
     * 读
     */
    public void read(Integer key){
        //加读取锁
        readWriteLock.readLock().lock();
        System.out.println(key+"开始读取...");
        map.get(key);
        System.out.println(key+"读取完毕...");
        //释放读取锁
        readWriteLock.readLock().unlock();
    }
}

写入锁也叫独占锁,只能被一个线程占用,读取锁也叫共享锁,多个线程可以同时占用。

11、线程池

常规考点:
1、7 大参数
2、3 种方法
3、4 种拒绝策略

线程池思想:预先创建好一定数量的线程对象,存入缓冲池中,需要用的时候直接从缓冲池中取出,用完之后不要销毁,还回到缓冲池中。
优点:
1、提高线程的利用率
2、提高响应速度
3、便于统一管理线程对象
4、可控制最大并发数

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
    public static void main(String[] args) {
        //单例线程池,缓冲池中只有一个线程读写
//        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //指定线程个数的线程池
//        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //缓冲线程池,线程个数由电脑硬件配置决定
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            executorService.execute(()->{
                System.out.println(Thread.currentThread().getName()+":"+temp);
            });
        }
        executorService.shutdown();
    }
}

//线程池源码如下:

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

7大参数:
①corePoolSize:核心池大小
②maximumPoolSize:线程池最大线程数
核心池大小就是线程池初始化的大小,最大线程数是一种应急方案,任务量突增的时候,额外去创建一定数量的线程对象,但是线程池中总的线程个数不能超过最大值。
③keepAliveTime:线程对象在没有任务的情况,最多保存多长时间,时间数值。
④unit:时间单位

TimeUnit.DAYS;		天
TimeUnit.HOURS;		小时
TimeUnit.MINUTES;	分钟
TimeUnit.SECONDS;	秒
TimeUnit.MILLISECONDS; 毫秒
TimeUnit.MICROSECONDS; 微秒
TimeUnit.NANOSECONDS;	 纳秒

⑤workQueue:工作队列,阻塞队列

ArrayBlockingQueue:基于数组的先进先出。
LinkedBlockingQueue:基于链表的先进先出。
SynchronousQueue:不会保存任务,而是直接新建一个线程来执行任务。
PriorityBlockQueue:具有优先级的队列。

⑥threadFactory:线程工厂,用来创建线程对象
⑦RejectedExecutionHandler:拒绝执行处理器

AbortPolicy:抛出异常
DiscardPolicy:放弃任务,不抛出异常
DisccardOldestPolicy:尝试与队列最前面的任务去竞争,不抛出异常
CallerRunsPlicy:谁调用谁处理7 大参数

Java 并发编程学习笔记_第10张图片
自定义实现线程池

import java.util.concurrent.*;
public class Test {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService executorService = null;
        executorService = new ThreadPoolExecutor(
                2,
                3,
                1L,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
                );
        //开始营业	分别尝试1-6个人
        for (int i = 0; i < 6; i++) {
            executorService.execute(()->{
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"==>办理业务");
            });
        }
        executorService.shutdown();
    }
}

四种拒绝策略:

  • AbortPolicy()
    在这里插入图片描述
  • DiscardPolicy()
    Java 并发编程学习笔记_第11张图片
  • DiscardOldestPolicy()
    Java 并发编程学习笔记_第12张图片
  • CallerRunsPolicy()
    Java 并发编程学习笔记_第13张图片

12、ForkJoin 框架

  ForkJoin 是 JDK 1.7 提供多线程并发处理框架,本质上是对线程池的一种补充,它的核心思想就将一个大型任务拆分成很多个小任务,分别执行,最终再将小任务的结果进行汇总。

Java 并发编程学习笔记_第14张图片

本质就是把一个线程任务拆分成多个线程并发执行。

工作窃取

A、B 两个线程同时执行,A 的任务比较多,B 先执行完了,此时 B 将 A 的一部分任务拿过来替 A 执行,从而提升效率。

ForkJoin 框架的使用需要用到两个类

  • ForkJoinTask:任务
  • ForkJoinPool:线程池

ForkJoinTask 拆分任务,ForkJoinPool 提供线程对象来执行任务,之后合并。

重点是搞定 ForkJoinTask 如何拆分任务?

ForkJoinTask 使用的是递归的思想。
递归
一个函数,直接或间接调用自身,就是递归。
Java 并发编程学习笔记_第15张图片
1、找出递推公式

f(n) = f(n-1) + 1
f(1) = 1

2、由递推公式得出函数

public int f(int n){
	if(n == 1) return 1;
	return f(n-1) + 1;
}

递归需要满足 3 要素:

1、一个父问题可以拆分成若干个子问题,并且若干个子问题的结果汇总起来就是父问题的答案。
2、父问题和子问题,解题思路完全一致,只是数据的规模不同。
3、存在终止条件。

假设有 n 个台阶,每次可以跨 1 个台阶或者 2 个台阶,请问走完这 n 个台阶,一共有多少种走法?

可以根据第一步的走法分成两类:

1、第一步走了 1 步,n-1 个台阶的走法
2、第一步走了 2 步,n-2 个台阶的走法

两类的总是相加就是最终的结果。

1、找出递推公式

f(n) = f(n-1)+f(n-2)
f(1) = 1
f(2) = 2

2、由递推公式得出函数

public int f(int n){
	if(n == 1) return 1;
	if(n == 2) return 2;
	return f(n-1)+f(n-2);
}
public class Test {
    public static void main(String[] args) {
        for (int i = 1; i <= 30; i++) {
            System.out.println(i+"个台阶共有走法:"+f(i));
        }
    }
    public static int f(int n){
        if(n == 1) return 1;
        if(n == 2) return 2;
        return f(n-1)+f(n-2);
    }
}

如何使用forkjoin(计算 0-20 亿数字之和)
Java 并发编程学习笔记_第16张图片

import java.util.concurrent.RecursiveTask;
public class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;
    private Long end;
    private Long temp = 100_0000L;
    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        //到达临界值,不再拆分
        if((end-start)<temp){
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        }else{
            Long avg = (start+end)/2;
            ForkJoinDemo task1 = new ForkJoinDemo(start,avg);
            task1.fork();
            ForkJoinDemo task2 = new ForkJoinDemo(avg,end);
            task2.fork();
            return task1.join()+task2.join();
        }
    }
}

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
public class Test {
    public static void main(String[] args) {
//        Long sum = 0L;
//        Long startTime = System.currentTimeMillis();
//        for (Long i = 0L; i <= 20_0000_0000L ; i++) {
//            sum += i;
//        }
//        Long endTime = System.currentTimeMillis();
//        System.out.println(sum+",耗时"+(endTime-startTime));
        Long startTime = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L,20_0000_0000L);
        forkJoinPool.execute(task);
        Long sum = 0L;
        try {
            sum = task.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        Long endTime = System.currentTimeMillis();
        System.out.println(sum+",耗时"+(endTime-startTime));
    }
}

接下来请看:Java 并发编程常见面试题
https://blog.csdn.net/qq_41822345/article/details/104640761

你可能感兴趣的:(二.Java并发编程篇)