多线程笔记
1.定义
①程序:为了完成特定的任务,使用某种语言编写的一组指令的集合。一段静态的代码,静态对象;
②进程:一个正在运行的程序,动态的过程,存在开始、运行、消亡的生命周期。
③线程:线程是程序内部的一条执行路径,也就是说一个程序可以一条路径执行下来那么这个程序就是单线程的。核心cpu执行一个线程
协程:在jvm层面 不走操作系统,绑定线程
线程是调度和执行的单位,每个线程都有独立的运行栈和程序计数器。
一个进程可以包括好多个线程。
④ 内存区域:主要包括本地方法栈、虚拟机栈、程序计数器、方法区、堆;其中方法区和堆区域是一个线程一份,共享区域,虚拟机栈和程序计数器一个线程一份。
⑤单核cpu :一个核处理器,一种假的多线程,因为在一个时间单元内只能执行一个线程。例如一个窗口收费站,一个饭店厨师做菜,可以炖菜的同时做一道凉菜,端出去后让客人误以为有多个厨师在做菜。
⑥并行:多个cpu同时执行多个任务:不同的人做不同的事情。
⑦并发:一个cpu同时执行多个任务:秒杀手机,多个人同时秒杀一台手机。交替执行
一个java应用程序至少要包含三条线程:main主线程、垃圾回收、异常处理线程
2优点
1、提高应用程序的利用率。对于图形化界面更有意义,可增强用户体验。
2、提高计算机cpu的利用率。
3、改善程序结构。将一个复杂的进程分成多个独立运行、利于理解的线程。
3线程方法
1、继承Thread类
步骤:①新建一个类继承Thread类,重写run方法
②创建该类对象
③调用start()方法
2、实现runnable接口
① 创建一个新的类实现runable接口
② 实现run方法
③ 创建实现接口的类对象
④ 将对象作为参数创建Tread 对象
⑤ Thread 对象调用start 方法
class ThreadRun implements Runnable{
@Override
public void run() {
for (int i = 0; i <50 ; i++) {
if(i%2 == 0){
System.out.println(Thread.currentThread().getName()+"偶数"+i);
}
}
}
}
public class ThreadJcRun {
public static void main(String[] args) {
ThreadRun t = new ThreadRun();
Thread thread = new Thread(t);
thread.setName("线程一");
thread.start();
Thread thread1 = new Thread(t);
thread1.setName("线程二");
thread1.start();
}
}
class ThreadChird extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if(i%2==0){
System.out.println(getName()+"**"+i);
}
}
}
}
public class ThreadJc {
public static void main(String[] args) {
ThreadChird t = new ThreadChird();
t.start();
for (int i = 0; i <100 ; i++) {
//主线程
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+"**"+i+"*******main********");
}
//释放cpu资源
if(i==5){
Thread.currentThread().yield();
}
if(i==20){
try {
//等待分线程执行完成之后,再此执行主线程
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
4常用方法
①start():启动线程;调用当前线程的run()方法
②run():重写Thread类中的此方法,将创建线程的执行操作声明在此方法中
③currentThread():静态方法,返回执行当前代码的线程
④getName():获取线程的名字
⑤setName():设置线程的名字
⑥yield():释放当前cpu的执行权(由操作系统cpu等决定)
⑦join():在线程a中调用线程b的join方法,此时线程a进入阻塞状态,等线程b完全执行后,线程a才会继续执行
多数用于线程a执行一半之后需要线程b的数据才能继续执行
⑧stop():已过时,当执行方法时,强制结束该线程。
⑨sleep():让当前线程进入睡眠状态,当前线程是阻塞状态。完成延迟任务但少(web服务器、tomcat、springboot内置web服务器 为了持续阻塞不关闭死循环)
⑩ isAlive():判断当前线程是否存活。
11、线程的优先级:
分为:MORM_PRIORITY:5默认优先级、MIN_PRIORITY:1最小优先级、MAX_PRIORITY:10最高优先级
getPriority()获取
说明:高优先级抢占低优先级线程cpu的执行权,但是未必强的过,概率上高优先级的先执行,但是并不是必须先执行。
interrupt() 线程中断,不会立即停止线程,只是打标上了中断标记
public static boolean interrupted() 撤销打断标记
public boolean isInterrupted() 不会撤销打断标记
在这里插入图片描述
睡眠时中断抛出异常会清除中断标记 所以需要再异常catch中再次中断
4两种创建方式的优点
开发中:选择实现runnable接口方式
原因:实现没有单继承限制
适合处理多个线程有共享数据的情况
相同点:都需要重写run方法,线程要执行的逻辑声明在run方法中
start()会调用本地方法栈 native 然后再调用target(runable) 再调用run
run()和start()方法区别
run():用于业务代码,同步方法,可以执行多次
start():创建新的线程,异步非阻塞,一个线程只能调用一次
线程的六种状态state
new:初始状态 线程被构建但没有调用start()方法
runnable :运行状态启动start方法
blocked :阻塞 synchorized 同步锁
waiting:等待状态
timedWaiting
terminated :终止状态
有返回值的线程
主线程不阻塞,当主线程调用future.get()方法时,会等待子线程执行完成拿到结果
子线程异步执行
callabel:只能再executorSerice的线程池中跑,返回结果可以通过返回的Future对象查询执行状态
Future :本身是一种设计模式,采用异步任务的结果
通过FutureTask包装Callable,Thread只接受Runnable参数
public class FutureTest implements Callable {
@Override
public Object call() throws Exception {
System.out.println("有返回值的线程执行中");
return 1;
}
}
FutureTask<Integer> futureTask = new FutureTask<Integer>(new FutureTest());
new Thread(futureTask).start();
System.out.println("线程返回的结果为"+futureTask.get());
FutureTask<Integer> future2 = new FutureTask<>(()->{
return 2;
});
new Thread(future2).start();
System.out.println("线程返回的结果为"+future2.get());
线程池
1.1线程池:容纳多个线程的容器
不用:
并发线程数量很多,并且每个线程执行一个短时间内的任务就结束,频繁创建销毁线程会降低系统的效率。
用的优势:
控制运行的线程数量,处理过程中将任务放入队列
降低资源消耗:重复利用已创建的线程降低线程创建和销毁造成的消耗
提高响应速度:任务可以不需要等待创建线程就能立即执行
提高线程的可管理:线程池可以进行统一的分配,调优和监控。
1.2线程池使用
ExecutorSerice
public class ThreadPoolTest {
public static void main(String[] args) {
class Task implements Runnable{
@Override
public void run() {
System.out.println("开始执行任务"+Thread.currentThread().getName());
}
}
Task task = new Task();
//ExecutorService executorService = Executors.newFixedThreadPool(2);//一个饭店两个张桌子
//ExecutorService single = Executors.newSingleThreadExecutor();//一个饭店一张桌子
//动态创建
//ExecutorService cache = Executors.newCachedThreadPool();
//定时闯将
ScheduledExecutorService schedule = Executors.newScheduledThreadPool(5);
try{
for (int i = 0; i <10 ; i++) {
//executorService.execute(task);
schedule.schedule(task,3, TimeUnit.MILLISECONDS);
schedule.awaitTermination(Long.MAX_VALUE,TimeUnit.MILLISECONDS);
}}
catch (Exception exception){
exception.printStackTrace();
}finally {
//不会立即关闭 会等所有任务执行完后进行关闭
schedule.shutdown();
//会等线程池中进行的任务执行完成后就关闭 不会等排队的任务
schedule.shutdownNow();
}
System.out.println(schedule.isTerminated());
//executorService.shutdown();
}
}
execute 和submit区别
1、参数 execute 请求参数Runnable
submit 请求参数callable、runnable
2、返回值
execute void 也可以执行带参数的 只不过要自己写FutureTask
submit Future
3、异常
execute 会在子线程中抛出异常,主线程捕捉不到
submit 不会立马抛出异常,会暂存等future.get()才会抛出,可以在主线程中捕捉到
public class ExeAndSub {
public static void main(String[] args) {
class Task implements Runnable{
@Override
public void run() {
System.out.println("开始执行任务"+Thread.currentThread().getName());
}
}
Task task = new Task();
ExecutorService executorService = Executors.newFixedThreadPool(3);
try{
for (int i = 0; i < 5; i++) {
executorService.execute(task);
int finalI = i;
Future<Integer> future = executorService.submit(()->{
return finalI;
});
System.out.println(future.get());
}
}catch (Exception e){
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
}
自定义线程池参数
corePoolSize:核心线程数量
maxnum最大线程数量
keepAliveTime存活时间:非核心线程空闲存活时间
unit:存活单位
workQueue:队列
threadFactory:线程工厂 (new Thread)
handler:拒绝策略(全满的情况)
abortpolicy 默认的拒绝策略 抛异常
callerRunsPolicy :线程池拒绝任务,调用当前线程执行该任务
discardOldestPolicy:淘汰队列里最前面的任务
discardPolicy:什么都不做
创建 核心判断后优先判断队列也是更好的保活核心线程
并没有标注哪一个是核心线程 最后剩下的就是核心线程
淘汰线程步骤:runWorker 不断的获取getTask()方法从队列里获取任务(poll() take())
源码:new ThreadPoolExecutor 此时是没有线程的 jdk
tomcat 有调用了一个提前启动核心线程方法。
线程安全 原子性 有序性 可见性
多线程并发同时对共享数据进行读写 就会对数据造成混乱
原子性:相应的操作是单一不可分割的
i++ 不是原子操作 1加载i 2 i+1 3 将值写入内存
synchronized、lock 原子类型(保障原子性)
可见性:当多个线程访问同一个变量时,一个线程修改了这个线程的值,其他线程能够立即看到修改的值。cpu缓存区每个线程给了一个共享变量副本
synchronized 解锁的时候将变量值同步到每个线程中
java 内存模型 jmm
有序性:cpu是否按照既定代码顺序执行依次代码指令。
编译器和cpu为了提高指令的执行率可能会进行指令重排
锁
类型 悲观锁 乐观锁
悲观锁 sync
synchorized
乐观锁 cas
cas compareAndSwap
atomic compareAndSet 底层 native
自旋锁 do while
wait 和sleep区别
sleep 所属类Thread 不依赖sync 有参数 不会释放lock还会占用资源 不需要唤醒
wait :所属类对象 Object 依赖sync 可以没有 会释放 需要notify
wait queue 简易版消费生产
代码:
package main.thread.basic;
import java.util.concurrent.ArrayBlockingQueue;
/**
* Created with IntelliJ IDEA.
*
* @Author: lht
* @Date: 2024/02/28/11:14
* @Description:
*/
public class Que {
//队列最大长度
private int queSize=10;
private ArrayBlockingQueue<Integer> queue =new ArrayBlockingQueue<>(10);
public static void main(String[] args) {
Que que = new Que();
Consume consume = que.new Consume();
consume.start();
//生产10条消息
for (int i = 0; i < 10; i++) {
Producter producter = que.new Producter();
producter.start();
}
}
class Consume extends Thread{
@Override
public void run(){
//消费者需要一直工作
while (true){
//消费者一直工作
synchronized (queue){
//如果队列为空 则wait
if(queue.isEmpty()){
try {
System.out.println("当前队列为空");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
//出现异常需要手动唤醒
queue.notify();
}
}else{
//消费 消费头部信息
Integer poll = queue.poll();
//模拟业务 睡一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者消费消息为 "+poll+"当前队列长度为"+
queue.size());
System.out.println("唤醒生产者");
queue.notify();
//生产者
}
}
}
}
}
//生产者
class Producter extends Thread{
@Override
public void run(){
synchronized (queue){
//判断当前队列长度是否小于最大长度
if(queSize>queue.size()){
//可以添加消息
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
queue.add(queue.size()+1);
System.out.println("生产者加入了消息,当前队列长度为"+queue.size());
//唤醒消费者
queue.notify();
}
if(queSize<queue.size()){
//进入等待
try {
System.out.println("当前队列已满,生产者进入等待状态,,当前队列长度为"+ queue.size());
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notify();
}
}
}
}
}
}
运行结果
sync锁升级
jdk 1.6之前 重量级锁 会由jvm用户态切换到操作系统由管程来实现互斥
管程 monitor (同步 互斥)
管理共享变量以及对共享变量操作的过程,让它们支持并发
无锁
偏向锁![
在这里插入图片描述](https://img-blog.csdnimg.cn/direct/26231ad51a0e4105aba97e3cfffa22e9.png)
会通过cas 将线程id放入到对象中 同一个线程调用不会升级该锁,就算上了多次锁也不会升级
jdk偏向锁存在延迟4秒启动 可以主线程睡4001毫秒
轻量级锁
当两个或以上线程交替获取锁,但并没有在对象上并发的获取锁时,偏向锁升级为轻量级锁。线程采用cas自旋的方式尝试获取锁,避免阻塞线程造成cpu在用户态和内核态间的消耗转换。
重量锁
两个或以上线程并发的在同一个对象上进行同步时,为了避免cpu的自旋消耗,轻量锁会升级为重量锁,此时mark word 中的指针指向管程(monitor)的起始地址
可重入锁 递归锁 不会自己锁自己
ReentrantLock
使用需要创建对象 更灵活更容易出错
锁粒度更细 适用一些动态解锁的场景
锁申请的等待限时 tryLock 尝试获取锁 并且可以设置时间
可以被中断lockInterruptibly
公平锁 非公平锁
Semaphore 信号量
Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信
号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore 可以用来
构建一些对象池,资源池之类的,比如数据库连接池
实现互斥锁(计数器为 1)
我们也可以创建计数为 1 的 Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,
表示两种互斥状态。
代码实现
它的用法如下:
// 创建一个计数阈值为 5 的信号量对象
// 只能 5 个线程同时访问
Semaphore semp = new Semaphore(5);
try { // 申请许可
semp.acquire();
try {
// 业务逻辑
13/04/2018 Page 69 of 283
} catch (Exception e) {
} finally {
// 释放许可
semp.release();
}
} catch (InterruptedException e) {
}
Semaphore 与 ReentrantLock
Semaphore 基本能完成 ReentrantLock 的所有工作,使用方法也与之类似,通过 acquire()与
release()方法来获得和释放临界资源。经实测,Semaphone.acquire()方法默认为可响应中断锁,
与 ReentrantLock.lockInterruptibly()作用效果一致,也就是说在等待临界资源的过程中可以被
Thread.interrupt()方法中断。
此外,Semaphore 也实现了可轮询的锁请求与定时锁的功能,除了方法名 tryAcquire 与 tryLock
不同,其使用方法与 ReentrantLock 几乎一致。Semaphore 也提供了公平与非公平锁的机制,也
可在构造函数中进行设定。
Semaphore 的锁释放操作也由手动进行,因此与 ReentrantLock 一样,为避免线程因抛出异常而
无法正常释放锁的情况发生,释放锁的操作也必须在 finally 代码块中完成。
AQS
AbstractQueuedSynchronizer 类如其名,抽象的队列式的同步器
juc包下 多线程同步器
ReentrantLock/Semaphore/CountDownLatch 都依赖aqs
它维护了一个 volatile int state(代表共享资源)和一个 FIFO 线程等待队列(多线程争用资源被
阻塞时会进入此队列)。
排它锁
共享锁
一、redisson 分布式锁
lock.lock
ddl 看门狗
锁的自动续期 如果业务超长 运行期间自动给锁续上新的30s
如果自己加时间锁,看门狗不会自动续期所以锁的时间一定要大于业务时间,不然自动删除后,下一个线程会拿到新的锁,当先线程业务执行完去删除就去删除了下个下一个线程的锁,会报错
只要占锁成功,就会启动定时任务,(重新给锁设置过期时间,新的过期时间就是看门狗默认时间)1/3 的看门狗时间 10s一续期。
最佳实战
设置时间 省掉续期时间30s
2、读写锁 成对出现
写锁排他锁 读锁共享锁
写没释放 读一直等待 没读完 写锁也会等待
@GetMapping("/read")
@ResponseBody
public String readValue() {
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
String s = "";
RLock rLock = readWriteLock.readLock();
rLock.lock();
try {
System.out.println("读锁加锁成功。。。。"+Thread.currentThread().getId());
s = stringRedisTemplate.opsForValue().get("writeValue");
Thread.sleep(30000);
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
System.out.println("读锁释放"+Thread.currentThread().getId());
}
return s;
}
@GetMapping("/write")
@ResponseBody
public String writeValue() {
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
String s = "";
RLock rLock = readWriteLock.writeLock();
try {
//1、改数据加写锁,读数据加读锁
rLock.lock();
System.out.println("写锁加锁成功。。。。"+Thread.currentThread().getId());
s = UUID.randomUUID().toString();
Thread.sleep(10000);
stringRedisTemplate.opsForValue().set("writeValue", s);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
System.out.println("写锁释放"+Thread.currentThread().getId());
}
return s;
}
3、信号量
停车 、流量肖锋
@GetMapping(“/park”)
@ResponseBody
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
boolean b = park.tryAcquire();
if (b) {
//执行业务
} else {
return "error";
}
return "ok" + b;
}
@GetMapping(“/go”)
@ResponseBody
public String go() throws InterruptedException {
RSemaphore park = redisson.getSemaphore(“park”);
park.release();
return “走了”;
}
4、看门狗
@GetMapping("/lockdoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await();
return "放假了";
}
@GetMapping("/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable int id){
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown();
return id+"号走了";
}
数据一致性 双写模式
失效模式
canal 插件