JUC并发编程

进程线程协程

img_v3_028i_ae2338c8-a716-44e6-9db2-4dbb33e1ebdg.jpg

线程的生命周期和状态

f6a2ef62-9137-4f1f-9649-afc2f06e772e.jpeg

线程的上下文切换

c43869b7-dcdb-4f57-b9a2-7cc415c37851.jpeg

并发安全的三大特性

7b22fa88-56f3-40d7-b607-3e90cb6eba65.jpeg

JMM

qapUShuzV9.jpg
每个线程中都有共享变量的本地副本
针对第三点补充,在工作内存操作结束后,再写回主存。

happends-before

815cf34f-4475-4ea2-b238-d7a8dde13a00.jpeg

volatile关键字

ac69dbc5-f661-4df1-b21e-947fd3b156ce.jpeg

 * 双重检查方式
 */
public class Singleton {
    //私有构造方法
    private Singleton() {}
    private static volatile Singleton instance;
   //对外提供静态方法获取该对象
    public static Singleton getInstance() {
  //第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
        if(instance == null) {
            synchronized (Singleton.class) {
                //抢到锁之后再次判断是否为null
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
       return instance;
  }
}

该关键字很有必要,instance = new Singleton()分三步执行1.为instance分配内存空间 2初始化instance 3将instace指向分配的地址内存。
由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,
但是在多线程环境下会导致一个线程获得还没有初始化的实例。
例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance() 后发现 Instance 不为空,因此返回 Instance,但此时 Instance 还未被初始化。

synchronized关键字

是什么?

RvQlzfuKmq.jpg
锁是什么?

CPfXPnwVfc.jpg
底层原理?

SidKke9d1l.jpg
锁升级机制?

CjoCwVy3IV.jpg
JDK1.6后的底层优化

TIVFHd4Xki.jpg

ThreadLocal

每个线程都有自己的专属本地变量,不能被其他线程访问修改,ThreadLocal可以比作存数据的盒子,盒子可以存储每个线程的私有数据。创建一个ThreadLocal变量,访问这个变量的每个线程都会有这个变量的本地副本。
可以使用 get() 和 set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

原理:变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal上, ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。
ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。
每个Thread中都有一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。
默认情况下该map为null只有当前线程调用 ThreadLocal 类的 set或get方法时才创建它们,调用这两个方法的时候,调用的是ThreadLocalMap类对应的 get()、set()方法。

内存泄漏问题:ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。如果 ThreadLocal 没有被外部强引用的情况下,在GC,key 会被清理掉,而 value 不会被清理掉。
,ThreadLocalMap 中就会出现 key 为 null 的 Entry。如果不管,value 永远无法被 GC 回收,可能会产生内存泄露。
ThreadLocalMap 实现中考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法。

线程池

好处:

f0792759-08a2-4b78-8114-24b5f4c18325.jpeg
原理:

dd866175-6f9e-4723-b364-4499136f4920.jpeg

参数

06670b63-882a-4fe2-aa7e-74129ef1159a.jpeg
饱和策略:

34c74d0b-bd97-430a-9a02-924c35eb254a.jpeg
线程复用的原理

img_v3_028j_acb1eece-a2e5-48a0-a5d2-38975d78b4bg.jpg
几种常见线程池

f597ff84-41e0-465f-8981-92a391a891cc.jpeg

5855d83d-56d2-46d9-9bf4-8257f98d5e95.jpeg

PhP4r8Qf2l.jpg

c4mKPKNsuB.jpg
为什么先添加队列而不是先创建最大线程
oSteBkwBk8.jpg

如何创建线程

5d43b71b-82c9-4140-b1ca-90a1c57dfb05.jpeg

433ce195-a3e7-48dc-ab21-386154d695da.jpeg

悲观锁 乐观锁

0c0b36f9-c73a-4282-a919-d8aca4e516c4.jpeg

CAS

34a11fcf-5fe0-414f-83e5-b694c56ee526.jpeg
三大问题

e2bad3eb-d5eb-45d4-8ec8-230707d4f5f5.jpeg

AQS

mDJK06pHEj.jpg

ReentrantLock源码

77906568-ab4a-4c50-b442-496bd9daf755.jpeg

ehuNt1wrX7.jpg

75ad6fe9-a1f1-49fc-b5d9-b86db9788ec4.jpeg

sleep()和wait()方法、yield()、join()方法

aae8d75c-93a8-4bb7-8c85-3794e6657bbb.jpeg

为什么wait()不定义在Thread中,sleep()为什么定义在Thread中

9579404a-5ecf-4482-afe4-4a7791228fe2.jpeg

Lock接口与synchronized

740e2fe6-9835-462a-86a8-f59b6a89f894.jpeg

线程通信

线程通讯指的是多个线程之间通过共享内存或消息传递等方式来协调和同步它们的执行。在多线程中,会出现多个线程共同完成某个任务的情况,这时就需要线程之间进行通讯,以保证任务能够顺利地执行。

线程通讯的实现方式主要有两种:共享内存:多个线程可以访问同一个共享内存区域,通过读取和写入内存中的数据来进行通讯和同步。 消息传递:多个线程之间通过消息队列、管道、信号量等机制来传递信息和同步状态。

常见场景

线程通讯的常见场景有以下几个:
多个线程共同完成某个任务:例如一个爬虫程序需要多个线程同时抓取不同的网页,然后将抓取结果合并保存到数据库中。这时需要线程通讯来协调各个线程的执行顺序和共享数据。

避免资源冲突:多个线程访问共享资源时可能会引发竞争条件,例如多个线程同时读写一个文件或数据库。这时需要线程通讯来同步线程之间的数据访问,避免资源冲突。

保证顺序执行:在某些情况下,需要保证多个线程按照一定的顺序执行,例如一个多线程排序算法。这时需要线程通讯来协调各个线程的执行顺序。

线程之间的互斥和同步:有些场景需要确保只有一个线程能够访问某个共享资源,例如一个计数器。这时需要使用线程通讯机制来实现线程之间的互斥和同步。

实现方法

线程通讯的实现方法有以下几种:
等待和通知机制:使用 Object 类的 wait() 和 notify() 方法来实现线程之间的通讯。当一个线程需要等待另一个线程执行完某个操作时,它可以调用 wait() 方法使自己进入等待状态,同时释放占有的锁,等待其他线程调用 notify() 或 notifyAll() 方法来唤醒它。被唤醒的线程会重新尝试获取锁并继续执行。

信号量机制:使用 Java 中的 Semaphore (森魔佛)类来实现线程之间的同步和互斥。Semaphore 是一个计数器,用来控制同时访问某个资源的线程数。当某个线程需要访问共享资源时,它必须先从 Semaphore 中获取一个许可证,如果已经没有许可证可用,线程就会被阻塞,直到其他线程释放了许可证。

栅栏机制:使用 Java 中的 CyclicBarrier(赛克掰瑞额) 类来实现多个线程之间的同步,它允许多个线程在指定的屏障处等待,并在所有线程都达到屏障时继续执行。

锁机制:使用 Java 中的 Lock 接口和 Condition 接口来实现线程之间的同步和互斥。Lock 是一种更高级的互斥机制,它允许多个条件变量(Condition)并支持在同一个锁上等待和唤醒。

具体代码实现

等待通知实现以下是一个简单的

wait() 和 notify() 方法的等待通知示例:

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

在这个示例中,定义了一个共享对象 lock,ThreadA 线程先获取 lock 锁,并调用 lock.wait() 方法进入等待状态。ThreadB 线程在获取 lock 锁之后,调用 lock.notify() 方法唤醒 ThreadA 线程,然后 ThreadB 线程执行完毕。 运行以上程序的执行结果如下:ThreadA start… ThreadB start… ThreadB end… ThreadA end…

锁机制实现

以下是一个使用 Condition 的示例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionDemo {

private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private volatile boolean flag = false;

public static void main(String[] args) {
    ConditionDemo demo = new ConditionDemo();
    new Thread(demo::waitCondition).start();
    new Thread(demo::signalCondition).start();
}

private void waitCondition() {
    lock.lock();
    try {
        while (!flag) {
            System.out.println(Thread.currentThread().getName() + " is waiting for signal.");
            condition.await();
        }
        System.out.println(Thread.currentThread().getName() + " received signal.");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

private void signalCondition() {
    lock.lock();
    try {
        Thread.sleep(3000); // 模拟等待一段时间后发送信号
        flag = true;
        System.out.println(Thread.currentThread().getName() + " sends signal.");
        condition.signalAll();
    } catch (InterruptedException e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}

在这个示例中,创建了一个 Condition 对象和一个 Lock 对象,然后创建了两个线程,一个线程等待 Condition 信号,另一个线程发送 Condition 信号。等待线程在获得锁后,判断标志位是否为 true,如果为 false,则等待 Condition 信号;如果为 true,则继续执行后面的任务。发送线程在获得锁后,等待一段时间后,将标志位设置为 true,并且发送 Condition 信号。 运行以上程序的执行结果如下:Thread-0 is waiting for signal. Thread-1 sends signal. Thread-0 received signal.从上面执行结果可以看出,等待线程在等待 Condition 信号的时候被阻塞,直到发送线程发送了 Condition 信号,等待线程才继续执行后面的任务。Condition 对象提供了一种更加灵活的线程通信方式,可以精确地控制线程的等待和唤醒。

信号量

实现在 Java 中使用 Semaphore 实现信号量,Semaphore 是一个计数器,用来控制同时访问某个资源的线程数。当某个线程需要访问共享资源时,它必须先从 Semaphore 中获取一个许可证,如果已经没有许可证可用,线程就会被阻塞,直到其他线程释放了许可证。它的示例代码如下:

import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);

    for (int i = 0; i < 5; i++) {
        new Thread(new Worker(i, semaphore)).start();
    }
}

static class Worker implements Runnable {
    private int id;
    private Semaphore semaphore;

    public Worker(int id, Semaphore semaphore) {
        this.id = id;
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            semaphore.acquire();
            System.out.println("Worker " + id + " acquired permit.");
            Thread.sleep(1000);
            System.out.println("Worker " + id + " released permit.");
            semaphore.release();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

}
在这个示例中,创建了一个 Semaphore 对象,并且设置了许可数为 2。然后创建了 5 个 Worker 线程,每个 Worker 线程需要获取 Semaphore 的许可才能执行任务。每个 Worker 线程在执行任务之前先调用 semaphore.acquire() 方法获取许可,如果没有许可则会阻塞,直到 Semaphore 释放许可。执行完任务之后调用 semaphore.release() 方法释放许可。 运行以上程序的执行结果如下:Worker 0 acquired permit. Worker 1 acquired permit. Worker 1 released permit. Worker 0 released permit. Worker 2 acquired permit. Worker 3 acquired permit. Worker 2 released permit. Worker 4 acquired permit. Worker 3 released permit. Worker 4 released permit

栅栏

实现在 Java 中,可以使用 CyclicBarrier 或 CountDownLatch 来实现线程的同步,它们两个使用类似,接下来我们就是 CyclicBarrier 来演示一下线程的同步,CyclicBarrier 的示例代码如下:
在这里插入图片描述
在这里插入图片描述

在这个示例中,创建了一个 CyclicBarrier 对象,并且设置了参与线程数为 3。然后创建了 3 个 Worker 线程,每个 Worker 线程会先执行一些任务,然后等待其他线程到达 Barrier。所有线程都到达 Barrier 之后,Barrier 会释放所有线程并执行设置的 Runnable 任务。 运行以上程序的执行结果如下:Worker 2 is working. Worker 3 is working. Worker 1 is working. Worker 3 has reached the barrier. Worker 1 has reached the barrier. Worker 2 has reached the barrier. All threads have reached the barrier. Worker 2 is continuing the work. Worker 3 is continuing the work. Worker 1 is continuing the work.从以上执行结果可以看出,CyclicBarrier 保证了所有 Worker 线程都到达 Barrier 之后才能继续执行后面的任务,这样可以保证线程之间的同步和协作。在本示例中,所有线程都在 Barrier 处等待了一段时间,等所有线程都到达 Barrier 之后才继续执行后面的任务。

Atomic原子类?

介绍?

Atomic原子类是Java中提供的一组原子操作类,可以保证对于特定变量的操作具有原子性,即对于多线程并发访问同一个变量时,可以保证操作的原子性,不会出现数据不一致的问题。

Java中提供了多种Atomic原子类,例如AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等,它们分别对应着不同类型的变量,可以进行不同类型的原子操作。

Atomic原子类的实现原理是利用了CAS(Compare And Swap)算法,即比较并交换。CAS算法是一种无锁算法,可以在多线程并发访问时保证数据的原子性,它通过比较内存中的值和期望值是否相等来判断变量是否被修改过,如果没有被修改过,则进行修改,否则重新进行比较,直到修改成功为止。

使用Atomic原子类可以避免使用synchronized关键字或者Lock锁来保证数据的同步,从而提高程序的并发性能和效率。

应用场景

原子类的应用场景主要是在多线程并发访问共享变量时,保证对变量的操作具有原子性,避免出现数据不一致的问题。以下是一些常见的应用场景:

  1. 计数器:在多线程环境下对某个计数器进行增减操作时,可以使用AtomicInteger保证操作的原子性,避免出现并发问题。

  2. 缓存控制:在使用缓存时,可以使用AtomicReference来保证缓存的原子性,避免出现脏数据问题。

  3. 状态标记:在多线程环境下,可以使用AtomicBoolean来表示某个状态的标记,避免出现并发问题。

  4. 序列号生成器:在生成序列号时,可以使用AtomicLong来保证序列号的唯一性和原子性。

  5. 数据库乐观锁:在实现数据库乐观锁时,可以使用AtomicReference或AtomicStampedReference来保证数据的版本号的原子性。

总之,当需要在多线程环境下对某个共享变量进行操作时,可以考虑使用Atomic原子类来保证操作的原子性,从而避免出现并发问题。

代码示例

下面是一些常见应用场景的代码示例:

  1. 计数器
import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}
  1. 缓存控制
import java.util.concurrent.atomic.AtomicReference;

public class Cache {
    private AtomicReference<Object> cache = new AtomicReference<>(null);

    public void setCache(Object obj) {
        cache.set(obj);
    }

    public Object getCache() {
        return cache.get();
    }
}
  1. 状态标记
import java.util.concurrent.atomic.AtomicBoolean;

public class Worker {
    private AtomicBoolean isWorking = new AtomicBoolean(false);

    public void start() {
        if (isWorking.compareAndSet(false, true)) {
            // do something
        }
    }

    public void stop() {
        if (isWorking.compareAndSet(true, false)) {
            // do something
        }
    }
}
  1. 序列号生成器
import java.util.concurrent.atomic.AtomicLong;

public class SeqGenerator {
    private AtomicLong seq = new AtomicLong(0);

    public long getNextSeq() {
        return seq.incrementAndGet();
    }
}
  1. 数据库乐观锁
import java.util.concurrent.atomic.AtomicStampedReference;

public class User {
    private String name;
    private int age;
    private AtomicStampedReference<Integer> version = new AtomicStampedReference<>(0, 0);

    public void update(int newAge) {
        int oldVersion = version.getStamp();
        if (version.compareAndSet(age, newAge, oldVersion, oldVersion + 1)) {
            age = newAge;
        } else {
            throw new IllegalStateException("Concurrent update detected");
        }
    }
}

以上示例代码仅供参考,实际使用时需要根据具体的业务需求进行调整。

CompletableFuture?

介绍?

CompletableFuture是Java 8引入的一种异步编程的工具类,它可以很方便地处理异步操作。它提供了一种简单的方式来处理异步操作,并且可以很方便地进行串行和并行操作。

CompletableFuture可以看作是Java 8中的Future的增强版,它可以更好地支持异步编程。它提供了很多方法来处理异步操作,例如thenApply、thenAccept、thenCompose等等。这些方法可以将异步操作串联起来,形成一个异步的处理流程。

同时,CompletableFuture也提供了一些方法来支持并行操作,例如allOf、anyOf等等。CompletableFuture的优点在于它可以很方便地处理异步操作,同时提供了很多方法来支持串行和并行操作。它也可以很方便地处理一些复杂的异步操作,例如等待多个异步操作完成后再进行下一步操作等等。

优点?

Java中的CompletableFuture有以下优点:

  1. 异步编程:CompletableFuture可以很方便地处理异步操作,不需要使用回调函数或者线程池等繁琐的操作。

  2. 可组合性:CompletableFuture提供了很多方法来支持串行和并行操作,可以将多个异步操作组合在一起,形成一个异步处理流程。

  3. 异常处理:CompletableFuture可以很方便地处理异步操作中可能出现的异常,提供了一些方法来处理异常情况。

  4. 可取消性:CompletableFuture可以很方便地取消异步操作,提供了一些方法来取消异步操作。

  5. 非阻塞式:CompletableFuture的操作是非阻塞式的,可以提高程序的性能和效率。

  6. 可以处理复杂异步操作:CompletableFuture可以很方便地处理一些复杂的异步操作,例如等待多个异步操作完成后再进行下一步操作等等。

综上所述,Java中的CompletableFuture具有非常强大的异步处理能力,可以很方便地处理异步操作,提高程序的性能和效率。

代码示例

public class CompletableFutureExample {

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello, CompletableFuture!";
    });

    future.thenAccept(result -> System.out.println(result));
    System.out.println("CompletableFuture example");

    Thread.sleep(2000);
}

}

CountDownLatch

什么是?

CountDownLatch是一个同步工具类,它通过一个计数器来实现的,初始值为线程的数量。每当一个线程完成了自己的任务,计数器的值就相应得减1。当计数器到达0时,表示所有的线程都已执行完毕,然后在等待的线程就可以恢复执行任务。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值