java中使用synchronized关键字来解决多个线程之间的资源同步访问问题。
synchronized关键字可以保证被它修饰的方法或对象任意时刻只能有一个线程执行。
在jdk1.6之后,对synchronized关键字有很大的优化,其效率有很大的提升,很多开源框架里面都可以直接看到synchronized关键字。所以在日常开发中可以放心使用。
使用形式
synchronized主要分为三种使用方式:
1. 修饰非静态方法。相当于给当前对象加对象锁,其它线程要访问同步代码时,需要先获得对象锁。
synchronized void method() {
// do something
}
2. 修饰静态方法。由于静态方法代码在jvm中仅有一份实例,所以相当于是加了类锁。其它线程要访问同步代码时,需要先获得类锁。由于类锁锁住的是当前类,对象锁锁住的是某一个实例化对象,因此二者不会产生互斥现象(也就是多线程同时访问1和2不会有先后顺序,会交替执行)。
synchronized static void method() {
// do something
}
3. 修饰代码块。需要指定加锁对象。synchronized(this|object),表示进入同步代码块之前需要先获得相应对象的锁。synchronized(类.class)表示进入同步代码块前要获得当前类的锁。
synchronized (this|object|X.class) {
// do something
}
在第3种情况中,又会分出很多具体的情况,如锁住的对象是:this,非静态成员变量,静态成员变量,X.class等。
示例
class TService {
private final Integer type;
private final static Object lock = new Object();
/**
* 1. 直接锁住非静态函数 相对于锁住类的一个具体实例
* @throws InterruptedException
*/
synchronized void primary_method() throws InterruptedException {
for (int i = 0; i < 10; ++ i) {
System.out.println(getName() + "primary-method" + i);
Thread.sleep(10);
}
}
/**
* 2. 锁住静态函数,由于静态方法只有一份,相当于是加了类锁
* @throws InterruptedException
*/
synchronized static void static_method() throws InterruptedException {
for (int i = 0; i < 10; ++ i) {
System.out.println("static-method" + i);
Thread.sleep(10);
}
}
/**
* 3. 直接锁住this对象
* @throws InterruptedException
*/
void sync_obj() throws InterruptedException {
synchronized (this) {
for (int i = 0; i < 10; ++ i) {
System.out.println(getName()+"synchronized-object" + i);
Thread.sleep(10);
}
}
}
/**
* 4. 锁住类成员变量
* @throws InterruptedException
*/
void sync_obj2() throws InterruptedException {
synchronized (type) {
for (int i = 0; i < 10; ++ i) {
System.out.println(getName()+"synchronized-object2" + i);
Thread.sleep(10);
}
}
}
/**
* 5. 锁住class 类锁
* @throws InterruptedException
*/
void sync_class() throws InterruptedException {
synchronized (TService.class) {
for (int i = 0; i < 10; ++ i) {
System.out.println(getName()+"synchronized-class" + i);
Thread.sleep(10);
}
}
}
/**
* 6. 锁住静态成员变量 类锁
*/
void sych_obj3() throws InterruptedException {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+"synchronized-object3" + i);
Thread.sleep(10);
}
}
}
}
测试同步情况
class TService extends Thread {
TService(int type) {
this.type = type;
}
// 同步函数
@Override
public void run() {
try {
switch (type) {
case 0: primary_method();break;
case 1: static_method();break;
case 2: sync_obj();break;
case 3: sync_class();break;
case 4: sync_obj2();break;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Main {
public static void main(String[] args) {
TService t1 = new TService(0);
TService t2 = new TService(2);
t1.setName("t1: ");
t2.setName("t2: ");
t1.start();
t2.start();
}
}
通过TService的type属性,指定该线程要运行的同步代码,测试多种同步代码的互斥情况。
注意到这里,t1和t2是两个对象,所以如果执行同步代码中的(1,3,4)是不会产生互斥情况的,因为它们需要获得的对象锁是不同的。(如果要测试同步情况,需要设计一下,使得多个线程访问t1的同步代码)
但是(2,5,6)均需要获得类锁,而类锁是唯一的,所以它们的同步代码会产生互斥情况,等待一个彻底执行完成后才会执行下一个。
多线程单例模式
在多线程情况下,想要保持单例,要考虑一种线程不安全的情况:线程A发现对象是空,于是去创建,此时线程B也发现对象是空,也去创建。然后必然会有一个被另一个覆盖掉的问题。
因此,一般会使用双重校验实现线程安全的单例:
public class Singleton {
private volatile static Singleton singleton;
// 注意不要忘了私有的构造函数
private Singleton() {}
public static Singleton getSingleton() {
// 一重校验
if (singleton == null) {
// 上锁 因为是静态方法 所以加的是类锁
synchronized (Singleton.class) {
// 二重校验
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}