在多线程中,可能出现有两个甚至多个线程试图访问同一个资源的情况,此时,就可能导致资源冲突,引起线程安全的问题。一个经典的例子就是两个人同时对同一个银行账户分别进行存钱取钱的操作。
Java当中,为了使多个线程之间能够安全的共享资源,引入了同步(synchronized)的机制。synchronized可以用在对象的实例方法中,也可以用在类的静态方法中,还可以用于同步语句块中。
1、synchronized实例方法
如下,synchronized关键字用于对象的实例方法中,访问该对象的这些方法时,需要首先获取该对象的锁。
synchronized void f() {
//
}
synchronized void g() {
//
}
对于每一个对象来说,都存在一把“锁”,这个锁的信息存放在对象头中。当我们想要调用一个对象的synchronized方法时,必须首先获取这个对象的锁,然后才能调用该方法。而且,当我们获取到了这把锁调用对象的一个synchronized方法时,该对象就会被锁定,此时就无法调用该对象的其他synchronized方法,直到前面一个synchronized方法调用结束,将锁抛出来解除了对该对象的锁定。
例如上面的例子中,在调用了f()之后就不能调用g(),除非f()调用完成了并解除了锁定。
一个特定对象的多个synchronized方法是共享该对象的同一个锁的,这样,就能防止该对象的多个方法同时对通用内存进行读写操作。
如果调用了对象的某一个被synchronized的方法,那么仍然可以调用该对象的其他的未被同步的方法。所以,为了避免线程安全问题,必须对访问某个关键资源的所有方法都进行同步。
2、synchronized静态方法
对于synchronized静态方法,它锁的是这个类的Class对象的锁。因此,所有访问同一个类的synchronized静态方法都需要首先获得这个类的Class对象的锁,而与实例方法获取的锁不是同一把。
static synchronized void f() {
//
}
static synchronized void g() {
//
}
3、synchronized语句块
另外,除了对一个方法进行同步后,还可以对一个语句块进行同步,例如:
void f() {
//
synchronized(aObject) {
//
}
//
}
同样的,在进入同步语句块之前,需要获取aObject这个对象的锁,并且,进入了同步块之后,在同步块结束之前,不能调用对象aObject的其他同步方法(同步块)。
上面提到的锁是针对每一个对象实例的。
除此之外,每一个类也有一把锁,它是作为该类的Class对象的一部分。所以,根据类的这把锁,synchronized static方法可以在一个类的范围内被相互锁定起来,防止类的static数据的访问冲突。
然而,为一个方法添加synchronized之后,调用该方法需要额外的代价,因此,不应滥用synchronized,对于已知的不会造成冲突的方法,不应该进行同步。