什么是CPU
-
中央处理器
主要由运算器、控制器、寄存器三部分组成
# 运算器:进行逻辑运算
# 控制器:负责发出CPU指令
# 寄存器:保存运算或者指令的一些临时文件
-
线程运行在CPU上
# 计算机在执行程序时,会为程序创建相应的进程,进行资源分配时,是以进程为单位进行相应的分配。每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。
进程
-
进程是资源分配的最小单位
# cpu从硬盘中读取一段程序到内存中,还执行程序的实例就叫做进程
# 一个程序如果被cpu多次读取到内存中,则变成多个独立的进程
线程
-
程序执行的最小单位
# 在一个进程中可以有多个不同的线程同时执行
为什么要在进程中还需要线程
-
同一个进程中,更好的进行并行操作
为什么需要多线程
-
目的就是提高程序开发的效率
串行和并行
-
多线程代码进行并行,同时执行
多线程就会提现效率吗
-
需要了解CPU的调度算法
-
线程是运行在CPU上,如果单核CPU,CPU会调度切换运行多个线程,非实际意义的多线程
CPU时间片
-
CPU执行一个线程是会有一个时间范围的,时间到了会进行重新调用
程序计算器
-
CPU调度时间片内保存当时执行停止前的位置,下次被调度时重新从记录位置开始
应用场景
-
异步短信,异步邮件
-
多线程异步执行耗时代码
-
异步写入日志
-
多线程下载
同步与异步
-
同步:代码从上而下执行
-
异步:单独分支执行,代码之间互不影响
创建多线程的方式
继承Thread类创建线程
package com.cyz.test;
/**
* @Author lian-chen
* @Date 2021/7/24 8:33
*/
public class Test01 extends Thread {
main主线程
主线程执行完毕
子线程开始执行
Thread-0我是子线程
阻塞等待
子线程开始执行
Thread-1我是子线程
阻塞等待
子线程执行完毕
子线程执行完毕
实现Runnable接口创建线程
使用匿名内部类的形式创建线程
使用lambda表达式创建线程
package com.cyz.test;
/**
* @Author lian-chen
* @Date 2021/7/27 8:37
*/
public class Test02 implements Runnable {
线程指定名称我是子线程
Thread-0匿名创建子线程
Thread-1lamdba创建子线程
使用Callable和Future创建线程
-
可以有返回值
-
底层基于LockSupport
-
Callable是Runnable的增强版,提供了一个call方法
-
call方法可以有返回值,可以声明异常
package com.cyz.test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.LockSupport;
/**
* @Author lian-chen
* @Date 2021/7/27 8:42
*/
public class Test03 implements Callable<Integer> {
Thread-0子线程开始执行
main,1
使用线程池 例如用Executor框架
-
复用机制,统一管理
package com.cyz.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author lian-chen
* @Date 2021/7/27 8:57
*/
public class Test04 {
public static void main(String[] args) {
/**
* public static ExecutorService newCachedThreadPool() {
* return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
* 60L, TimeUnit.SECONDS,
* new SynchronousQueue<Runnable>());
* }
*/
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
spring的@Async异步注解结合线程池
开启异步支持
package com.cyz;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* @Author lian-chen
* @Date 2021/7/22 8:45
*/
异步执行组件
package com.cyz.async;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
* @Author lian-chen
* @Date 2021/7/28 8:29
*/
业务方法
package com.cyz.service;
/**
* @Author lian-chen
* @Date 2021/7/28 8:33
*/
public interface TestService {
void getAsync();
}
package com.cyz.service.impl;
import com.cyz.async.AsyncManage;
import com.cyz.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author lian-chen
* @Date 2021/7/28 8:33
*/
接口实现
package com.cyz.controller;
import com.cyz.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author lian-chen
* @Date 2021/7/22 8:44
*/
开始执行
结束执行
异步执行
手写@Async注解
-
aop拦截只要在我们的方法上有使用到我们自己定义的注解,就单独开启一个异步线程去执行我们的目标方法
依赖
启动类
package com.cyz;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* @Author lian-chen
* @Date 2021/7/22 8:45
*/
自定义注解
package com.cyz.annocation;
import java.lang.annotation.*;
/**
* @Author lian-chen
* @Date 2021/7/28 8:45
*/
aop实现
package com.cyz.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* @Author lian-chen
* @Date 2021/7/28 8:44
*/
异步方法组件
package com.cyz.async;
import com.cyz.annocation.LianChenAsync;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @Author lian-chen
* @Date 2021/7/28 8:29
*/
业务方法
package com.cyz.service;
/**
* @Author lian-chen
* @Date 2021/7/28 8:33
*/
public interface TestService {
void getAsync();
}
package com.cyz.service.impl;
import com.cyz.async.AsyncManage;
import com.cyz.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Author lian-chen
* @Date 2021/7/28 8:33
*/
接口
package com.cyz.controller;
import com.cyz.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author lian-chen
* @Date 2021/7/22 8:44
*/
开始执行
2021-07-28 08:56:24.890 INFO 9640 --- [nio-8080-exec-1] com.cyz.aop.ExtThreadAsyncAop : 环绕执行=====
结束执行
2021-07-28 08:56:24.933 INFO 9640 --- [ Thread-17] com.cyz.async.AsyncManage : 目标异步方法执行====
异步执行
什么是线程安全问题
-
多个线程对统一个全局变量进行写操作,可能会受到其他线程的干扰,就会发生线程安全问题
package com.cyz.test;
/**
* @Author lian-chen
* @Date 2021/7/29 8:31
*/
public class ThreadCount implements Runnable{
private int count =100;
/**
* 如何保证线程一直处于运行状态 死循环控制
*/
Thread-1:98
Thread-0:99
Thread-0:97
Thread-1:97
Thread-0:95
Thread-1:95
Thread-0:93
Thread-1:93
Thread-1:92
Thread-0:91
Thread-0:90
Thread-1:89
Thread-1:88
Thread-0:87
Thread-1:86
...
如何解决线程安全问题
-
核心思想:上锁,
-
在同一个jvm中,多个线程需要竞争锁的资源,最终只有一个线程会拿到锁,拿到锁就可以获取资源
-
代码从哪一块需要上锁:可能会发生线程安全性问题的代码上需要上锁
-
获取所的过程,没有获取成功会一直阻塞等待
-
锁: 重入锁,悲观锁,乐观锁,公平锁,非公平锁
-
加锁缺点:
-
可能会影响代码的执行效率
-
互斥锁(内置锁):synchronized
锁的升级过程
偏向锁:自始至终只有一个线程需要获取到该锁,没有其他线程竞争
当出现有其他线程进行竞争锁时,升级为轻量级锁
自选锁:锁力度小,竞争不激烈,持有时间短的情况下,降低线程切换成本
当线程竞争锁失败时,打算阻塞自己,
不直接阻塞自己,而是自旋(空等待)一会
自旋的同时重新竞争锁
如果自旋结束前获取到了锁,那么获取锁成功,否则,自旋结束后阻塞自己
不适用于线程多而处理器少或单核处理器
不适用于计算密集型任务,竞争时间长,竞争激烈,持有锁时间长的场景中
自适应自旋锁:JVM根据以往时候获取到锁的情况,自适应增加或减少自旋时间
轻量级锁:无实际竞争情况下,目标在不存在锁竞争的场景
如果存在锁竞争当不激烈,升级为自旋锁
如果自旋失败,升级为重量级锁
重量级锁:有实际竞争,且锁竞争时间长
乐观锁和悲观锁
乐观锁:事情总是往好处想
读数据不会上锁
更新数据是,会判断在此期间是否有其他人对当前数据进行变更过(使用版本号或CAS算法)
适用于:读多写少
悲观锁:事情总是不好的
不管什么都要先上锁
适用于:读少写多
CAS算法:无锁算法,保证线程同步 非阻塞同步
需要读写的内存值V
进行比较的值A
拟写入的新值B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试
公平锁和非公平锁
公平锁:加锁之前先检查是否有排队等待的线程,有的话优先处理排前面的线程,先来先得
非公平锁:线程加锁时直接尝试获取,获取不到就到队尾等待
重入锁
重入锁:一个线程获取到锁后,可以进行加锁多次,和解锁多次,而其他线程不可
synchronized 和 ReentrantLock 都是可重入锁
意义在于防止死锁、
实现原理:记录计数器+占用线程
当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1
如果同一个线程再次请求这个锁,计数将递增
每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放
ublic static void main(String[] args){
Lock lock=new ReentrantLock();
lock.lock();
lock.lock();
lock.unlock();
lock.unlock();
}
线程同步
-
就是如何保证线程安全问题
-
使用synchronized锁,JDK1.6开始 锁的升级过程: 偏向锁,轻量级锁,重量锁
-
使用Lock锁(JUC),需要自己实现锁的升级过程,底层是基于aqs+cas实现的
-
使用Threadlocal,需要注意内存泄露的问题
-
原子类CAS非阻塞式
AQS
-
AbstractQueuedSynchronizer
-
抽象的队列式的同步器
-
AQS定义了一套多线程访问共享资源的同步器框架
volatile
-
对变量进行加锁,是变量在主存中进行计算,之后释放锁,使变量不会出现运算失误状态
原子性
-
要么都成功,要么都失败
可见性
-
在多线程环境下,如果一个线程修改了,其他线程能立即读到。这是因为他们读取的时候不会先把变量读进自己的缓存,直接在内存读取
缓存一致性协议
-
线程中处理器一直在总线上进行查看进行探测情况,一旦发现其他处理器要修改这个内存地址的值,那么,就让自己的高速缓存区变为无效状态,从内存进行读取
有序性
-
禁止JVM指令重排
对于代码
int a= 1;
int b =2;
虚拟机会对指令进行重排序(会有线程安全问题),重排序会考虑数据的依赖性
所以我们可以使用volatile保证有序
synchronized
-
jvm虚拟机底层已经实现自动化获取和释放
package com.cyz.test;
/**
* @Author lian-chen
* @Date 2021/7/29 8:31
*/
public class ThreadCount implements Runnable{
private int count =100;
/**
* 如何保证线程一直处于运行状态 死循环控制
*/
/**
* 为什么不直接锁run方法
* 我们的run方法内死循环:或一直获取锁二不释放锁,那么线程2获取不到锁将一直阻塞等待
*/
Thread-1:99
Thread-0:98
Thread-1:97
Thread-0:96
Thread-1:95
Thread-0:94
Thread-1:93
Thread-0:92
Thread-1:91
Thread-0:90
Thread-0:89
Thread-1:88
Thread-0:87
基本用法
修饰代码块
-
指定加锁对象,对给定对象加锁,进入同步代码块前要获取给定对象的锁
-
加锁对象必须为同一个
ThreadCount threadCount = new ThreadCount();
ThreadCount threadCount2 = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount2);
thread1.start();
thread2.start();
//两个this锁不住
package com.cyz.test;
/**
* @Author lian-chen
* @Date 2021/7/29 8:31
*/
public class ThreadCount implements Runnable{
private int count =100;
/**
* 自定义同步对象
*/
private Object objectLock = new Object();
/**
* 如何保证线程一直处于运行状态 死循环控制
*/
/**
* 为什么不直接锁run方法
* 我们的run方法内死循环:或一直获取锁二不释放锁,那么线程2获取不到锁将一直阻塞等待
*/
@Override
public void run() {
while (true){
cal();
}
}
public void cal(){
if (count>1){
/**
* 就绪---> 运行 ---> 休眠 ---> 就绪 ---> 运行
*
*/
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectLock){
count--;
System.out.println(Thread.currentThread().getName()+":"+count);
}
}
}
public static void main(String[] args) {
ThreadCount threadCount = new ThreadCount();
Thread thread1 = new Thread(threadCount);
Thread thread2 = new Thread(threadCount);
thread1.start();
thread2.start();
}
}
修饰实例方法
-
作用于当前实例加锁,进入同步代码前需要得到当前实例的锁
package com.cyz.test;
/**
* @Author lian-chen
* @Date 2021/7/29 8:31
*/
public class ThreadCount implements Runnable {
private int count = 100;
/**
* 如何保证线程一直处于运行状态 死循环控制
*/
/**
* 为什么不直接锁run方法
* 我们的run方法内死循环:或一直获取锁二不释放锁,那么线程2获取不到锁将一直阻塞等待
*/
Thread-0:99
Thread-1:98
Thread-0:97
Thread-1:96
Thread-0:95
Thread-1:94
Thread-0:93
Thread-1:92
Thread-0:91
Thread-1:90
Thread-0:89
修饰静态方法
-
作用于当前类对象(当前类.class)加锁,进入同步代码前需要获得当前类对象的锁
package com.cyz.test;
/**
* @Author lian-chen
* @Date 2021/7/29 8:31
*/
public class ThreadCount implements Runnable {
private static int count = 100;
/**
* 如何保证线程一直处于运行状态 死循环控制
*/
/**
* 为什么不直接锁run方法
* 我们的run方法内死循环:或一直获取锁二不释放锁,那么线程2获取不到锁将一直阻塞等待
*/
Thread-1:99
Thread-0:98
Thread-1:97
Thread-0:96
Thread-0:95
Thread-1:94
Thread-0:93
Thread-1:92
Thread-1:91
Thread-0:90
Thread-0:89
Thread-1:88
Thread-1:87
死锁问题
-
锁嵌套问题
package com.cyz.test;
/**
* @Author lian-chen
* @Date 2021/7/29 8:31
*/
public class DeedLockThread implements Runnable {
private int count = 1;
private String lock = "lock";
@Override
public void run() {
while (true){
count++;
if (count%2==0){
synchronized (lock){
a();
}
}else{
synchronized (this){
b();
}
}
}
}
public synchronized void a(){
System.out.println(Thread.currentThread().getName()+",a方法。。。");
}
public void b(){
synchronized (lock){
System.out.println(Thread.currentThread().getName()+",b方法。。。");
}
}
public static void main(String[] args) {
DeedLockThread deedLockThread = new DeedLockThread();
Thread thread1 = new Thread(deedLockThread);
Thread thread2 = new Thread(deedLockThread);
thread1.start();
thread2.start();
}
}
线程1 先获取到自定义对象的lock锁,进入到a方法需要获取this锁
线程2 先获取this锁,进入b方法需要获取自定义对象的lock锁
线程1和线程2是在同时执行
线程1 获取到了lock锁,线程2获取到了this锁
下一步线程1需要this锁,线程2需要lock锁
此时双方都持有对方所需要的锁,但还没有释放
所以两个线程都进入到阻塞等待,就形成了死锁
-
死锁检测工具
-
jconsole.exe
-
wait、notify
-
wait
package com.cyz.test.sy;
/**
* @Author lian-chen
* @Date 2021/8/2 8:28
*/
public class Test01 {
private Object objectLock = new Object();
public static void main(String[] args) throws InterruptedException {
new Test01().print();
}
public void print() throws InterruptedException {
/**
* this.wait(); 释放锁资源 同时当前线程会阻塞
* this.wait();,notify 的使用结合锁来使用 sunchronized
*/
synchronized(objectLock){
System.out.println("1===>");
/**
* 获取到锁的对象
*/
objectLock.wait();
System.out.println("2===>");
}
}
}
-
notify
package com.cyz.test.sy;
/**
* @Author lian-chen
* @Date 2021/8/2 8:28
*/
public class Test01 {
private Object objectLock = new Object();
public static void main(String[] args) throws InterruptedException {
new Test01().print();
}
public void print() throws InterruptedException {
new Thread(new Runnable() {
生产者和消费者模型
package com.cyz.test.sy;
/**
* @Author lian-chen
* @Date 2021/8/2 8:41
*/
public class Thread04 {
/**
* 共享对象
*/
class Res {
public String userName;
public char sex;
/**
* flag 为false时 标志res还未生成 需要输入线程输入 输出线程等待输出
* flag 为true时 标志res已经生成 输入线程通知输出线程可以输出 输出线程开始输出
*/
public boolean flag = false;
}
/**
* 输入线程
*/
class InputThread extends Thread {
private Res res;
public InputThread(Res res) {
this.res = res;
}