简介:
Java中的synchronized关键字是一种同步机制,用于控制多个线程对共享资源的访问。
原理:
在Java锁有一个内部锁 Intrinsic Lock,也称为监视器锁或管程锁,每个Java对象都有一个关联的监视器锁,隐式锁通常是由编程语言、运行时库或者虚拟机自动管理的,不需要程序员手动调用锁的获取和释放方法,使用较为简便,并且 隐式锁通常会在不需要的时候自动释放,从而减少了由于忘记释放锁而导致的死锁等问题的风险。通过synchronized
关键字来获取和释放对象的监视器锁。
使用:
实例方法加锁:
使用synchronized修饰方法时,它将锁定整个方法,即使方法中有多个代码块,也只有一个线程可以进入该方法。
public class AccountingSync implements Runnable {//共享资源(临界资源)static int i=0;/*** synchronized 修饰实例方法*/public synchronized void increase(){i++;}@Overridepublic void run() {for(int j=0;j<1000000;j++){increase();}}public static void main(String[] args) throws InterruptedException {AccountingSync instance = new AccountingSync();Thread t1=new Thread(instance);Thread t2=new Thread(instance);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}
}
运行上述代码,打印出了我们预期的结果 2000000,由于 ++ 操作是非线程安全的,所以这一结果表明我们加锁是成功的。 两个线程是通过同一个实例 instance 运行的,所以他们通过这个实例的同一把锁实现了线程的并发安全,而下面的代码就将无法得到预期的结果:
public class AccountingSyncBad implements Runnable {static int i=0;public synchronized void increase(){i++;}@Overridepublic void run() {for(int j=0;j<1000000;j++){increase();}}public static void main(String[] args) throws InterruptedException {//new新实例Thread t1 = new Thread(new AccountingSyncBad());//new新实例Thread t2 = new Thread(new AccountingSyncBad());t1.start();t2.start();//join含义:当前线程A等待thread线程终止之后才能从thread.join()返回t1.join();t2.join();System.out.println(i);}
}
运行结果打印出了 1452317,这是因为两个线程分别运行在两个 new 出来的不同的实例中,这意味着他们有着两个不同的实例对象锁,因此他们各自对 increase 方法加锁是无法实现锁的作用的。
static 方法加锁:
与实例方法不同,static 方法是无法获取到实例对象的 this 引用的,因此对 static 方法加锁,锁定的目标就是 class 对象,所有使用该类的线程都将获取到同一把锁。
public class AccountingSyncBad implements Runnable {static int i=0;public synchronized void increase(){i++;}@Overridepublic void run() {for(int j=0;j<1000000;j++){increase();}}public static void main(String[] args) throws InterruptedException {//new新实例Thread t1 = new Thread(new AccountingSyncBad());//new新实例Thread t2 = new Thread(new AccountingSyncBad());t1.start();t2.start();//join含义:当前线程A等待thread线程终止之后才能从thread.join()返回t1.join();t2.join();System.out.println(i);}
}
此时运行上述代码可以得到预期的加锁效果,虽然我们通过 new 传入不同的实例,但是因为 static 方法上的 synchronized 关键字是通过对 class 对象加锁,两个线程仍然是在竞争同一把锁。
synchronized 同步代码块
编写 synchronized 同步代码块是最灵活的一种加锁方式了,他不仅可以实现上述两种加锁方式的功能,还可以实现更加精细化的加锁控制。 在某些情况下,我们编写的方法体可能比较大,也可能存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了。
public class AccountingSync implements Runnable{static AccountingSync instance = new AccountingSync();static int i=0;@Overridepublic void run() {//省略其他耗时操作....//使用同步代码块对变量i进行同步操作,锁对象为instancesynchronized(instance){for(int j=0;j<1000000;j++){i++;}}}public static void main(String[] args) throws InterruptedException {Thread t1=new Thread(instance);Thread t2=new Thread(instance);t1.start();t2.start();t1.join();t2.join();System.out.println(i);}
}
上述代码我们通过对代码执行的一部分加锁,实现了上述对实例方法加锁的相同功能,我们通过对类的 static 成员 instance 加锁,实现并发安全性。 常用的使用方法有对 this 引用加锁和对 class 对象加锁:
//this,当前实例对象锁
synchronized(this){for(int j=0;j<1000000;j++){i++;}
}//class对象锁
synchronized(AccountingSync.class){for(int j=0;j<1000000;j++){i++;}
}