synchronized关键字是同步锁,依赖于对象而存在,每一个对象有且仅有一个同步锁,当我们调用一个被synchronized所修饰的方法或者代码块的时候,就可以获取到当前对象的的锁
synchronized是原理
synchronized是通过对象中monitor(监视锁)来实现的,而monitor底层又依赖于操作系统的互斥锁来实现的,而阻塞或者唤醒一个线程都是需要操作系统来帮忙,这就需要从用户态转到内核态,这个过程相对于来说比较长,这就是为什么synchronized效率低的原因,所以synchronized被称之为”重量级锁”
1、monitor监视锁
当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
- 如果一个monitor的进数为0,则该线程可以进入,然后monitor的进数加1
- 如果线程已经占有的monitor,只是重新进入的话,monitor的进数加1
- 如果monitor被其他线程所占用的,则该线程会进入阻塞状态,直到monitor的进数等于0的情况下,该线程才会重新去竞争monitor的所有权
2、synchronized锁
在java SE1.6中对synchronized进行了优化,它也不在是那么重量级了,不同的场景会采用不同的锁
- 偏向锁:适用于锁没有竞争的情况下,假如共享变量只有一个线程在访问的时候,就不会存在同步的操作,该线程重新进入或者退出同步代码块,都不需要进行解锁或者加锁的操作,当有其他线程来竞争的时候,偏向锁会膨胀成轻量锁
- 轻量锁:当多个线程同时竞争一个锁,只有一个线程可以获取到锁,其他线程并不会阻塞,而是进入自旋状态,也就是空循环,直到占有锁的线程解锁以后,其他线程再次竞争锁,线程进入自旋状态下会消耗CPU资源,当自旋的次数超过次数限制时,锁会再次膨胀成为重量级锁
- 重量级锁:重量级锁又称为互斥锁,依赖于monitor锁来实现,会造成线程阻塞,每一次唤醒以及阻塞线程都需要从用户态切换到内核态,时间较长,但是支持的吞吐量较大
尽管采用了偏向锁和轻量锁的优化方案,但是依旧有着一些问题,比如在锁竞争比较激烈的情况下,中间多了膨胀锁以及撤销锁的步骤,不但没有提高效率,反而大大的降低了效率,此时就需要显示的去禁用偏向锁
3、synchronized锁各阶段优缺点
偏向锁:
- 优点:线程重新进入和退出线程不需要加锁以及解锁的操作,和执行非同步代码块的速度仅相差纳秒级别
- 缺点:当线程竞争的情况下,需要撤销原有的偏向锁,使用轻量锁,会造成额外的锁撤销消耗
- 适用场景:使用只有一个线程去获取锁的情况下
轻量锁:
- 优点:线程不会阻塞,响应速度快
- 缺点:线程会进入自旋状态,消耗CPU资源
- 适用场景:并发量较小,同步块执行速度较快,追求响应速度
重量级锁:
- 优点:线程不会自旋,不会消耗CPU
- 缺点:线程会进入阻塞状态,每次唤醒和阻塞线程会从用户态切换到内核态,所以响应速度较慢
- 适用场景:追求吞吐量
synchronized用法
- 修饰代码块,作用于当前对象
public void test5() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
count++;
Thread.sleep(1000);
log.info("{}-{}", Thread.currentThread().getName(), i);
}
countDownLatch.countDown();
}
}
@SneakyThrows
public static void main(String[] args) {
SynchronizedTest SynchronizedTest = new SynchronizedTest();
new Thread(() -> SynchronizedTest.test5()).start();
new Thread(() -> SynchronizedTest.test5()).start();
countDownLatch.await();
}
// 多线程的情况,从执行结果可以看出只有当线程0执行完毕后,线程1才可以执行, 被synchronized修饰得代码块只作用于当前对象
- 修饰方法,作用于当前对象
@SneakyThrows
public synchronized void test6() {
for (int i = 0; i < 10; i++) {
count++;
Thread.sleep(1000);
log.info("{}-{}", Thread.currentThread().getName(), i);
countDownLatch.countDown();
}
}
@SneakyThrows
public static void main(String[] args) {
SynchronizedTest SynchronizedTest = new SynchronizedTest();
new Thread(() -> SynchronizedTest.test6()).start();
new Thread(() -> SynchronizedTest.test6()).start();
countDownLatch.await();
}
// 多线程的情况,从执行结果可以看出只有当线程0执行完毕后,线程1才可以执行, 被synchronized修饰得方法只作用于当前对象
- 修饰静态方法,作用于当前类对象
@SneakyThrows
public synchronized static void test7() {
for (int i = 0; i < 10; i++) {
count++;
Thread.sleep(1000);
log.info("{}-{}", Thread.currentThread().getName(), i);
countDownLatch.countDown();
}
}
@SneakyThrows
public static void main(String[] args) {
SynchronizedTest SynchronizedTest1 = new SynchronizedTest();
SynchronizedTest SynchronizedTest2 = new SynchronizedTest();
new Thread(() -> SynchronizedTest1.test7()).start();
new Thread(() -> SynchronizedTest2.test7()).start();
countDownLatch.await();
}
// 多线程的情况,从执行结果可以看出只有当线程0执行完毕后,线程1才可以执行, 通过不同的对象调用同一个静态方法,结果还是按照同步执行的,也就说明了被Synchronized所修饰的静态方法,作用于类对象
- 修饰类,作用于类对象
public void test1() {
synchronized (SynchronizedTest.class) {
for (int i = 0; i < 10; i++) {
log.info("当前线程:{} - {}", Thread.currentThread().getName(), i);
}
countDownLatch.countDown();
}
}
@SneakyThrows
public static void main(String[] args) {
SynchronizedTest SynchronizedTest1 = new SynchronizedTest();
SynchronizedTest SynchronizedTest2 = new SynchronizedTest();
new Thread(() -> SynchronizedTest1.test1()).start();
new Thread(() -> SynchronizedTest2.test1()).start();
countDownLatch.await();
}
// 多线程的情况,从执行结果可以看出只有当线程0执行完毕后,线程1才可以执行, 通过不同的对象调用还是同步在执行,也就说明了被Synchronized所修饰的类,作用于类对象