- 线程安全的理解?
线程安全说的是,当多个线程并发访问互斥资源时,读写互斥资源的代码逻辑能正常处理,获得正确结果,不会互相干扰的情况。
- 守护线程的理解?
守护线程是与普通线程相区分的概念,用户一般使用的就是普通线程,普通线程有自身独立的生命周期,而守护线程的生命周期取决于普通线程,只有当所有相关的普通线程都结束时,守护线程才会结束。
守护线程是为所有普通线程提供服务的线程
- threadlocal的底层原理
threadlocal:在线程里面多保存了一份map结构,由于变量存在线程内部,从而保证不同线程互相不干扰。
现在大多数用的都是线程池,由于线程都是复用的,而threadlocal往线程里面添加了一个map,因此,需要代码里面手动回收移除threadlocal保存的数据,否则,线程一直复用,数据量会一直累加至OOM。
- threadlocal的应用场景
threadlocal是基于副本的隔离机制,保证了共享变量的修改安全
1.线程上下文传递:在不修改方法签名的情况下,将threadlocal设置为成员变量,既能全类通用,又能线程安全,还不需要修改方法签名
2.数据库的连接管理:同一段处理的多线程并发时,由于数据库连接用的是threadlocal,因此,数据库连接可以互相不影响[否则成员变量的话,同一段处理会形成共用,非成员变量的话,又无法形成线程池的复用]
3.事务管理,事务上下文通过这个隔离互相不影响
- 并发、并行与串行之间的区别?
并行:多条处理流同时执行,例如多端口同时工作,多条线路同时传送数据
串行:多条处理流排队执行。
并发:例如微机系统上有多个进程存活,从宏观上,多个进程上同时工作,是个并行的流程,但是,从微观底层上来看,多个进程是由CPU通过时间片轮转,逐个调度执行的,是个串行的流程,这个就是并发。
- Java死锁应如何避免?-√
Java死锁由四个因素导致:
- 资源是互斥资源 每次仅可由单个线程持有
- 在获得所有资源之前,线程自身不释放已持有资源
- 所需资源在其他线程上,且不可被剥夺
- 循环依赖,A持有B所需资源,B持有A所需资源
应当打破第四点因素,即可避免死锁发生。
即将所有线程持有资源的顺序固定,避免产生循环依赖
每个锁的持有时间应当设置过期时间,确保锁最终一定会被释放
- 线程池底层工作原理-√
num:线程数量
当有任务需求时,
当num<poolSize时,优先创建线程
当num>poolSize时,且queue没满时,将任务添加到队列中排队
当maxpoolsize>num>poolSize时,且queue已满时,增加线程处理任务
当maxpoolsize==num,且queue已满时,不再新增线程,会按照handler所指定策略处理新任务。
同时会检查,是否有线程的空闲时间大于keepalivetime,如果有,终止对应线程
- 线程池中阻塞队列的作用?线程池为何先添加队列而不是先创建线程-√
阻塞队列可以通过阻塞保留那些想要入队的任务;阻塞队列自带阻塞和唤醒功能,不需要一直占有CPU资源。
因为创建新线程需要获取全局锁,影响其他线程的效率,且线程数量不足有可能是临时性不足,因此,优先排队等待。
- 线程池如果阻塞队列都满了,需要新开线程,执行的是新任务还是队头任务?
阻塞队列中的队头任务由核心线程调度
新开线程会处理阻塞队列之外的新任务
- ReentrantLock中的公平锁与非公平锁的底层实现
加锁[竞争锁]:
公平锁:新线程直接进入到队列
非公平锁:先直接竞争锁,竞争不到再进入队列
唤醒:无论公平锁还是非公平锁都会直接唤醒队列第一个获取锁
- ReentrantLock的tryLock()与lock()区别
tryLock()可能加锁成功[返回真],也可能加锁不成功[返回假],非阻塞加锁
lock()阻塞性加锁,一直执行,直到加锁成功
- CountDownLatch与Semphore的区别和底层原理
CountDownLatch共两个核心方法:await() 以及 countDown()
Semphore 也是两个核心方法:acquire() 以及 release()
CountDownLatch 更强调依赖关系:只有前置函数调用了countDown()以后,将信号量置0以后,await线程才会执行
Semphore 更强调共享资源数量:例如只有三台打印机,那么就只会有三个线程能够acquire成功,否则就只能排队等待[保存中间库那个功能 感觉用这个效果更好 逻辑更清晰]
- sychronized的偏向锁、轻量锁以及重量锁
sychronized语法,某个线程持有锁的时候,锁就已经是偏向锁
当某个线程持有锁,且有其他线程也在等待锁释放,锁就升级为轻量锁
某个线程持有锁,其他线程会自旋尝试获取锁,自旋次数达到一定程度,线程仍然没有获取到锁时,轻量锁就升级为重量锁
- ReentrantLock与synchronized的区别?-√
含义:ReentrantLock是一个类;synchronized是一个关键字
是否公平:ReentrantLock可以加公平锁,也可以加非公平锁;synchronized只能是非公平锁。
synchronized自动加锁释放锁,ReentrantLock需要手动加锁释放锁
synchronized是JVM层面的锁,ReentrantLock是API层面的锁
- 什么是可重入锁
可重入锁,即线程抢到锁以后,再去竞争同一把锁的时候,不需要等待,仅需要记录重入次数
可重入锁主要是避免线程死锁的问题,自己去等待自己已经抢到的锁,这是不合理的。
- 对AQS的理解,AQS如何实现可重入锁-※
AQS是一个用于JAVA线程同步的框架。
AQS内部维护了一个状态变量state和一个线程双向队列。
在可重入锁的场景下,state表示加锁次数,当state减少至0时,代表持有锁的线程释放锁了,就唤醒下一个线程竞争锁。
- Java中的CAS机制
CAS是Java中Unsafe类中的方法,全称是compareAndSwap,用于绑定两个操作原子性操作[判断是否为期望值,如果是期望值就更新目标值]
使用场景:AtomicInteger 该类就包含了CAS的实现,可用于共享资源的修改
- 一个线程调用两次start()
线程按5态模型而言,共启动、排队[就绪]、冻结、运行、结束,一般情况下,单个线程仅能启动一次,因为仅有未启动线程可以启动,运行、排队与冻结线程不可重复启动。
- 线程创建的三种方式
公共代码:
@Component
public class ShareLock {
public int lockNum = 0;
public int lockCircleNum = 3;
}
public class Count{//三种方式都要修改类签名 但是方法内容不变
private final int printNum = 3;
private ShareLock item = null;
private int state = 100;
private char content = 100;
public Count(ShareLock itemInput,int stateInput,char contentInput) {
this.item = itemInput;
this.state = stateInput;
this.content = contentInput;
}
public void run(){
for(int i=0,countNum=0;countNum<this.printNum;i++){
if(item.lockNum==state){
synchronized (this){
System.out.println(content);
item.lockNum = (item.lockNum+1+item.lockCircleNum)%item.lockCircleNum;
}
countNum+=1;
}
}
}
}
- 继承Thread类
public class Count ---> public class Count extends Thread
@Component
public class DebugMain {
@Autowired
public ShareLock shareLock;
public void debug() {
Count countA = new Count(this.shareLock,0,'A');
Count countB = new Count(this.shareLock,1,'B');
Count countC = new Count(this.shareLock,2,'C');
countA.start();
countB.start();
countC.start();
}
}
- 实现Runnable接口
public class Count ---> public class Count implements Runnable
@Component
public class DebugMain {
@Autowired
public ShareLock shareLock;
public void debug() {
Count countA = new Count(this.shareLock,0,'A');
Count countB = new Count(this.shareLock,1,'B');
Count countC = new Count(this.shareLock,2,'C');
new Thread(countA).start();
new Thread(countB).start();
new Thread(countC).start();
}
}
- 实现Callable和Future创建线程
public class Count implements Callable<String> {
private final int printNum = 3;
private ShareLock item = null;
private int state = 100;
private char content = 100;
public Count(ShareLock itemInput,int stateInput,char contentInput) {
this.item = itemInput;
this.state = stateInput;
this.content = contentInput;
}
public String call(){
for(int i=0,countNum=0;countNum<this.printNum;i++){
if(item.lockNum==state){
synchronized (this){
// System.out.println(content);
item.lockNum = (item.lockNum+1+item.lockCircleNum)%item.lockCircleNum;
}
countNum+=1;
}
}
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return Thread.currentThread().getName()+">>>>>"+this.content+"<<<<<<"+sdf.format(System.currentTimeMillis());
}
}
@Component
public class DebugMain {
@Autowired
public ShareLock shareLock;
public void debug() throws ExecutionException, InterruptedException {
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Count countA = new Count(this.shareLock,0,'A');
Count countB = new Count(this.shareLock,1,'B');
Count countC = new Count(this.shareLock,2,'C');
System.out.println(Thread.currentThread().getName()+">>>>>11111111<<<<<<"+sdf.format(System.currentTimeMillis()));
FutureTask<String> stringFutureTaskA = new FutureTask<>(countA);
FutureTask<String> stringFutureTaskB = new FutureTask<>(countB);
FutureTask<String> stringFutureTaskC = new FutureTask<>(countC);
new Thread(stringFutureTaskA).start();
System.out.println(Thread.currentThread().getName()+">>>>>222222222<<<<<<"+sdf.format(System.currentTimeMillis()));
new Thread(stringFutureTaskB).start();
System.out.println(Thread.currentThread().getName()+">>>>>33333333<<<<<<"+sdf.format(System.currentTimeMillis()));
new Thread(stringFutureTaskC).start();
System.out.println(Thread.currentThread().getName()+">>>>>44444444<<<<<<"+sdf.format(System.currentTimeMillis()));
System.out.println(stringFutureTaskA.get());
System.out.println(Thread.currentThread().getName()+">>>>>5555555555<<<<<<"+sdf.format(System.currentTimeMillis()));
System.out.println(stringFutureTaskB.get());
System.out.println(Thread.currentThread().getName()+">>>>>66666666<<<<<<"+sdf.format(System.currentTimeMillis()));
System.out.println(stringFutureTaskC.get());
System.out.println(Thread.currentThread().getName()+">>>>>77777777<<<<<<"+sdf.format(System.currentTimeMillis()));
}
}
由于callable要验证回调,因此,代码增加了返回值以及外层打印与内层打印,打印结果如下:[对调用方而言,最终结果打印的地方依然是串行的,因此,如果正式使用时,可能更多情况下,还需要在Runnable实现接口,run里面去调用这种callable才能保证不影响原来的主进程]
http-nio-8383-exec-1>>>>>11111111<<<<<<2023-07-12 19:36:27
http-nio-8383-exec-1>>>>>222222222<<<<<<2023-07-12 19:36:27
http-nio-8383-exec-1>>>>>33333333<<<<<<2023-07-12 19:36:27
http-nio-8383-exec-1>>>>>44444444<<<<<<2023-07-12 19:36:27
Thread-13>>>>>A<<<<<<2023-07-12 19:36:28
http-nio-8383-exec-1>>>>>5555555555<<<<<<2023-07-12 19:36:28
Thread-14>>>>>B<<<<<<2023-07-12 19:36:28
http-nio-8383-exec-1>>>>>66666666<<<<<<2023-07-12 19:36:28
Thread-15>>>>>C<<<<<<2023-07-12 19:36:28
http-nio-8383-exec-1>>>>>77777777<<<<<<2023-07-12 19:36:28
- Thread和Runnable的区别?(3)
1.定义: runnable是接口 thread是类,这个类也是实现了该接口的一个扩展而已
2.用法:继承thread以后可直接启动;实现接口以后还是需要new一个thread才能启动
3.限制:类只能单继承,接口可以多实现,如果已有继承关系,就只能实现接口
- 1 W 条数据 分10个线程 分别录入数据库 如何保证 10个线程组成的这个任务的事务性?
10个线程都写入临时表
每个线程都通过标记来记录成功或失败,10个标记写入Redis[setnx]或者单机上的共享变量[CAS机制]
总体上用countdownlatch 当10个依赖都搞定的时候 才会执行第11个 第11个去判断前10个标记
10个标记都OK 利用一条SQL插入最终的表 或者 记录表增加这个临时表
有线程失败了 直接返回失败 并删除临时表