原子性提供了互斥访问,同一个时刻只能有一个线程对其进行操作,Java里能够保持原子性的除了atomic包之外还有锁
synchronized关键字:主要依赖jvm来实现锁,在这个关键字的作用对象的作用范围内只能有一个线程来执行操作。
Lock:jdk提供的代码层面的锁,依赖特殊的cpu指令,比较有代表性的有ReentrantLock
synchronized是一种同步锁,它修饰的对象主要有四种:
修饰代码块(同步语句块):大括号括起来的代码,作用于调用的对象。
修饰方法(同步方法):整个方法,作用于调用的对象。
同步语句块及同步方法的作用仅局限于调用的对象。即该锁存在于同一个实例当中。演示如下:
@Slf4j
public class SynchronizedExample1 {
// 修饰一个代码块
public void test1(int j) {
synchronized (this) {
for (int i = 0; i < 10; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
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);
});
}
}
查看执行结果如下:
可以看到执行的结果是test1 0-9,test2 0-9。
分别创建两个对象:
@Slf4j
public class SynchronizedExample1 {
// 修饰一个代码块
public void test1(int j) {
synchronized (this) {
for (int i = 0; i < 10; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test2(1);
});
executorService.execute(() -> {
example2.test2(2);
});
}
}
执行结果:
可以看到线程一和二是乱序执行的。说明锁只存在于调用的对象里。
同理修饰一个方法执行代码及结果如下:
@Slf4j
public class SynchronizedExample1 {
// 修饰一个方法
public synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test2(1);
});
executorService.execute(() -> {
example1.test2(2);
});
}
}
执行结果如下:
分别创建两个对象:
@Slf4j
public class SynchronizedExample1 {
// 修饰一个方法
public synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test2(1);
});
executorService.execute(() -> {
example2.test2(2);
});
}
}
执行结果如下:
修饰静态方法:整个静态方法,作用于所有对象。
修饰类:括号括起来的部分,作用于所有对象。
修饰类示例如下:
@Slf4j
public class SynchronizedExample2 {
// 修饰一个类
public static void test1(int j) {
synchronized (SynchronizedExample2.class) {
for (int i = 0; i < 10; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
public static void main(String[] args) {
SynchronizedExample2 example1 = new SynchronizedExample2();
SynchronizedExample2 example2 = new SynchronizedExample2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test1(1);
});
executorService.execute(() -> {
example2.test1(2);
});
}
}
执行结果如下:
修饰静态方法示例如下:
@Slf4j
public class SynchronizedExample2 {
// 修饰一个静态方法
public static synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
SynchronizedExample2 example1 = new SynchronizedExample2();
SynchronizedExample2 example2 = new SynchronizedExample2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test2(1);
});
executorService.execute(() -> {
example2.test2(2);
});
}
}
执行结果如下:
可以看到,即使是不同实例,它们之间仍然能顺序执行。所以修饰静态方法和修饰类,它作用于所有对象。
注意:子类继承父类是带不上synchronized关键字,synchronized是不属于方法声明的一部分。如果子类也想用synchronized需要自己显示声明。
synchronized:不可中断锁,一旦代码执行到其作用范围内是必须等待代码执行完毕。适合竞争不激烈,可读性好。在竞争激烈时,性能急剧下降。
Lock:可中断锁,多样化同步,比如有时间限制同步。竞争不激烈时,性能稍比synchronized差。竞争激烈时能够维持常态(对于相同的代码,不断提高请求(或并发)时,平均处理速度上变化不大)
Atomic包:竞争不激烈时,性能稍比synchronized差,竞争激烈时能维持常态。比lock性能好,但是只能同步一个值。
所以,写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock和Atomic如果用的不好,不仅不能提高性能,还可能带来灾难。
想要查看各测试结果,可以参看https://blog.csdn.net/z69183787/article/details/48420643