线程安全问题的主要诱因
- 存在共享数据(也称临界数据)
- 存在多条线程共同操作这些共享数据
解决问题的根本方法
同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后在对共享数据进行操作
互斥锁的特性
- 互斥性:即在同一时间只运行一个线程有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性也称为操作的原子性
- 可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在你获得锁时应获得最新恭喜那个变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致
synchronized锁的不是代码,锁的是对象
根据获取的锁的分类:获取对象锁和获取类锁
获取对象锁的两种方法
- 同步代码块(synchronized(this), synchronized(类
实例对象
)),锁时小括号()中的实例对象
- 同步非静态方法(synchronized method),锁是
当前对象的实例
public class SyncThread implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.startsWith("A")) {
async();
} else if (threadName.startsWith("B")) {
syncObjectBlock1();
} else if (threadName.startsWith("C")) {
syncObjectMethod1();
} else if (threadName.startsWith("D")) {
syncClassBlock1();
} else if (threadName.startsWith("E")) {
syncClassMethod1();
}
}
/**
* 异步方法
*/
private void async() {
try {
System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 方法中有 synchronized(this|object) {} 同步代码块
*/
private void syncObjectBlock1() {
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* synchronized 修饰非静态方法
*/
private synchronized void syncObjectMethod1() {
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void syncClassBlock1() {
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (SyncThread.class) {
try {
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private synchronized static void syncClassMethod1() {
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
Thread A_thread1 = new Thread(syncThread, "A_thread1");
Thread A_thread2 = new Thread(syncThread, "A_thread2");
Thread B_thread1 = new Thread(syncThread, "B_thread1");
Thread B_thread2 = new Thread(syncThread, "B_thread2");
Thread C_thread1 = new Thread(syncThread, "C_thread1");
Thread C_thread2 = new Thread(syncThread, "C_thread2");
A_thread1.start();
A_thread2.start();
B_thread1.start();
B_thread2.start();
C_thread1.start();
C_thread2.start();
}
C_thread1_SyncObjectMethod1: 10:38:14
B_thread2_SyncObjectBlock1: 10:38:14
B_thread1_SyncObjectBlock1: 10:38:14
C_thread1_SyncObjectMethod1_Start: 10:38:14
A_thread1_Async_Start: 10:38:14
A_thread2_Async_Start: 10:38:14
C_thread1_SyncObjectMethod1_End: 10:38:15
A_thread1_Async_End: 10:38:15
A_thread2_Async_End: 10:38:15
B_thread1_SyncObjectBlock1_Start: 10:38:15
B_thread1_SyncObjectBlock1_End: 10:38:16
B_thread2_SyncObjectBlock1_Start: 10:38:16
B_thread2_SyncObjectBlock1_End: 10:38:17
C_thread2_SyncObjectMethod1: 10:38:17
C_thread2_SyncObjectMethod1_Start: 10:38:17
C_thread2_SyncObjectMethod1_End: 10:38:18
Process finished with exit code 0
通过以上代码可以看到,A线程异步执行不受影响,B和C被上对象锁需要等待被上锁的线程执行完毕后,其他线程才竞争去获取对象锁然后执行。
当把new Thread(syncThread,..)改为new Thread(new syncThread(),..)
后会发现,由于不在是同一对象导致对象锁不一致,将不在同步。
获取类锁的两种方法:
- 同步代码块(synchronized(类.class)),锁时小括号()中的
类对象
(Class对象) - 同步非静态方法(synchronized static method),锁是当前的
类对象
(Class对象)
public static void main(String[] args) {
SyncThread syncThread = new SyncThread();
Thread A_thread1 = new Thread(syncThread, "A_thread1");
Thread A_thread2 = new Thread(syncThread, "A_thread2");
Thread D_thread1 = new Thread(syncThread, "D_thread1");
Thread D_thread2 = new Thread(syncThread, "D_thread2");
Thread E_thread1 = new Thread(syncThread, "E_thread1");
Thread E_thread2 = new Thread(syncThread, "E_thread2");
A_thread1.start();
A_thread2.start();
D_thread1.start();
D_thread2.start();
E_thread1.start();
E_thread2.start();
}
A_thread1_Async_Start: 10:52:24
D_thread1_SyncClassBlock1: 10:52:24
D_thread2_SyncClassBlock1: 10:52:24
E_thread1_SyncClassMethod1: 10:52:24
A_thread2_Async_Start: 10:52:24
E_thread1_SyncClassMethod1_Start: 10:52:24
A_thread1_Async_End: 10:52:25
A_thread2_Async_End: 10:52:25
E_thread1_SyncClassMethod1_End: 10:52:25
D_thread2_SyncClassBlock1_Start: 10:52:25
D_thread2_SyncClassBlock1_End: 10:52:26
D_thread1_SyncClassBlock1_Start: 10:52:26
D_thread1_SyncClassBlock1_End: 10:52:27
E_thread2_SyncClassMethod1: 10:52:27
E_thread2_SyncClassMethod1_Start: 10:52:27
E_thread2_SyncClassMethod1_End: 10:52:28
Process finished with exit code 0
这里类锁和上面的对象锁效果一致,但是当我们将new Thread(syncThread,..)改为new Thread(new syncThread(),..)
后,发现锁的限制依然有效。其实从名字都能看出对象锁的作用域是对象实例,而类锁的作用域是类
对象锁和类锁的总结:
- 有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块
- 若锁住的是同一个对象,一个线程在访问对象的
同步代码块
时,另一个访问对象的同步代码块的
线程会被阻塞 - 若锁住的是同一个对象,一个线程在访问对象的同步方法时,另一个访问对象的同步方法的线程会被阻塞
- 若锁住的是同一个对象,一个线程在访问对象的
同步代码块
时,另一个访问对象的同步方法
的线程会被阻塞,反之同理 - 同一个类的不同对象的对象锁互不干扰
- 类锁由于也是一种特殊的对象锁,因此表现和上诉的前4点一致,而由于一类只有一把对象锁,所以同一个类的不同对象使用类锁将会是同步的
- 类锁和对象锁互不干扰
synchronized底层实现原理
实现synchronized的基础
-
Java对象头
对象头结构
- Monitor
存在于对象头中,其具体实现为ObjectMonitor位于虚拟机底层源码:
ObjectMonitor
可以看到它里面有多线程那篇中介绍过的waitSet(等待池)和EntryList(锁池),它们就是用来保存ObjectMonitor对象列表。owner指向持有ObjectMonitor的线程。
当多个线程访问对象时,首先会进入到EntryList获取到锁,然后owen指向当前线程,count加1。调用wait()后,owner设置为null,count减1,当前对象进入到waitSet区
通过字节码文件来看看monitor的运行
java:
public class SyncBlockAndMethod {
public void syncsTask() {
//同步代码库
synchronized (this) {
System.out.println("Hello");
synchronized (this){
System.out.println("World");
}
}
}
public synchronized void syncTask() {
System.out.println("Hello Again");
}
}
同步代码块字节码文件
同步代码块分析synchronized (this):
通过字节码文件3-42行能够看出同步语句块的实现是有monitorenter开始到monitorexit结速,第一次在3行执行
synchronized (this)
调用monitorenter执行同步,然后4-6行调用PrintStream进行打印,在15行时再次执行synchronized (this)
调用monitorenter执行重入
,但是我们在后面看到多出了两个monitorexit,这是因为编译器会自动产生一个异常处理器防止在异常情况下monitor能正常退出
重入(补充)
从互斥锁的设计来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁
的临界资源时,这种情况属于重入
同步方法分析
同步方法并没有显示的持有monitor,而是通过ACC_SYNCHRONIZED标志位来隐式的持有monitor,进入到同步状态
锁的分类
偏向锁->轻量级锁->重量级锁(由小到大)
锁 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比存在纳秒级差距 | 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 | 只有一个线程访问同步块或者同步方法的场景 |
轻量级锁 | 竞争的线程不会阻塞,提高响应速度 | 若线程长时间抢不到锁,自旋会消耗CPU性能 | 线程交替执行同步块或者同步方法的场景 |
重量级锁 | 线程竞争不使用自旋,不会消耗CPU性能 | 线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗 | 追求吞吐量,同步块或者同步方法执行时间较长的场景 |
网友评论