本章内容:
1、什么是线程安全?
2、解决线程安全的几种方式?
3、加锁的机制
1、什么是线程安全?
多个线程访问某个类时,不管运行环境以何种调度方式或者这些线程如何交替执行,并且在主代码中不需要额外的同步或者协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。如http请求,处理类内部只使用局部变量就是线程安全的。比如做一个调用计数,每个线程都加一这种就是不安全的。但对于http请求来说,无状态的本身就是线程安全的。
2、解决线程安全的几种方式?
synchronized、Lock、volatile、Atomic*等几种方式
3、加锁的机制?
1)内置锁,相当于互斥锁
持有锁的线程运行时,其他线程阻塞直到持有锁的线程释放锁。
进入锁的代码块自动获得锁,退出代码块自动释放锁。
例:
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
String name = Thread.currentThread().getName();
synchronized (lock) {
System.out.println("thread : " + name + " start....");
while(true){
System.out.println(name + " dead loop");
}
}
}).start();
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println("thread : " + name + " start....");
synchronized (lock) {
while(true){
System.out.println(name + " dead loop");
}
}
}).start();
TimeUnit.SECONDS.sleep(Long.MAX_VALUE);
}
2)重入。内置锁是可重入的
当某个线程访问其他线程持有的锁时,发出请求的线程会阻塞。然而如果某个线程试图获取由它自己持有的锁,这个请求会成功。如以下示例,程序正常运行,说明内置锁是可重入的。
重入实现逻辑:对于每个锁对应一个Count和持有线程。当count==0这个锁没有被任何线程持有,当线程请求一个未被持有的锁,JVM记录锁的持有者并count=1.如果同一个线程再次获取这个锁则Count++ ,而当线程退出同步代码块则计数器会相应减少。当count =0 时锁释放。
如果是不可重入锁,这种逻辑会发生死锁
public class Reet {
public static void main(String[] args) {
FinalProcess finalProcess = new FinalProcess();
finalProcess.process();
}
}
interface IProcess{
void process();
}
class Process implements IProcess{
@Override
public synchronized void process() {
System.out.println("Process !");
}
}
class FinalProcess extends Process{
@Override
public synchronized void process() {
super.process();
System.out.println("FinalProcess !");
}
}
3)用锁来保护状态。
如果只是保证某个变量原子性Atomic*即可,但是对于多个变量(尤其是还有联动关系)时,需要我们保证一段代码串行化。这时需要锁,但是需要注意的是如果针对相同变量组合有多个代码块需要同步,则需要同一个锁来保护。
比如:
方法1:代表几个变量增加的逻辑
方法2:代表几个变量减少的逻辑。这两方法加的锁要是同一个
4)活跃性与性能
就是同步的代码块的区域大小。理想状态是尽量减小范围,做好线程安全和程序性能的兼顾