synchronized 关键字是Java中实现线程同步的基本机制之一。它用于保护共享资源不被多个线程同时访问,从而避免数据不一致和保证
线程安全。synchronized 可以用于方法和代码块,有助于确保在同一时刻只有一个线程可以进入同步代码块。
1.synchronized 是如何实现线程同步的?
- 同步代码块:通过
monitorenter和monitorexit字节码指令实现 - 同步方法:通过方法访问标志
ACC_SYNCHRONIZED实现,JVM检查方法的ACC_SYNCHRONIZED标志,若存在,则自动获取对象Monitor锁,方法执行完毕后释放锁.
Monitor 被翻译为监视器或管程
每个 Java 对象都可以关联一个 Monitor 对象,Monitor 也是 class,其实例存储在堆中,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针,这就是重量级锁
无论是同步代码块还是同步方法,最终都是通过Monitor 对象来管理锁的获取和释放,Monitor 对象锁机制依赖于操作系统的互斥锁。
Java的对象有一个头 (0bject Header),其中包含一个 mark word ,存储锁状态(无锁/偏向锁/轻量级锁/重量级锁)及指向Monitor的指针
JVM会根据竞争情况自动进行状态变换以优化性能。
2.使用方法
原则上:
- 锁对象建议使用共享资源
- 在实例方法中使用 this 作为锁对象,锁住的 this 正好是共享资源
- 在静态方法中使用类名 .class 字节码作为锁对象,因为静态成员属于类,被所有实例对象共享,所以需要锁住类
同步代码块格式:
synchronized(锁对象){
// 访问共享资源的核心代码
}
实例:
public class demo {
static int counter = 0;
//static修饰,则元素是属于类本身的,不属于对象 ,与类一起加载一次,只有一个
static final Object room = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
counter++;
}
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (room) {
counter--;
}
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter); //最终打印结果为0
}
}
3.Synchronized与ReentrantLock的区别
Synchronized 和 ReentrantLock 都是java中用于实现线程同步的机制,它们各有优缺点和适用场景。以下是它们的比较和适用示例:
ReentrantLock 相对于 synchronized 具备如下特点:
- 锁的实现:synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的
- 性能:新版本 Java 对 synchronized 进行了很多优化,synchronized 与 ReentrantLock 大致相同
- 使用:ReentrantLock 需要手动解锁,synchronized 执行完代码块自动解锁
- 可中断:ReentrantLock 可中断,而 synchronized 不行
- 公平锁:公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁
- ReentrantLock 可以设置公平锁,synchronized 中的锁是非公平的
- 不公平锁的含义是阻塞队列内公平,队列外非公平
- 锁超时:尝试获取锁,超时获取不到直接放弃,不进入阻塞队列
- ReentrantLock 可以设置超时时间,synchronized 会一直等待
- 锁绑定多个条件:一个 ReentrantLock 可以同时绑定多个 Condition 对象,更细粒度的唤醒线程
- 两者都是可重入锁
适用场景:
- synchronized:适用于简单的同步需求。由于其语法简单且嵌入在Java语言中,特别适合锁定范围与方法等价的情况。小规模、多线程竞争不高的情况下表现优异。适合开发者不想处理锁的复杂生命周期时使用。
- ReentrantLock:适用于需要更高级的同步控制,或者锁定范围与方法不同时。特别是在需要公平锁、可中断锁操作、尝试获取带超时功能的锁,或者需要多个条件等待时,应选择 ReentrantLock。当系统规模较大、线程数较多,且具有复杂同步需求的情境时表现突出。
- 选择一个合适的锁机制,将有助于提升应用程序的性能并简化并发代码的编写。