synchronized
是Java
中的同步关键字。
如果一个对象可能被多个线程同时访问,我们将这个资源称之为临界资源。当多个线程同时访问一个资源时,可能出现数据不一致或不完整的情况。所以需要采取同步措施,保证同一时间内只有一个线程访问该资源。
synchronized
修饰符实现的同步机制叫互斥锁机制,它所获得的锁叫做互斥锁,每个对象都有一个锁标记。
synchronized
可以修饰方法,代码块。
修饰代码块
看一个栗子:
/**
* @author zhaojiabao 2017/8/31
*/
public class TestSynchronized {
private void func1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println("func1: " + Thread.currentThread());
}
}
}
public static void main(String[] args) {
TestSynchronized obj1 = new TestSynchronized();
new Thread(obj1::func1).start();
new Thread(obj1::func1).start();
}
}
输出:
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-0,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
可以看到,虽然Thread1
和Thread2
是并发执行的,但等到Thread1
执行完毕后Thread2
才开始执行。
是因为这两个线程同时访问了同一个对象的synchronized
代码块。所以Thread2
的访问被阻塞了。直到Thread1
访问完毕,Thread2
才可以访问synchronized
代码块。
注意这里锁定的是this
:
synchronized (this)
规则是这样的:
则当一个线程在访问某个对象的synchronized (this)
代码块时,其他试图访问这个对象中所有被
synchronized (this)
修饰的代码块的线程都会被阻塞。
也可以用一个特定的对象来作为线程锁:
Object syncObject = new Object();
synchronized (syncObject) {
//TODO
}
this
也是一个对象,本质上是一样的。
修饰方法
当一个方法被synchronized
修饰时,线程想要执行该方法,必须拿到这个方法所属对象的锁。
看一个例子:
/**
* @author zhaojiabao 2017/8/31
*/
public class TestSynchronized {
private synchronized void func2() {
for (int i = 0; i < 10; i++) {
System.out.println("func2: " + Thread.currentThread());
}
}
private void func1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.println("func1: " + Thread.currentThread());
}
}
}
public static void main(String[] args) {
TestSynchronized obj1 = new TestSynchronized();
new Thread(obj1::func2).start();
new Thread(obj1::func1).start();
}
}
输出:
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func2: Thread[Thread-0,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
func1: Thread[Thread-1,5,main]
可以看到,由于Thread1
拿到了obj1
对象锁,故Thread2
的执行被阻塞, 直到Thread1
执行完毕后Thread2
才开始执行。
类锁
类锁的两种写法:
synchronized (Foo.class) {
}
2.同步的static方法:
synchronized static void foo() {
}
类锁是用来控制静态方法(或静态变量互斥体)之间的同步。
对象锁是用来控制实例方法之间的同步。
这就是区别。
PS:
其实我感觉第一种和第二种写法其实是不等价的。因为静态方法只能访问类的静态变量,所以第一种同步的其实是类的静态变量;而第二种方法同步是可以访问类的静态和非静态变量,所以相当于将静态/非静态变量都同步了,综上,两种写法其实应该是不等价的。
使用一个局部变量作为线程锁
正常情况下,这种写法是没有意义的。
因为是局部变量,多线程是无法共享的。借用圣骑士Wind博客中的一句话:
如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝(即便是同一个对象中的方法的局部变量,也会对每一个线程有一个拷贝),一个线程对该局部变量的改变不会影响到其他线程。
既然局部变量不会被多个线程共享,那么多个线程拿到根本不是同一个对象,也就意味着不是同一把对象锁。综上,局部变量作为线程锁是没有意义的。
但是,某些时候你会发现,有人会把一个局部变量作为线程锁,看下Stack Overflow上的这个问题:
use local variable as thread sync lock
对象锁的选择
这里稍微装下逼,当我们需要一个对象锁时,一般都会这样写:
Object mSyncObj = new Object();
咳咳,其实有一种逼格更高的写法:
byte[] mSyncObj = new byte[0];
零长度的byte
数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]
对象只需3条操作码,而
Object mSyncObj = new Object();
则需要7行操作码。