java多线程—synchronized关键字
一.临界区
在讲解synchronized关键字之前,需要了解什么是临界区。临界区是指访问共用资源的程序片段,如:相同的内存(变量,数组或对象),系统(数据库,Web服务等)或文件,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,其他线程或进程必须等待,有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用。如下面的代码,如果多个线程同时执行,则可能不是我们想要的效果。
public class Counter{
private int count = 0;
public void add(int value){
this.count += value;
}
}
如果有多个线程同时执行同一个Counter类实例的add方法,那么每个线程都会从内存中读取count值0。再与传入的值相加,并将结果写回内存。我们会发现,最后count的值为最后一个执行add方法的结果,而不是多次执行add方法的结果的累加。这个add方法里的代码,就是一个临界区。
二.线程同步
为了防止在临界区发生竞争情况,必须使用线程同步,也就是当一个线程在执行临界区的代码时,在允许其他任何线程同时执行。关键字synchronized就可以达到这以目的。上面的代码可以改为:
public class Counter{
private int count = 0;
public synchronized void add(int value){
this.count += value;
}
}
这样,无论多少线程同时去执行add方法,在一个线程未执行完之前,其他线程都无法执行,从而保证了线程的安全。
除了对函数的同步,我们也可以对代码块进行同步,如:
public class Counter {
private int count = 0;
public void add(int value){
synchronized(this){
this.count += value;
}
}
}
这与上面同步方法的效果一样。
三.死锁
死锁是指两个或多个线程被阻塞等待获取死锁中的某些其他线程所持有的锁。当多个线程同时需要相同的锁,但是以不同的顺序获取它们时,可能会发生死锁。下面我们展示一段简单的死锁代码:
public class ThreadDeath {
public static void main(String[] args) throws InterruptedException {
Object obj1 = new Object();
Object obj2 = new Object();
Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
Thread t2 = new Thread(new SyncThread(obj2, obj1), "t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class SyncThread implements Runnable{
private Object obj1;
private Object obj2;
public SyncThread(Object o1, Object o2){
this.obj1=o1;
this.obj2=o2;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
synchronized (obj1) {
System.out.println("线程:" + name + " 已锁定" + obj1.toString());
sleep();
synchronized (obj2) {
System.out.println("线程:" + name + " 已锁定 " + obj2.toString());
sleep();
}
System.out.println("线程:" + name + " 已释放 " + obj2.toString());
}
System.out.println("线程:" + name + " 已释放 " + obj1.toString());
}
private void sleep() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
线程:t1 已锁定java.lang.Object@1cdad4b
线程:t2 已锁定java.lang.Object@e35fcc
在main方法中,我有运行了两个SyncThread的线程,每个线程之间都有一个共享资源。每个线程都能够获取第一个对象的锁定,但是当它试图获取第二个对象的锁定时,它会进入等待状态,因为这个对象已被另一个线程锁定,这样就形成了死锁。
四.防止死锁
避免嵌套锁:这是死锁的最常见原因,如果已经存在死锁,要避免锁定另一个资源。如果只使用一个对象锁,几乎不可能出现死锁情况。
仅限锁定所需内容:我们应该只对必须的代码上加锁。
避免无限期等待:如果两个线程正在等待彼此无限期地使用线程连接,则可能会出现死锁。如果线程必须等待另一个线程完成,那么最好在想要等待线程完成的最长时间内使用join。