在java的多线程领域,有一种机制叫作“重入”。都知道在java处理多线程的程序中,为了确保共享数据的准确性,避免失效,通常会对共享数据进行“加锁”。有一种加锁方式为对属性或者方法加上synchronized修饰符,表示对此属性或者方法的操作属于原子性。所谓原子性,就是在多线程都要访问此属性或者方法时,某个线程对属性或者方法的相关操作都执行完毕,产生结果之后,其他的线程才可以对此属性或者方法进行操作。
而synchronized之所以能做到对属性或者方法的原子性的操作,是因为synchronized对属性或者方法锁上了一道门,而只有持有特定锁的线程才可以访问,并且在访问期间,这道门是上锁的状态,其他的线程在没有得到这道门的特定锁情况下,是没有权限访问属性或者方法的。而想要得到锁,必须等拥有此锁的线程做完所有相关操作并释放锁后,才能得到锁,并对属性或方法做相关的操作。
用synchronized加锁有两种方式,一种是同步块,一种是同步方法。
同步快
把方法中的代码块用synchronized关键字“括住”。此种做法需要显示地声明“锁”,看下面代码
String strLock = "";
public void leave(){
synchronized (strLock){
System.out.println(Thread.currentThread().getName()+"我先来的,等我取完钱了");
try {
System.out.println(Thread.currentThread().getName()+"我也好困,睡会吧");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"睡醒了,要走了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面代码就是“同步快”的写法,而声明的strLock就是“同步快”持有的“锁”。“同步快”必须要这种格式显示地声明“持有锁”。
同步方法
用synchronized修饰的方法称为“同步方法”,此种方法不需要显示的声明锁,看下面代码
public synchronized void come(){
try {
System.out.println(Thread.currentThread().getName()+"我今夜来到ATM前");
System.out.println(Thread.currentThread().getName()+"由于时间太晚了,所以就睡了一觉");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"睡醒了,要取钱了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
“同步方法”隐式地声明了锁,而“锁”就是this,即“同步方法”所属的对象。
重入
所谓“重入”,即当持有“锁”的线程想要访问另一个被“相同锁”锁住的“同步方法”或者“同步块”时,不必等到“锁”释放即可访问。先看下面代码
package altsd.thread;
public class Bank {
String strLock = "";
public synchronized void come(){
try {
System.out.println(Thread.currentThread().getName()+"我今夜来到ATM前");
System.out.println(Thread.currentThread().getName()+"由于时间太晚了,所以就睡了一觉");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"睡醒了,要取钱了");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (strLock){
System.out.println(Thread.currentThread().getName()+"我正在取钱");
}
}
public void leave(){
synchronized (strLock){
System.out.println(Thread.currentThread().getName()+"我先来的,等我取完钱了");
try {
System.out.println(Thread.currentThread().getName()+"我也好困,睡会吧");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"睡醒了,要走了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这段代码中,有个“同步方法”come()被this锁“锁住”,“同步方法”中有个“同步快”,用的是另一个对象“strLock”;在leave()方法中有一个“同步块”被“strLock”锁住。另有两个线程
Thead1:访问bank的come()方法,代码如下
package altsd.thread;
public class Thread1 implements Runnable{
Bank bank = new Bank();
@Override
public void run() {
bank.come();
}
}
Thead2:访问bank的leave()方法,代码如下
package altsd.thread;
public class Thread2 implements Runnable {
Bank bank = new Bank();
@Override
public void run() {
bank.leave();
}
}
在主线程中运行这两个线程会发生什么呢?
package altsd.thread;
public class TestThread {
public static void main(String[] args) {
Thread t1 = new Thread(new Thread1());
t1.setName("小明");
Thread t2 = new Thread(new Thread2());
t2.setName("小红");
t1.start();
t2.start();
}
}
结果如下:
小明我今夜来到ATM前
小明由于时间太晚了,所以就睡了一觉
小红我先来的,等我取完钱了
小红我也好困,睡会吧
小明睡醒了,要取钱了
小红睡醒了,要走了
小明我正在取钱
首先“小明”访问了come()方法,持有了“this锁”,但途中睡了一会儿;此时“小红”在“小明”睡觉的时候访问leave()方法,持有了“strLock锁”,这时“小红”也睡了一会儿。当“小明”醒来,想访问“同步块”时,发现“strLock锁”还被小红拿着,没办法访问,只好等“小红”醒来再访问“同步块”。
如果没有让Bank对象中的“同步方法”和“同步块”都被“this锁”锁住,会发生什么呢?看下面代码
package altsd.thread;
public class Bank {
String strLock = "";
public synchronized void come(){
try {
System.out.println(Thread.currentThread().getName()+"我今夜来到ATM前");
System.out.println(Thread.currentThread().getName()+"由于时间太晚了,所以就睡了一觉");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"睡醒了,要取钱了");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){
System.out.println(Thread.currentThread().getName()+"我正在取钱");
}
}
public void leave(){
synchronized (this){
System.out.println(Thread.currentThread().getName()+"我先来的,等我取完钱了");
try {
System.out.println(Thread.currentThread().getName()+"我也好困,睡会吧");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"睡醒了,要走了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
这块代码中,“同步方法”和“同步块”都是用的“this锁”,其他程序不变,看结果:
小明我今夜来到ATM前
小红我先来的,等我取完钱了
小明由于时间太晚了,所以就睡了一觉
小红我也好困,睡会吧
小明睡醒了,要取钱了
小明我正在取钱
小红睡醒了,要走了
我们发现,“小明”并没有等“小红”醒来释放“this锁”就可以进入到“同步块”中,这就是“重入”。当多个同步代码持有的“锁”是同一个锁时,则持有此锁的线程可随意进入同步代码中,不论此锁是否被占用。