synchronized 关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
另外,在 Java 1.6之前的版本中,synchronized
属于 重量级锁,效率低下。
因为监视器锁(monitor)是依赖于底层的操作系统的
Mutex Lock
来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。庆幸的是在 Java 6 之后 Java 官方对从 JVM 层面对
synchronized
较大优化,所以现在的synchronized
锁效率也优化得很不错了。JDK1.6 对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。所以,你会发现目前的话,不论是各种开源框架还是 JDK 源码都大量使用了
synchronized
关键字
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
synchronized实现同步的基础:Java中的每一个队形都可以作为锁,具体表现有以下几点:
尽量不要使用
synchronized(String a)
因为 JVM 中,字符串常量池具有缓存功能!
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("synchronized 代码块");
}
}
}
通过IDK自带的 == javap ==命令查看.class文件
从上面我们可以看出:synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。
当执行 monitorenter
指令时,线程试图获取锁也就是获取 对象监视器 monitor 的持有权。
在 Java 虚拟机(HotSpot)中,Monitor 是基于 C++实现的,由ObjectMonitoropen in new window实现的。每个对象中都内置了一个
ObjectMonitor
对象。另外,
wait/notify
等方法也依赖于monitor
对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify
等方法,否则会抛出java.lang.IllegalMonitorStateException
的异常的原因。
在执行monitorenter
时,会尝试获取对象的锁,如果锁的计数器为 0 则表示锁可以被获取,获取后将锁计数器设为 1 也就是加 1。
对象锁的的拥有者线程才可以执行 monitorexit
指令来释放锁。在执行 monitorexit
指令后,将锁计数器设为 0,表明锁被释放,其他线程可以尝试获取锁。
如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
public synchronized void method() {
System.out.println("synchronized 方法");
}
synchronized
修饰的方法并没有 monitorenter
指令和 monitorexit
指令,取得代之的确实是 ACC_SYNCHRONIZED
标识,该标识指明了该方法是一个同步方法。JVM 通过该 ACC_SYNCHRONIZED
访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
public static synchronized void method() {
System.out.println("synchronized 静态方法");
}
可以看出synchronized修饰静态方法和实例方法没有区别,都是增加一个ACC_SYNCHRONIZED的flag,静态方法只是比实例方法多一个ACC_STATIC标识代表这个方法是静态的。
如果是实例方法,JVM 会尝试获取实例对象的锁。如果是静态方法,JVM会尝试获取当前class的锁
package com;
//第一步 创建资源类,定义属性和和操作方法
class Ticket {
//票数
private int number = 30;
//操作方法:卖票
public synchronized void sale() {
//判断:是否有票
if(number > 0) {
System.out.println(Thread.currentThread().getName()+" : 卖出:"+(number--)+" 剩下:"+number);
}
}
}
public class SaleTicket {
//第二步 创建多个线程,调用资源类的操作方法
public static void main(String[] args) {
//创建Ticket对象
Ticket ticket = new Ticket();
//创建三个线程
new Thread(new Runnable() {
@Override
public void run() {
//调用卖票方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"AA").start();
new Thread(new Runnable() {
@Override
public void run() {
//调用卖票方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"BB").start();
new Thread(new Runnable() {
@Override
public void run() {
//调用卖票方法
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}
},"CC").start();
}
}
如果一个代码块被Synchronzied修饰了,当一个线程获得了相应地锁,并执行了该代码块,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里 获取锁的线程释放锁只会有两种情况:
那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用 sleep 方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一 下,这多么影响程序执行效率。 因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到。