Java中的synchronized关键字是一种用来控制多线程同时访问共享资源的机制,通过synchronized关键字的应用可以保证同一时刻只有一个线程执行某个方法或代码块。
synchronized的锁定对象可以是普通对象,但需要注意的是,synchronized作用在对象上时,不同的对象之间互不影响,一个对象的锁与另一个对象的锁是互相独立的。下面来详细讲解Java synchronized底层的实现原理。
1. synchronized 实现原理
synchronized 的实现原理主要通过对象头(Mark Word)的结构来完成。
在JVM中,对象在Heap区域中分配,它所具有的内存结构中,有一个叫做"对象头"(Mark Word)的区域,这个区域用2个字节(16bit)的存储空间来表示锁。
每个对象都有一个ID(lock ID)和自旋次数(spin times)。JVM中的线程在尝试获得锁的时候,会更新这个对象头的lock ID和自旋次数。当线程成功获得锁时,这个锁的ID会变为线程的ID,同时,自旋计数器也会被清零。当一个线程尝试获得锁失败时,就会进入等待状态。
在Java 6之前,synchronized锁只有一种形式,也就是重量级锁,这种同步锁在JVM内部使用操作系统线程互斥量(Mutex)来实现。在JVM内部,Java对象头中有一个锁标志位,它表示这个对象是被哪个线程锁定的,如果没有线程锁定它,就表示这个对象没有被锁定。
在Java 6之后,引入了锁升级和轻量级锁的机制,可以高效的控制锁的获取和解除,从而提高程序的并发性能。
2. synchronized 的锁优化
2.1 锁升级
在JVM中,锁除了重量级锁和轻量级锁之外,还有一种适应性自旋锁。这种锁会在未竞争时自旋获取锁,如果自旋的时间过长或者自旋的次数达到了一定的值,就会升级为重量级锁。
锁升级的优点在于可以高效的控制锁的获取和解除,从而提高程序的并发性能。
2.2 锁消除
在代码编写过程中,如果发现某些代码块不会被多个线程同时访问,就可以使用锁消除的机制,将锁消除掉,从而达到优化的效果。
3. 示例说明
3.1 示例一:synchronized 语句块
下面是一个使用synchronized关键字的简单示例代码,代码中使用了synchronized语句块,来保证对共享资源的操作是线程安全的。
public class SynchronizedExample {
private int count = 0;
private Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
return count;
}
}
在这个示例代码中,节点lock就是锁定的对象。当某一个线程想要访问increment()方法时,如果这个方法已经被另一个线程锁定了(也就是lock已经被锁定了),那么这个线程就会进入等待状态,直到锁被释放。
3.2 示例二:volatile 与 synchronized 的区别
下面是一个使用volatile关键字的示例代码,代码中使用了volatile关键字,来保证对共享资源的操作是线程安全的。
public class VolatileExample {
private volatile int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
在这个示例代码中,使用volatile关键字来修饰count变量,将其声明为volatile型变量。由于volatile变量的特性,保证了它的可见性和禁止指令重排序的效果,从而保证了多线程下的安全访问。
然而,上述代码仅仅保证了volatile变量的原子性,而没有保证整个increment()方法的原子性,也就是说,如果多个线程同时访问increment()方法,就有可能造成线程安全问题。
相比之下,使用synchronized关键字能够保证整个代码块的原子性,不会出现线程安全问题。但是,synchronized关键字会在进入和退出同步块时,执行一些额外的操作,从而降低了程序的性能。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java synchronized底层的实现原理 - Python技术站