synchronized可以用于修饰类的实例方法、静态方法和代码块。
1,实例方法
synchronized实例方法实际保护的是同一个对象的方法调用,确保同时只有一个线程执行。synchronized实例方法保护的是当前实例对象,即this,this对象有一个锁和一个等待队列,锁只能被一个线程持有,其他试图获得同样锁的线程需要等待。执行synchronized实例方法的过程大致如下:
1,尝试获得锁,如果能够获得锁,继续下一步,否则加入等待队列,阻塞并等待唤醒。
2,执行实例方法体代码。
3,释放锁,如果等待队列上有等待的线程,从中取一个并唤醒,如果有多个等待的线程,唤醒哪一个是不一定的,不保证公平性,
当前线程不能获得锁的时候,它会加入等待队列等待,线程的状态会变为BLOCKED。
多个线程是可以同时执行同一个synchronized实例方法的,只要它们访问的对象时不同的即可。
synchronized保护的是对象而非代码,只要访问的是同一个对象的synchronized方法,即使是不同的代码,也会被同步顺序访问。比如,访问一个实例的两个synchronized修饰的实例方法methodA和methodB,一个线程执行methodA,另一个线程执行methodB,它们是不能同时执行的,会被synchronized同步顺序执行。
synchronized方法不能防止非synchronized方法被同时执行,比如,访问一个实例的synchronized修饰的实例方法methodA和非synchronized修饰实例方法methodB,一个线程执行methodA,另一个线程执行methodB,它们可以同时执行的,一般在保护变量时,需要在所有访问该变量的方法上加上synchronized。
2,静态方法
synchronized修饰的静态方法保护的是类对象,即xxx.class。跟实例对象一样,每个类对象都有一个锁和一个等待队列。
synchronized静态方法和synchronized实例方法保护的是不同的对象,不同的线程,可以一个执行synchronized静态方法,另一个执行synchronized实例方法。
3,代码块
伪代码
//实例对象synchronized(this)
//类对象synchronized(test.class)
//任意对象Object lock = new Object();
synchronized(lock)
synchronized同步的对象可以是任意对象,任意对象都有一个锁和等待队列,任何对象都可以作为锁对象。
进一步理解synchronized
可重入性
synchronized是可重入的,对同一个执行线程,它在获得了锁之后,在调用其他需要同样锁的代码时,可以直接调用。
可重入是通过记录锁的持有线程和持有数量来实现的,当调用synchronized保护的代码时,检查对象是否已被锁,如果是,再检查是否被当前线程锁定,如果是,增加持有数量,如果不是被当前线程锁定,才加入等待队列,当释放锁时,减少持有数量,当数量为0时才释放整个锁。
内存可见性
synchronized可以保证原子操作外,还可以保证内存可见性,在释放锁时,所有写入都会写回内存,而获得锁后,都会从内存中读最新数据。
如果只是为了保证内存可见性,使用synchronized的成本有点高,可以使用轻量级的修饰符volatile。
死锁
使用synchronized或者其他锁,要注意死锁。
public class DeadLockDemo {
private static Object lockA = new Object();
private static Object lockB = new Object();
private static void startThreadA() {
Thread aThread = new Thread() {
@Override
public void run() {
synchronized (lockA) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
synchronized (lockB) {
}
}
}
};
aThread.start();
}
private static void startThreadB() {
Thread bThread = new Thread() {
@Override
public void run() {
synchronized (lockB) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
synchronized (lockA) {
}
}
}
};
bThread.start();
}
public static void main(String[] args) {
startThreadA();
startThreadB();
}
}
死锁出现的原因就是多个线程互相持有锁。
应该尽量避免在持有一个锁的同时去申请另一个锁,如果确实需要多个锁,所有代码都应该按照相同的顺序去申请锁。
如果需要更细粒度的锁控制,可以使用显式锁Lock。
java自带的jstack命令会报告发现的死锁。如何使用jstack分析线程状态 - Jessica程序猿 - 博客园www.cnblogs.com
参考:
《java编程的逻辑》