Java synchronized关键字详解

synchronizedJava中的同步关键字。

如果一个对象可能被多个线程同时访问,我们将这个资源称之为临界资源。当多个线程同时访问一个资源时,可能出现数据不一致或不完整的情况。所以需要采取同步措施,保证同一时间内只有一个线程访问该资源。

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]

可以看到,虽然Thread1Thread2 是并发执行的,但等到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行操作码。

你可能感兴趣的:(Java synchronized关键字详解)