上文中说起在java中能够使用synchronized
关键字来解决竟态条件。主要经过synchronized
关键字来标注代码块,告诉jvm该代码块为临界区代码,以保证每次只会有一个线程能访问到该代码块里的代码,直到一个线程执行完毕后,另外一个线程才能执行。html
synchronized
使用对象做为同步锁。若多个同步代码块使用的是同一个对象锁,那么一次只能有一个线程访问多个同步代码块中的一个。若多个同步代码块使用的不是同一个对象锁,那么多个线程可以同时访问多个同步代码块。java
synchronized
一共有四种用法,以下所示:安全
在方法签名中声明synchronized
,可以让整个方法标注为代码块。多线程
public synchronized void method0() {
// do something
}
复制代码
示例代码中使用的是该方法所属的实例对象做为对象锁。并发
public synchronized static void method0() {
// do something
}
复制代码
示例代码中使用的是该方法所属类声明指向的静态实例对象做为对象锁。jvm
若不但愿将整个方法标注为代码块,能够在方法中标注部分代码块做为同步代码块。工具
public void method1() {
synchronized (this) {
// do something
}
}
复制代码
在实例方法中,经过synchronized
构造方法的方式来标注代码块,括号中传递的是该同步代码块使用的对象锁,须要实现同步的临界区代码写在{}
中。代码中的this
指该代码块所属方法所属的对象实例做为对象锁。该方式与在实例方法签名中声明synchronized
效果至关。post
public synchronized static void method1() {
synchronized (MyClass.class) {
// do something
}
}
复制代码
在静态方法中,经过synchronized
构造方法的方式来标注代码块,括号中传递的是该同步代码块使用的对象锁,须要实现同步的临界区代码写在{}
中。代码中的MyClass.class
指该代码块所属方法所属类的静态对象实例做为对象锁。该方式与在静态方法签名中声明synchronized
效果至关。this
synchronized
编码实例,咱们在SynchronizedExample中编写四个方法,分别反映上文说起的四种状况。每一个方法中都在线程进入后暂停3s,随后线程退出代码块。编码
public class SynchronizedExample {
private static void method(String name) {
final Thread thread = Thread.currentThread();
LocalDateTime now = LocalDateTime.now();
System.out.println(thread.getName() + ": [" + now + "] in " + name);
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void method0() {
method("instance-method0");
}
public void method1() {
synchronized (this) {
method("instance-method1");
}
}
public synchronized static void method2() {
method("static-method2");
}
public synchronized static void method3() {
synchronized (SynchronizedExample.class) {
method("static-method3");
}
}
public static void main(String[] args) {
SynchronizedExample example = new SynchronizedExample();
Runnable myRunnable0 = () -> {
example.method0();
example.method1();
};
Runnable myRunnable1 = () -> {
SynchronizedExample.method2();
SynchronizedExample.method3();
};
IntStream.range(1, 3)
.forEach(i -> new Thread(myRunnable0, "Thread-" + i).start());
// 实例同步方法须要12s才能执行完,主线程等待13s后再执行静态同步方法
try {
Thread.sleep(13000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
IntStream.range(1, 3)
.forEach(i -> new Thread(myRunnable1, "Thread-" + i).start());
}
}
复制代码
执行结果:
Thread-1: [2019-03-15T14:48:50.251] in instance-method0
Thread-2: [2019-03-15T14:48:53.255] in instance-method0
Thread-2: [2019-03-15T14:48:56.258] in instance-method1
Thread-1: [2019-03-15T14:48:59.262] in instance-method1
Thread-1: [2019-03-15T14:49:03.234] in static-method2
Thread-2: [2019-03-15T14:49:06.238] in static-method2
Thread-2: [2019-03-15T14:49:09.243] in static-method3
Thread-1: [2019-03-15T14:49:12.247] in static-method3
从结果能够看出,使用同个对象实例做为对象锁和使用同个静态对象做为对象锁的方法分别被线程1和线程2访问。 从上文打印的时间能够看出每一个线程每次仅能访问使用同个对象锁的多个同步代码块中的一个。每3s执行完一个同步代码块。
实际上synchronized
是java中第一次针对竞态条件发布的同步措施,但在实际开发中并非那么好用,所以在jdk1.5后,发布了整个并发工具包
,提供了各式各样的多线程安全控件,用于协助开发者编写线程安全的应用程序。
该系列博文为笔者复习基础所著译文或理解后的产物,复习原文来自Jakob Jenkov所著Java Concurrency and Multithreading Tutorial