- 多线程学习笔记
- 多线程基础
- 一、进程与线程
- 1.1 进程与线程的概念
- 1.2 线程状态
- 二、多线程
- 2.1 概念区分
- 2.2 多线程编程应用场景
- 三、线程编程
- 3.1 线程的创建方式
- 3.2 线程优先级
- 3.3 关键字和方法讲解
- 3.3 线程停止方式
- 3.4 多线程编程步骤
- 3.5 生产者消费者模式
- JUC并发编程
- 一、JUC简介
- 1、什么是JUC:
- 2、线程Thread的状态
- 3、管程
- 4、用户线程和守护线程
- 二、Lock接口
- 三、虚假唤醒
- 四、线程定制化通信
- 4.1 Condition条件队列
- 4.2 实现方式
- 五、集合的线程安全
- 列表的线程不安全
- HashSet线程不安全
- HashMap线程不安全
- 六、多线程锁
- 6.1 死锁
- 6.2 公平锁和非公平锁
- 6.3 可重入锁和不可重入锁
- 6.4 乐观锁和悲观锁
- 6.5 读写锁
- 6.6 锁降级
- 七、Callable接口
- 八、辅助类
- 8.1 减少计数CountDownLatch
- 8.2 循环栅栏CyclicBarrier
- 8.3 信号灯Semaphore
- 十、阻塞队列
- 10.1 概述
- 10.2 常见的BlockingQueue
- 10.3 核心方法
- 十一、线程池
- 11.1 线程池的框架
- 11.2 线程池的使用方式
- newFixedThreadPool
- newSingleThreadExecutor
- newCachedThreadPool
- 11.3 线程池参数讲解
- 11.4 拒绝策略
- 11.5 自定义线程池
- 十二、Fork/Join框架
- 12.1 Fork/Join框架简介
- 12.2 使用案例
- 十三、CompletableFuture
-
进程是计算机的程序关于某数据集合的一次运行活动
-
进程是资源分配的最小单位,线程是资源调度的最小单位
-
线程是进程中一个独立的执行流
**进程和程序:**程序是指令、数据及其组织形式的描述,进程是程序的实体
1.2 线程状态线程状态图:
五个状态:
- 新建:new创建后到start方法前
- 就绪:调用start方法后就是处于就绪态
- 运行:获取CPU资源,执行run方法,此时就是运行态
- 阻塞:
- 同步阻塞:synchronized获取同步锁失败阻塞
- 等待阻塞:执行wait方法后
- 其他阻塞:sleep,join方法执行后
- 死亡:线程执行结束,或其他条件终止
并发与并行:
- 并发:一个时间段内,几个任务同时进行。
- 并行:同一时间点,几个任务同时进行,必要条件:多核,多处理器。
同步与异步:
- 同步:任务有序执行,一个任务执行完,再执行下一个任务。
- 异步:几个任务独立执行,执行一个任务的同时也可以执行另一个任务。
异步与多线程:
- 异步是目的,多线程是实现异步的一种手段
什么是多线程:
- 是指从软件或者硬件上实现多个线程并发执行的技术
- 高并发
- 处理大任务:分步上传,分步计算
- 耗时操作
- 低优先级定时操作:垃圾回收
-
继承Thread类:
- 重写run方法
-
实现Runnable接口:
- 重写run方法
- 将Runnable交给Thread执行
-
Callable接口和FutureTask
- 基于委托的适配器模式(FutureTask适配Runnable和Callable)
- 将FutureTask(Runnable)交给Thread执行
-
线程池
- 将Runnable交给线程池的execute方法执行
public class ThreadTest {
public static void main(String[] args) {
//方式1 Thread
MyThread myThread = new MyThread("w");
myThread.start();
//方式2 Runnable
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable,"我的线程2").start();
//方式3 Callable和FutureTask
MyCallable myCallable = new MyCallable();
FutureTask futureTask = new FutureTask<>(myCallable);
new Thread(futureTask,"我的线程3").start();
//方式4 线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 2L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
threadPool.execute(myRunnable);
}
}
class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println("我的线程");
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("创建可执行任务");
}
}
class MyCallable implements Callable {
@Override
public Integer call() throws Exception {
System.out.println("创建MyCallable");
return 1;
}
}
3.2 线程优先级
Java线程优先级是整数,范围(Thread.MIN_PRIORITY )1 —(Thread.MAX_PRIORITY )10,默认优先级 NORM_PRIORITY(5)。
更改优先级:
- setPriority(int newPriority)
关键字:
- volatile:变量可见,防止指令重排
- synchronzied:同步悲观锁
- 修饰普通方法:锁当前对象
- 修饰静态方法:锁类对象
- 修饰同步方法块:锁synchronized括号里配置的对象
方法:
- wait:
- 是对象进入等待态,同步阻塞
- wait会释放锁,出现中须定义同步锁
- notify:随机唤醒等待队列中持有相同锁的对象
- notifyAll:唤醒所有等待队列中持有相同锁的对象
- sleep:线程休眠
- join:让调用方法的线程加入到当前线程,顺序执行
- yield:让步,让相同或更高优先级的线程执行
- interrupt:设置线程中断标志位为true
- interrupt可以的打断sleep,wait,join使其抛出异常
- interrupted:返回中断状态,并清除中断状态
- isInterrupted:返回中断状态,不清除中断状态
问题:
volatile原理:
将变量的修改直接写入主存中,保证各个线程对于变量的可见性。
sleep方法和wait方法:
- sleep是Thread类的静态方法,wait是Object的实例方法
- sleep不会释放锁,wait会释放锁
- wait用于同步代码块,sleep不需要写在同步代码块中interrupt唤醒
- wait作用与对象,sleep作用于线程
为什么wait方法是Object类
- 简单来说,Java中锁的级别是对象,由对象本身(共享资源本身)来持有锁。
-
运行完停止
-
自定义中断标志符
使用volatile修饰一个变量作为标识
volatile boolean flag = false; Thread t = new Thread(()->{ while (true) { System.out.println("hello"); if (flag) { System.out.println("线程结束"); break; } } }); t.start(); Thread.sleep(1000); flag = true; -
interrupt:
使用interrupt和isInterrupted使线程停止。
Thread t = new Thread(()->{ while (!Thread.currentThread().isInterrupted()) { System.out.println("hello"); } }); t.start(); Thread.sleep(1000); t.interrupt();
- 创建资源类,在资源类创建属性和操作方法
- 在资源类操作方法
- 判断
- 干活
- 通知
- 创建多个线程,调用资源类的操作方法
- 防止虚假唤醒
public class ConsumerProducer {
public static void main(String[] args) {
Buffer buffer = new Buffer(5);
Producer producer = new Producer(buffer);
Consumer consumer = new Consumer(buffer);
new Thread(producer).start();
new Thread(consumer).start();
}
}
//资源类
class Buffer {
private Queue queue;
private int size;
public Buffer(int size) {
this.queue = new LinkedList();
this.size = size;
}
public synchronized void add(int i) throws InterruptedException {
//判断
while (queue.size() >= size) {
wait();
}
//干活
queue.add(i);
System.out.println("生产者生成了:"+i);
//通知
notifyAll();
}
public synchronized int get() throws InterruptedException {
while (queue.isEmpty()) {
wait();
}
Integer value = queue.poll();
notifyAll();
return value;
}
}
class Producer implements Runnable {
private Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
buffer.add(i);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable {
private Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
int thing = buffer.get();
System.out.println("消费者消费了:"+thing);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
JUC并发编程
一、JUC简介
1、什么是JUC:
- java.util.concurrent工具包的简称
- 这是一个处理线程的工具包,JDK1.5出现的
- NEW:新建
- RUNNABLE:准备就绪
- BLOCKED:阻塞
- WATING:等待
- TIMED_WAITING:过时不候
- TERMINATED:终止
- 监视器Monitor
- 通常说的锁
- 是一种同步机制,保证同一时间,只有一个线程访问被保护的数据或者代码
- JVM的同步基于进入和退出,使用管程对象实现
- 用户线程:主线程结束,用户线程还在运行,jvm存活
- 守护线程:没有用户线程,都是都是守护线程,vnm结束
Lock和synchronize的比较:
- Lock锁提供了比同步方法和语句更广泛的操作。结构更灵活,并且可以支持多个关联的条件对象(Condition)
- Lock是接口,不是java内置的,synchronize是java的关键字。
- Lock需要手动释放锁,synchronize不用手动释放锁,发生异常时,synchronize会自动释放锁,Lock不可以。
- Lock等待的线程可以使用interrupt中断,synchronize等待的线程不会响应中断
- 通过Lock可以知道是否成功获得锁(tryLock方法),synchronize不行
synchronized卖票例子:
class Ticket {
private int ticketNum = 30;
public synchronized void sale() {
if( ticketNum > 0) {
System.out.println(Thread.currentThread().getName()+"卖出第"+(ticketNum--)+"票,还剩"+ticketNum+"张票");
}
}
}
public class SaleTicketSyn {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i< 30; i++)
ticket.sale();
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0; i< 30; i++)
ticket.sale();
}
},"B").start();
}
}
可重入锁卖票:
class TicketL {
private int ticketNum = 30;
private final ReentrantLock lock = new ReentrantLock();
public synchronized void sale() {
lock.lock();
try {
if( ticketNum > 0) {
System.out.println(Thread.currentThread().getName()+"卖出第"+(ticketNum--)+"票,还剩"+ticketNum+"张票");
}
} finally {
lock.unlock();
}
}
}
三、虚假唤醒
虚假唤醒问题:
- 出现情况:使用if条件判断,可能会出现虚假唤醒问题
- 当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
- 出现原因:
- 因为if只会执行一次,执行完会接着向下执行if()外边的,可能出现有线程跳过if条件的情况
- 而while不会,直到条件满足才会向下执行while()外边的
出现虚假唤醒的例子:
class Share {
private int num = 0;
public synchronized void incr() throws InterruptedException {
if (num != 0) { //使用if
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
this.notifyAll();
}
public synchronized void decr() throws InterruptedException {
if (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+":"+num);
this.notifyAll();
}
}
public class AddMinusSyn {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i=0; i<20; i++) {
try {
share.incr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i=0; i<20; i++) {
try {
share.decr();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
防止虚假唤醒的例子:
class ShareL {
private int num = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void incr() throws InterruptedException {
lock.lock();
try {
while (num != 0) { //使用while
condition.await();
}
num++;
System.out.println(Thread.currentThread().getName()+":"+num);
condition.signalAll();
} finally {
lock.unlock();
}
}
public void decr() throws InterruptedException {
lock.lock();
try {
while (num != 1) {
condition.await();
}
num--;
System.out.println(Thread.currentThread().getName()+":"+num);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
四、线程定制化通信
4.1 Condition条件队列
- condition理解为条件(条件队列或条件变量),可以控制一组线程
- condition和锁绑定
Object的wait和notify对比Condition的await和signal:
- Object的监视是java底层实现的,Condition是语言层面实现的
- Object只有一个等待队列,Condition支持多个
- Conditon支持进入等待状态不响应中断,Object不支持
- Conditon支持等待状态设置时间,Object不支持
**思路:**使用标志位来控制线程执行顺序
例子:A,B,C轮替执行
class Share {
private int flag = 1;
private ReentrantLock lock = new ReentrantLock();
private Condition c1 = lock.newCondition();
private Condition c2 = lock.newCondition();
private Condition c3 = lock.newCondition();
public void funA() throws InterruptedException {
lock.lock();
try {
while (flag != 1) {
c1.await();
}
this.flag = 2;
System.out.println(Thread.currentThread().getName()+"funA");
c2.signal();
} finally {
lock.unlock();
}
}
public void funB() throws InterruptedException {
lock.lock();
try {
while (flag != 2) {
c2.await();
}
this.flag = 3;
System.out.println(Thread.currentThread().getName()+"funB");
c3.signal();
} finally {
lock.unlock();
}
}
public void funC() throws InterruptedException {
lock.lock();
try {
while (flag != 3) {
c3.await();
}
this.flag = 1;
System.out.println(Thread.currentThread().getName()+"funC");
c1.signal();
} finally {
lock.unlock();
}
}
}
五、集合的线程安全
异常:并发修改异常
列表的线程不安全解决方案:
-
Vector:效率低,比较老
-
Collections:
- Collections.synchronziedList(new ArrayList<>()) 会返回一个线程安全的集合
- 比较老
-
CopyOnWriteArrayList:写时复制
- 原理:写入数据的时候,将原来的数组复制一份,在复制的那个数组加锁写入数据,之后覆盖掉原来的数组
- 读不加锁,写加锁
解决方案:
- CopyOnWriteHashSet
解决方案:
- ConcurrentHashMap
两个及以上进程在执行过程中,由于竞争资源或彼此通信而造成的一种阻塞现象,若无外力作用,它们将永远等待下去。
产生死锁的四个原因
- 资源独占
- 不可抢占
- 保持请求
- 循环等待
判断死锁
- jps -l:查看进程
- jstack 进程:可以查看是否死锁
- 非公平锁:
- 多个线程去获取锁的时候会直接获取,获取不到会进入队列排队
- 减少CPU唤醒线程的开销,效率更高
- 公平锁:
- 多个线程按照申请锁的顺序去获得锁,线程会进入队列排队,只有第一位才能得到锁
- 不会出现线程饿死
- 可重入锁:线程获取了该对象的锁后,可以获取该对象的其他锁,将锁的次数加一
- 避免死锁
- 不可重入锁:线程获取了该对象的锁后,获取该对象的其他锁时,将判断该对象的锁是否释放,未释放就不能获取
例子:
- 执行methodA方法
- 可重入锁在获得methodA的锁后,因为是同一线程,也可以获取methodB的锁
- 不可重入锁在获得methodA的锁后,执行methodB,但是B方法已上锁,不能需要等待,发生死锁
public class LockTest {
Lock lock = new Lock();
public void methodA() {
lock.lock();
try {
System.out.println("A方法已上锁");
this.methodB();
} finally {
lock.unlock();
}
}
public void methodB() {
lock.lock();
try {
System.out.println("B方法已上锁");
} finally {
lock.unlock();
}
}
}
6.4 乐观锁和悲观锁
- 乐观锁
- 简介:在操作数据时比较乐观,认为没有其他线程同时操作数据,不会上锁,但是在更新数据时会判断该数据是否被修改过。
- 实现:
- CAS机制
- 版本号比较
- 适合读操作较多的情况
- 悲观锁
- 简介:操作数据时比较悲观,认为别的线程也会同时修改数据,每次拿数据时都会上锁。
- 实现:synchronized和Lock的可重入锁
- 适合写操作较多的情况
读写锁:一个资源可以被多个读线程访问,或者一个写线程访问,但不能同时存在读写线程,读写互斥,读读共享。
实现方法:
- 使用ReentrantReadWriteLock,可以获取读锁和写锁
- 获取写锁——获取读锁——释放写锁——释放读锁
- 写锁可以降级为读锁,读锁不能升级为写锁
使用Callable接口可以时线程有返回值。
Runnable接口和Callable接口:
- Runnable接口无返回值,Callable接口有返回值
- Runnable接口无法抛出异常,Callable接口可以抛出异常
- 一个是run()方法,一个是call()方法
FutureTask:
- 可取消的异步计算
原理:
基于委托的适配器模式
案例:
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//方式1
FutureTask futureTask = new FutureTask<>(new CallableClass());
//方式2lambda表达式
FutureTask futureTask1 = new FutureTask(()->{
System.out.println(Thread.currentThread().getName()+"进入callable");
return 200;
});
new Thread(futureTask,"A").start();
System.out.println(futureTask.get());
}
}
class CallableClass implements Callable {
@Override
public Object call() {
System.out.println(Thread.currentThread().getName()+"进入callable");
return 200;
}
}
八、辅助类
8.1 减少计数CountDownLatch
CountDownLatch类可以设置一个计数器,然后通过countDown方法来进行减一的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句。
- CountDownLatch主要有两个方法,一个或多个线程调用await方法时,这些线程会阻塞
- 其他线程调用countDown方法会将计数器减一(调用该方法的线程不会阻塞)
- 计数器减为0时,因await方法阻塞的线程会被唤醒,继续执行
使用流程:
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i=0; i<6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"走了");
countDownLatch.countDown();
},i+"").start();
}
countDownLatch.await();
System.out.println("线程都运行完了");
8.2 循环栅栏CyclicBarrier
CyclicBarrier构造方法第一个参数时目标障碍数,每执行一次障碍数加一,达到目标障碍数之后才会执行await之后的语句。可以理解为加一操作。
使用流程:
final int NUM = 7;
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUM,()->{
System.out.println("阻塞七个,我可以运行了");
});
for (int i = 0; i < 7; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"阻塞");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},i+"").start();
}
CyclicBarrier和CountDownLatch的区别:
- 动作的实施者:CountDownLatch计数为0时,动作实施者时主线程,而CyclicBarrier是其他线程
- 使用的次数:CountDownLatch只能使用一次,CyclicBarrier可以使用reset()方法重复使用
六辆车停3个停车位,加减的操作。
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"进行停车");
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
System.out.println(Thread.currentThread().getName()+"-----离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
十、阻塞队列
10.1 概述
10.2 常见的BlockingQueue
ArrayBlockingQueue
由数组结构组成的有界阻塞队列
LinkedBlockingQueue
由链表组成的有界阻塞队列
DelayQueue
使用优先级队列实现的延迟无界阻塞队列
PriorityBlockingQueue
支持优先级排序的无界阻塞队列
SynchronousQueue
单个元素的队列
LinkedTransferQueue
链表组成的无界阻塞队列
LinkedBlockingDeque
链表组成的双向队列
10.3 核心方法 十一、线程池一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过度调度。11.1 线程池的框架
Java中的线程池时通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类
11.2 线程池的使用方式 newFixedThreadPool特征:
- 一池n线程
- 线程可以重复被使用,在显示关闭之前,都将一直存在
- 超出一定量的线程被提交时候需要在队列中等待
- 一池一线程
- 线程池根据需求创建,可扩容
使用演示:
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
ExecutorService threadPool3 = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
threadPool1.execute(()->{
System.out.println(Thread.currentThread().getName() + "办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool1.shutdown();
}
}
}
11.3 线程池参数讲解
- int corePoolSize:常驻线程数量
- int maximumPoolSize:最大线程数量
- long keepAliveTime:线程存活时间,(超出常驻线程数量的线程,存活的时间)
- TimeUnit unit:时间单位
- BlockingQueue< Runnnable> workQueue:阻塞队列
- ThreadFactory threadFactory:线程工程
- RejectedExecutionHandler handler:拒绝策略
- CallerRunsPolicy:谁提交的交给谁执行
- AbortPolicy:抛异常
- DiscardPolicy:直接丢弃
- DiscardOldestPolicy:丢弃队列中时间最长的
实际使用中一般都是自定义线程池:
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 2L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
十二、Fork/Join框架
12.1 Fork/Join框架简介
Fork/Join可以将一个大的任务拆分成许多小任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。
- fork进行拆分
- join进行合并
计算a加到b的值,每10个进行计算
class MyTask extends RecursiveTask十三、CompletableFuture{ private static final Integer VALUE = 10; private int begin; private int end; private int result; public MyTask(int begin,int end) { this.begin = begin; this.end = end; } @Override protected Integer compute() { if((end - begin) <= VALUE) { for (int i = begin; i <= end; i++) { result = result + i; } } else { int middle = (begin + end)/2; MyTask myTask1 = new MyTask(begin,middle); MyTask myTask2 = new MyTask(middle + 1,end); myTask1.fork(); myTask2.fork(); result = myTask1.join() + myTask2.join(); } return result; } } public class ForkJoinTest { public static void main(String[] args) throws ExecutionException, InterruptedException { MyTask myTask = new MyTask(0,100); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTask forkJoinTask = forkJoinPool.submit(myTask); Integer res = forkJoinTask.get(); System.out.println(res); forkJoinPool.shutdown(); } }
CompletableFuture异步回调的使用
public class Asyn {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值
CompletableFuture completableFuture = CompletableFuture.runAsync(()->{
System.out.println("hi");
});
completableFuture.get();
//有返回值
CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(()->{
System.out.println("hi");
return 43;
});
Integer integer = completableFuture1.whenComplete((t, u) -> {
System.out.println(t + " 返回值");
System.out.println(u + " 异常");
}).get();
System.out.println("返回值"+integer);
}
}



