【多线程】synchronized的特性

简介: 【多线程】synchronized的特性

synchronized 的特性

互斥

synchronized 会起到互斥效果,某个线程执行到某个对象的 synchronized 中时,其他线程如果也执行到同一个对象 synchronized 就会阻塞等待。进入 synchronized 修饰的代码块,相当于 加锁。退出 synchronized 修饰的代码块,相当于 解锁。synchronized用的锁是存在Java对象头里的。synchronized的底层是使用操作系统的mutex lock实现的。

可重入

synchronized 同步块对同一条线程来说是可重入的,不会出现死锁。可重入就是一个线程对同一把锁连续加锁两次不会出现死锁。

我们来看下面的代码👇👇👇

static class Counter {
  public int count = 0;
  synchronized void increase() {
    count++;
  }
  synchronized void increase2() {
    increase();
  }
}

increase 和 increase2 两个方法都加了 synchronized, 此处的 synchronized 都是针对 this 当前对象加锁的。在调用 increase2 的时候, 先加了一次锁, 执行到 increase 的时候, 又加了一次锁. (上个锁还没释放, 相当于连续加两次锁),因为synchronized是可重入锁,所以上面的代码不会出现死锁问题。

在可重入锁的内部,包含了 “线程持有者” 和 “计数器” 两个信息。如果某个线程加锁的时候,发现锁已经被人占用,但是恰好占用的正是自己,那么仍然可以继续获取到锁,并让计数器自增。解锁的时候计数器递减为 0 的时候,才真正释放锁。 (才能被别的线程获取到)

synchronized的使用

直接修饰普通方法:锁的 SynchronizedDemo 对象

public class SynchronizedDemo {
  public synchronized void fun() {
        //......
  }
}

修饰静态方法:锁的 SynchronizedDemo 对象

public class SynchronizedDemo {
  public synchronized static void fun() {
        //......
  }
}

修饰代码块:明确指定锁哪个对象

public class SynchronizedDemo {
  public void method() {
        //锁的是当前对象
    synchronized (this) {
            //......
    }
  }
}
public class SynchronizedDemo {
  public void method() {
        //锁的是类对象
    synchronized (SynchronizedDemo.class) {
            //......
    }
  }
}

加锁过程

JVM将synchronized锁分为:无锁、偏向锁、轻量级锁、重量级锁四种状态。会根据情况,进行依次升级。

1.偏向锁

第一个尝试加锁的线程,优先进入偏向锁状态。

偏向锁不是真的加锁,只是在对象头中做一个偏向锁的标记,记录这个锁属于哪个线程,后续如果没有所来竞争就不进行同步操作,避免了加解锁的开销。如果有其他线程来竞争就会取消偏向锁状态,进入轻量级锁的状态。偏向锁的本质是延迟加锁,能不加锁就不加锁,避免不必要的加解锁开销。但是标记还是要做,不然就无法区分何时需要正真需要加锁。

2.轻量级锁

随着其他线程的竞争,偏向锁状态消除,进入轻量级锁状态(自适应的自旋锁)此处的轻量级锁通过CAS来实现的

通过CAS检查并更新一块内存(比如null=>该线程被调用),如果更新成功则认为加锁成功,如果更新失败,则认为该锁被占用,继续自旋式等待。(自旋锁是一直让CPU空转,比较浪费CPU资源,因此这里的自旋不会一直进行,而是达到一定次数或时间就会停止自旋,这也就是“自适应”)

3.重量级锁

如果锁竞争进一步激烈,自旋锁就不能快速获取锁的状态了,进而就会升级为重量级锁(此处的重量级锁就是用到内核提供的mutex)

执行加锁操作,先进入内核态,在内核态判断当前锁是否已经被占用,如果该锁没有被占用,则加锁成功,并切换回用户态。如果该所被占用,则加锁失败,此时线程进入锁的等待队列,挂起,等待被操作系统唤醒。

总结:

1.synchronized可以保证内存可见性

2.synchronized初始使用乐观锁策略。 当发现锁竞争比较频繁的时候,就会自动切换成悲观锁策略。

3.synchronized不是读写锁

4.synchronized开始是一个轻量级锁,如果锁冲突比较严重,就会变成重量级锁。

5.synchronized中的轻量级锁策略大概率就是通过自旋锁的方式实现的。

6.synchronized是非公平锁。

7.synchronized是可重入锁。

相关文章
|
22天前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
55 5
|
24天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
Java并发编程实战:使用synchronized关键字实现线程安全
32 0
|
2月前
|
缓存 安全 算法
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
Java面试题:如何通过JVM参数调整GC行为以优化应用性能?如何使用synchronized和volatile关键字解决并发问题?如何使用ConcurrentHashMap实现线程安全的缓存?
25 0
|
22天前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
78 5
|
22天前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
18 2
|
22天前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
Java多线程同步实战:从synchronized到Lock的进化之路!
83 1
|
1月前
|
NoSQL 安全 Java
Lettuce的特性和内部实现问题之Lettuce连接与Jedis连接在线程安全性的问题如何解决
Lettuce的特性和内部实现问题之Lettuce连接与Jedis连接在线程安全性的问题如何解决
|
2月前
|
消息中间件 缓存 NoSQL
Redis快速度特性及为什么支持多线程及应用场景
Redis快速度特性及为什么支持多线程及应用场景
68 11
|
1月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
50 1
|
27天前
|
Java C++
【Java 并发秘籍】synchronized vs ReentrantLock:揭秘线程同步神器的对决!
【8月更文挑战第24天】本文详细对比了Java并发编程中`synchronized`关键字与`ReentrantLock`的不同之处。`synchronized`作为内置关键字,提供自动锁管理但不支持中断或公平锁;`ReentrantLock`则通过显式调用方法控制锁,具备更多高级功能如可中断、公平锁及条件变量。文章通过两个计数器类实例展示了两种机制的具体应用,帮助读者理解其差异及适用场景。掌握这两者对于提升多线程程序设计能力至关重要。
38 0