原子性概念
原子性提供了程序的互斥操作,同一时刻只能有一个线程能对某块代码进行操作。
原子性的实现方式
在jdk中,原子性的实现方式主要分为:
synchronized
:关键词,它依赖于JVM,保证了同一时刻只能有一个线程对作用对象的作用范围内进行操作。Lock
:代码层面的锁,依赖CPU指令,主要实现类ReentrantLock
,之后再说。atomic
包:该包中提供了一些可以保证原子操作的类。
注意上面加粗内容,比较关键,后面会有讲解,着重理解
从性能上看,上面三个逐优,但各自又有不同。
synchronized
是不可中断锁,适合竞争不激烈,可读性比较好。Lock
是可中断锁,多样化同步,竞争激烈时能维持常态。atomic
竞争激烈时能维持常态,比Lock性能好,但只能同步一个值。
synchronized的四种用法
synchronized
修饰的代码块,作用于调用的对象synchronized
修饰的方法,作用于调用的对象synchronized
修饰的静态方法,作用于这个类的所有对象synchronized
修饰的类,作用于这个类的所有对象
synchronized修饰的代码块和修饰的方法
package cn.com.dotleo.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by liufei on 2018/6/24.
*/
public class Sync1 {
public void test1(int x) {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println("["+ x + "]test1: " + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
final Sync1 sync = new Sync1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
public void run() {
sync.test1(1);
}
});
executorService.execute(new Runnable() {
public void run() {
sync.test1(2);
}
});
}
}
代码参见Sync1.java
上面代码中,主要体现了多线程下,同一个对象调用同步代码块。
运行结果:
[1]test1: 0
[1]test1: 1
[1]test1: 2
[1]test1: 3
[1]test1: 4
[1]test1: 5
[1]test1: 6
[1]test1: 7
[1]test1: 8
[1]test1: 9
[2]test1: 0
[2]test1: 1
[2]test1: 2
[2]test1: 3
[2]test1: 4
[2]test1: 5
[2]test1: 6
[2]test1: 7
[2]test1: 8
[2]test1: 9
由此结果可以看出,在多线程下,两个线程同时作用于一个对象时,该代码块被先抢到资源的进程执行结束后另一个线程才得以执行。
将主函数修改如下:
public static void main(String[] args) {
final Sync1 sync1 = new Sync1();
final Sync1 syncNew = new Sync1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
public void run() {
sync1.test1(1);
}
});
executorService.execute(new Runnable() {
public void run() {
syncNew.test1(2);
}
});
}
代码参见Sync1.java
[1]test1: 0
[2]test1: 0
[1]test1: 1
[2]test1: 1
[1]test1: 2
[2]test1: 2
[1]test1: 3
[2]test1: 3
[1]test1: 4
[2]test1: 4
[1]test1: 5
[2]test1: 5
[1]test1: 6
[2]test1: 6
[1]test1: 7
[2]test1: 7
[1]test1: 8
[2]test1: 8
[2]test1: 9
[1]test1: 9
从结果中可以看出,对于两个线程分别调用两个对象时,并没有锁的出现,线程交叉执行并输出。
同理,synchronized
关键词修饰方法也是如此,这里不再演示,代码参见Sync2.java
因此我们可以看出:
synchronized
修饰的代码块,作用于调用的对象synchronized
修饰的方法,作用于调用的对象
synchronized修饰的静态方法和类
修饰静态方法:
package cn.com.dotleo.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by liufei on 2018/6/24.
*/
public class Sync3 {
public static synchronized void test3(int x) {
for (int i = 0; i < 10; i++) {
System.out.println("["+ x + "]test3: " + i);
}
}
public static void main(String[] args) {
final Sync3 sync3 = new Sync3();
final Sync3 syncNew = new Sync3();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
public void run() {
sync3.test3(1);
}
});
executorService.execute(new Runnable() {
public void run() {
syncNew.test3(2);
}
});
}
}
代码参见Sync3.java
关于synchronized修饰静态方法和类不再演示两个线程调用同一个对象,因为那样肯定是加锁的顺序输出。
[1]test3: 0
[1]test3: 1
[1]test3: 2
[1]test3: 3
[1]test3: 4
[1]test3: 5
[1]test3: 6
[1]test3: 7
[1]test3: 8
[1]test3: 9
[2]test3: 0
[2]test3: 1
[2]test3: 2
[2]test3: 3
[2]test3: 4
[2]test3: 5
[2]test3: 6
[2]test3: 7
[2]test3: 8
[2]test3: 9
修饰静态类:
package cn.com.dotleo.sync;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by liufei on 2018/6/24.
*/
public class Sync4 {
public static void test4(int x) {
synchronized (Sync4.class) {
for (int i = 0; i < 10; i++) {
System.out.println("["+ x + "]test4: " + i);
}
}
}
public static void main(String[] args) {
final Sync4 sync4 = new Sync4();
final Sync4 syncNew = new Sync4();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
public void run() {
sync4.test4(1);
}
});
executorService.execute(new Runnable() {
public void run() {
syncNew.test4(2);
}
});
}
}
代码参见Sync4.java
[1]test4: 0
[1]test4: 1
[1]test4: 2
[1]test4: 3
[1]test4: 4
[1]test4: 5
[1]test4: 6
[1]test4: 7
[1]test4: 8
[1]test4: 9
[2]test4: 0
[2]test4: 1
[2]test4: 2
[2]test4: 3
[2]test4: 4
[2]test4: 5
[2]test4: 6
[2]test4: 7
[2]test4: 8
[2]test4: 9
从上面的两个示例中都可以看出,在两个线程调用一个类的不同对象时,仍然能保证原子性。
由此可见:
synchronized
修饰的静态方法,作用于这个类的所有对象synchronized
修饰的类,作用于这个类的所有对象
特别强调:
synchronized
不是方法申明的一部分,如果子类继承了父类的synchronized
方法,除非子类不会有synchronized
修饰的。