1、Java中synchronized关键字的使用
synchronized关键字主要用于对java程序对共享资源的访问控制,可以在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized还可以保证一个线程对共享资源的修改被其他线程看到,完全可以替换volatile。
synchronized关键字在Java程序中主要有以下三种应用方式:
(1) 修饰静态方法,相当于对类的class对象加锁。由于静态方法不专属于任何一个实例对象,是类的成员,当线程A访问加锁的静态方法时,线程B就只能等待,具体示例代码如下:
public class SynchronizedForClass {
static int i = 0;
/*
* 此方法是加了synchronized关键字的静态方法
*/
public static synchronized void synStatic() {
i++;
}
/*
* 此方法是加了synchronized关键字的普通方法
*/
public synchronized void synNoStatic() {
i++;
}
public static void main(String[] args) throws InterruptedException {
// 创建2个线程t1、t2
Thread t1 = new SynClass(new SynchronizedForClass());
Thread t2 = new SynClass(new SynchronizedForClass());
// 启动线程t1、t2
t1.start();
t2.start();
// 主线程等待t1、t2执行完成
t1.join();
t2.join();
// 调用的是静态加锁的方法,因此t1和t2执行完成后,i的值是20000
System.out.println(i);
}
}
class SynClass extends Thread {
private SynchronizedForClass synchronizedForClass;
public SynClass(SynchronizedForClass synchronizedForClass) {
this.synchronizedForClass = synchronizedForClass;
}
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
synchronizedForClass.synStatic();
}
}
}
上述代码执行完结果是i=20000,这便是synchronized对静态方法加锁的作用,加锁的对象是SynchronizedForClass 类对象。线程t1和线程t2对synStatic()方法访问是互斥的,t1在执行synStatic()方法时,t2只能等待t1执行完,因为t2需要竞争SynchronizedForClass 类对象锁,但t2可以在t1执行synStatic()方法时去执行synNoStatic()方法,因为两个方法执行时加锁的对象不同,但是对共享资源i的执行结果就不会再是绝对的i=20000了,而是10000<i<=20000。代码如下:
public class SynchronizedForClass {
static int i = 0;
/*
* 此方法是加了synchronized关键字的静态方法
*/
public static synchronized void synStatic(){
i++;
}
/*
* 此方法是加了synchronized关键字的普通方法
*/
public synchronized void synNoStatic(){
i++;
}
public static void main(String[] args) throws InterruptedException {
// 创建2个线程t1、t2
Thread t1 = new SynClass(new SynchronizedForClass());
Thread t2 = new SynClass(new SynchronizedForClass());
// 启动线程t1、t2
t1.start();
t2.start();
// 主线程等待t1、t2执行完成
t1.join();
t2.join();
// i的值不固定
System.out.println(i);
}
}
class SynClass extends Thread {
private SynchronizedForClass synchronizedForClass;
public SynClass(SynchronizedForClass synchronizedForClass) {
this.synchronizedForClass = synchronizedForClass;
}
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
synchronizedForClass.synNoStatic();
}
}
}
上述代码t1和t2加锁的对象变了,由原来的共享一个SynchronizedForClass 类对象锁,变成了分别拥有各自的SynchronizedForClass 类实例对象锁,因此对共享资源的操作就不再是互斥的,线程安全就无法保证了。
(2) 修饰普通实例方法,相当于对类的实例对象加锁,与类的class对象锁属于不同的锁。
(3) 修饰代码块,对指定对象加锁,进入同步代码块前需要获得指定对象的锁。
Object obj = new Object();
synchronized (obj){
i++;
}
同步代码块加锁方式可以减小锁的粒度,对于方法体比较大,且方法体内有比较耗时的代码,耗时的代码与共享资源操作无关时,我们可以使用同步代码块单独对共享资源的操作加锁,从而提升代码执行效率。