目录
synchronized 和 ReentrantLock 的区别
AQS队列、等待队列(wait、await)、阻塞队列(BlockQueue)区别
synchronized 关键字和 volatile 关键字的区别
创建多线程方法
1.继承Thread类,重写run方法
2.实现Runable接口,实现run方法;
3.通过Callable和Future创建线程
线程的生命周期
创建:当new了一个线程,并没有调用start之前,线程处于创建状态;
就绪:当调用了start之后,线程处于就绪状态,这是,线程调度程序还没有设置执行当前线程;
运行:线程调度程序执行到线程时,当前线程从就绪状态转成运行状态,开始执行run方法里边的代码;
阻塞:线程在运行的时候,被暂停执行(通常等待某项资源就绪后在执行,sleep、wait可以导致线程阻塞),这是该线程处于阻塞状态;
死亡:当一个线程执行完run方法里边的代码或调用了stop方法后,该线程结束运行
java中的线程池(ThreadPoolExecutor) ThreadPoolExecutor类继承AbstractExecutorService四个构造方法
线程池的参数
corePoolSize:线程池的大小。线程池创建之后不会立即去创建线程,而是等待线程的到来。当当前执行的线程数大于改值是,线程会加入到缓冲队列;
maximumPoolSize:线程池中创建的最大线程数;
keepAliveTime:空闲的线程多久时间后被销毁。默认情况下,改值在线程数大于corePoolSize时,对超出corePoolSize值得这些线程起作用
unit:TimeUnit枚举类型的值,代表keepAliveTime时间单位;
workQueue:阻塞队列,用来存储等待执行的任务,决定了线程池的排队策略
threadFactory:线程工厂,是用来创建线程的
handler:线程拒绝策略。当创建的线程超出maximumPoolSize,且缓冲队列已满,新任务会拒绝
线程池工作原理
1、线程在有任务的时候会创建核心的线程数corePoolSize
2、当线程满了(有任务但是线程被使用完)不会立即扩容,而是放到阻塞队列中,当阻塞队列满了之后才会继续创建线程。
3、如果队列满了,线程数达到最大线程数则会执行拒绝策略。
4、当线程数大于核心线程数事,超过KeepAliveTime(闲置时间),线程会被回收,最终会保持corePoolSize个线程
Jdk自带线程池的创建方法
- newSingleThreadExecutor()
- newFixedThreadPool():创建一个核心线程数跟最大线程数相同的线程池,线程池数量大小不变,如果有任务放入队列,等待空闲线程、
- newCachedThreadPool()
- newScheduledThreadPool()
https://blog.csdn.net/qq_40996741/article/details/109557885
阻塞队列有哪些
- ArrayBlockingQueue :由数组结构组成的有界阻塞队列
- LinkedBlockingQueue :由链表结构组成的有界阻塞队列
- PriorityBlockingQueue :支持优先级排序的无界阻塞队列
- DelayQueue:使用优先级队列实现的无界阻塞队列
- SynchronousQueue:不存储元素的阻塞队列
- LinkedTransferQueue:由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
https://blog.csdn.net/a_higher/article/details/113126601
解决多线程死锁
1.加锁顺序(线程按照一定的顺序加锁)
2.加锁时限(线程尝试获取锁时加上一定的时限,超时则放弃对该锁的请求,并释放自己占有的锁)
3.死锁检测(每当一个线程获得了锁,在线程和锁相关的数据结构【如:map、graph等】中将其记下。当一个线程请求锁失败时,这个线程可以遍历锁的关系图看看是否有死锁发生。)
https://blog.csdn.net/m0_57278011/article/details/128909703
Sleep()和wait()方法的区别
(1)sleep() 方法是 Thread 类中的方法,而 wait() 方法是 Object 类中的方法。
(2)sleep() 方法不会释放 lock,但是 wait() 方法会释放,而且会加入到等待队列中
(3)sleep() 方法不依赖于同步器 synchronized(),但是 wait() 方法 需要依赖 synchronized 关键字。
(4)线程调用 sleep() 之后不需要被唤醒,但是 wait() 方法需要被重新唤醒
synchronized 和 ReentrantLock 的区别
1.synchronized 可用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用在代码块上。
ReentrantLock 在使用之前需要先创建 ReentrantLock 对象,然后使用 lock 方法进行加锁,使用完之后再调用 unlock 方法释放锁
2.获取锁和释放锁方式不同,synchronized 会自动加锁和释放锁,当进入 synchronized 修饰的代码块之后会自动加锁,当离开 synchronized 的代码段之后会自动释放锁;ReentrantLock 需要手动加锁和释放锁,使用lock 方法进行加锁,使用 unlock 方法释放锁
3.synchronized 属于非公平锁,而 ReentrantLock 既可以是公平锁也可以是非公平锁。默认情况下 ReentrantLock 为非公平锁
4.响应中断不同,ReentrantLock 可以使用 lockInterruptibly 获取锁并响应中断指令,而 synchronized 不能响应中断,也就是如果发生了死锁,使用 synchronized 会一直等待下去,而使用 ReentrantLock 可以响应中断并释放锁,从而解决死锁的问题
5.底层实现不同,synchronized 是 JVM 层面通过监视器(Monitor)实现的,而 ReentrantLock 是通过 AQS(AbstractQueuedSynchronizer)程序级别的 API 实现
公平锁和非公平锁,锁升级过程
https://blog.csdn.net/vgfvgf/article/details/129628969
sql有那些锁
https://blog.csdn.net/ANobility/article/details/127822782
AQS队列
AQS是一个抽象类。定义了线程同步机制(是否要阻塞,是否要排队,是否要唤醒),是锁的核心过程实现(到底做了什么);
https://www.jianshu.com/p/fc04e84e3470
AQS队列、等待队列(wait、await)、阻塞队列(BlockQueue)区别
AQS队列:针对的是没有获取到同步锁的线程
等待队列:针对的是已经获取到同步锁但是没有满足继续往下条件的线程
阻塞队列(BlockQueue)在线程池中,没有空余线程来执行任务时,会将待执行任务放入阻塞队列,等待后续执行;
Synchronized锁升级
无锁-> 偏向锁 -> 轻量级锁 -> 重量级锁
synchronized 关键字和 volatile 关键字的区别
synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!
volatile 关键字是线程同步的轻量级实现,所以volatile 性能肯定比 synchronized 关键字要好。但是volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块。
volatile 关键字能保证数据可见性,但不能保证数据原子性。synchronized 关键字两者都能保证。
volatile 关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性
静态方法和非静态方法上加锁的区别
synchronized修饰一个static方法时,多线程下,获取的是类锁,作用范围是整个静态方法,作用的对象是这个类的所有对象
修饰一个非static方法时,实际上是对调用该方法的对象加锁,作用对象是调用该方法的对象;类锁和对象锁不同,他们之间不会产生互斥
并发编程的三个重要特性
原子性 : 一个的操作或者多次操作,要么所有的操作全部都得到执行并且不会收到任何因素的干扰而中断,要么所有的操作都执行,要么都不执行。synchronized 可以保证代码片段的原子性。
可见性 :当一个变量对共享变量进行了修改,那么另外的线程都是立即可以看到修改后的最新值。volatile 关键字可以保证共享变量的可见性。
有序性 :代码在执行的过程中的先后顺序,Java 在编译器以及运行期间的优化,代码的执行顺序未必就是编写代码时候的顺序。volatile 关键字可以禁止指令进行重排序优化。
乐观锁和悲观锁
出现并发的情况时,需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的否则会导致脏读、幻读和不可重复读,这时就用到了乐观锁悲观锁;
1,悲观锁:借助数据库锁机制,在修改数据之前先锁定,再修改;悲观锁:主要分为共享锁(读锁,简称 S 锁,多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改)和排他锁(写锁,简称 X 锁,不能与其他锁并存,如果一个事务获取了一个数据行的排 他锁,其他事务就不能再获取该行的其他锁,可对数据进行修和读取)
实现:传统的关系型数据库使用这种锁机制,比如行锁、表锁、读锁、写锁等,都是在操作之前先上锁;Java 里面的同步 synchronized 关键字的实现
2,乐观锁:乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量,是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,
实现:1.CAS 实现,2.版本号控制
线程之间的通信
通过 volatile 关键字 2.通过 Object类的 wait/notify 方法
3.通过 condition 的 await/signal 方法 4.通过 join 的方式
线程通信的四种方式_线程间通信_我是哎呀呀的博客-CSDN博客
多线程事务回滚
思想就是使用两个CountDownWatch实现子线程的二段提交
主线程将任务分发给子线程,然后 使用 boolean await = threadLatch.await(20,
TimeUnit.SECONDS); 阻塞主线程,等待所有子线程处理向数据库中插入的业务
使用 threadLatch.countDown(); 释放子线程锁定,同时使用 mainLatch.await(); 阻塞子线程,将程序的控制权交还给主线程
主线程检查子线程执行插入数据库的结果,若有非预期结果出现,主线程标记状态告知子线程回滚,然后使用 mainLatch.countDown(); 将程序控制权再次交给子线程,子线程检测回滚标志,判断是否回滚
子线程执行结束,主线程拼接处理结果,响应给请求方