synchronized 关键字的作用
synchronized 关键字解决的是多个线程之间访问资源的同步性,用于保证在同一时刻
最多只有一个线程
执行该段代码,以达到保证并发安全的效果。
作用范围
- 修饰代码块:大括号括起来的代码,作用于
调用的对象
。 - 修饰方法:整个方法,作用于
调用的对象
。 - 修饰静态方法:整个静态方法,作用于
所有对象
。 - 修饰类:括号括起来的部分,作用于
所有对象
。
其中前两种使用的锁称为对象锁
,后两种称为类锁
。我们来分别演示一下这几种情况:
修饰代码块和修饰方法
1.同一个对象不同线程调用
public class SynchronizedExample1 {
private static final Logger LOGGER = LoggerFactory.getLogger(SynchronizedExample1.class);
/**
* 修饰一个代码块
*
* @param j 标识
*/
public void test1(int j) {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
//每次循环睡50毫秒
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("test1 {} - {}", j, i);
}
}
}
/**
* 修饰一个方法
*
* @param j 标识
*/
public synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
try {
//每次循环睡50毫秒
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(
() -> example1.test1(1)
);
executorService.execute(
() -> example1.test1(2)
);
}
}
打印结果:
16:16:09.924 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 0
16:16:09.979 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 1
16:16:10.029 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 2
16:16:10.080 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 3
16:16:10.131 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 4
16:16:10.436 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 0
16:16:10.487 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 1
16:16:10.538 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 2
16:16:10.589 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 3
16:16:10.639 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 4
从结果我们发现他确实是按照我们预期锁住了代码块,test(1) 执行完之后,再执行 test(2)。
2.不同对象不同线程调用
我们把 main 方法改成如下,再执行:
public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(
() -> example1.test1(1)
);
executorService.execute(
() -> example2.test1(2)
);
}
打印结果:
16:23:48.821 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 0
16:23:48.821 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 0
16:23:48.877 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 1
16:23:48.877 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 1
16:23:48.928 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 2
16:23:48.928 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 2
16:23:48.979 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 3
16:23:48.979 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 3
16:23:49.030 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 2 - 4
16:23:49.030 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test1 1 - 4
从结果看不同对象之间互不影响,所以 synchronized 修饰代码块时,作用于对象。修饰方法和修饰代码块是一样的,可以把以上例子中调用的 test1() 改成如下 test2(),执行观察结果。
修饰静态方法和修饰类
/**
* 修饰一个类
*
* @param j 标识
*/
public static void test1(int j) {
synchronized (SynchronizedExample2.class) {
for (int i = 0; i < 5; i++) {
try {
//每次循环睡50毫秒
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("test1 {} - {}", j, i);
}
}
}
/**
* 修饰一个静态方法
*
* @param j 标识
*/
public static synchronized void test2(int j) {
for (int i = 0; i < 5; i++) {
try {
//每次循环睡50毫秒
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(
() -> test2(1)
);
executorService.execute(
() -> test2(2)
);
}
打印结果:
16:48:47.699 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 1 - 0
16:48:47.754 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 1 - 1
16:48:47.805 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 1 - 2
16:48:47.856 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 1 - 3
16:48:47.907 [pool-1-thread-1] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 1 - 4
16:48:47.958 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 2 - 0
16:48:48.009 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 2 - 1
16:48:48.060 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 2 - 2
16:48:48.110 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 2 - 3
16:48:48.161 [pool-1-thread-2] INFO com.hd.concurrency.example.sync.SynchronizedExample1 - test2 2 - 4
其实在测试之前就很明显能知道结果了,因为静态方法是属于类的,不属于某个对象,也就是说无论那个对象调用,synchronize 的锁都是静态方法所在的类。也即,synchronize 修饰静态方法和修饰类时,他是一个类锁,与调用对象无关(其实调用静态方法也不是对象的形式调用,类.test1() 的形式调用)。
synchronized 关键字的性质
- 可重入:避免死锁、提升封装性
- 不可中断:一旦这个锁已经被别人获得了,如果我还想获得,就只能选择等待或者阻塞,知道别的线程释放这个锁。如果别人永远都不释放这个锁,那么我只能永远等待下去。
synchronized 关键字的缺陷
- 效率低
- 锁的释放情况少(执行完锁定代码块或者遇到异常退出)
- 试图获得锁时不能设定超时时间
- 不能中断一个正在试图获得锁的线程
- 不够灵活
- 加锁和释放锁的时机单一
- 每个锁仅有单一的条件(某个对象)
- 无法知道是否成功获取到锁
原子性-对比
- synchronize:不可中断,适合竞争不激烈的场景,可读性好
- lock:可中断,多样化同步,竞争激烈时能维持常态
- Atomic:竞争激烈时能维持常态,比lock性能好,只能同步一个值