小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
Java中锁的可重入性
在JDK5中增加了Lock锁接口,有ReentrantLock实现类,ReentrantLock锁称为可重入锁, 它功能比synchronized多。
锁的可重入性
锁的可重入是指,当一个线程获得一个对象锁后,再次请求该对象锁时是可以获得该对象的锁的。
机制:每个锁都关联一个请求计数器和一个占有他的线程,当请求计数器为0时,这个锁可以被认为是unhled的,当一个线程请求一个unheld的锁时,JVM记录锁的拥有者,并把锁的请求计数加1,如果同一个线程再次请求这个锁时,请求计数器就会增加,当该线程退出syncronized块时,计数器减1,当计数器为0时,锁被释放。
package com.wkcto.lock.reentrant;
/**
* 演示锁的可重入性
*/
public class Test01 {
public synchronized void sm1(){
System.out.println("同步方法1");
//线程执行sm1()方法,默认this作为锁对象,在sm1()方法中调用了sm2()方法,注意当前线程还是持有this锁对象的
//sm2()同步方法默认的锁对象也是this对象, 要执行sm2()必须先获得this锁对象,当前this对象被当前线程持有,可以 再次获得this对象, 这就是锁的可重入性. 假设锁不可重入的话,可能会造成死锁
sm2();
}
private synchronized void sm2() {
System.out.println("同步方法2");
sm3();
}
private synchronized void sm3() {
System.out.println("同步方法3");
}
public static void main(String[] args) {
Test01 obj = new Test01();
new Thread(new Runnable() {
@Override
public void run() {
obj.sm1();
}
}).start();
}
}
Java ReentrantLock使用
调用lock()方法获得锁, 调用unlock()释放锁。
package com.wkcto.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock锁的基本使用
*/
public class Test02 {
//定义显示锁
static Lock lock = new ReentrantLock();
//定义方法
public static void sm(){
//先获得锁
lock.lock();
//for循环就是同步代码块
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " -- " + i);
}
//释放锁
lock.unlock();
}
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
sm();
}
};
//启动三个线程
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
package com.wkcto.lock.reentrant;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用Lock锁同步不同方法中的同步代码块
*/
public class Test03 {
static Lock lock = new ReentrantLock(); //定义锁对象
public static void sm1(){
//经常在try代码块中获得Lock锁, 在finally子句中释放锁
try {
lock.lock(); //获得锁
System.out.println(Thread.currentThread().getName() + "-- method 1 -- " + System.currentTimeMillis() );
Thread.sleep(new Random().nextInt(1000));
System.out.println(Thread.currentThread().getName() + "-- method 1 -- " + System.currentTimeMillis() );
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //释放锁
}
}
public static void sm2(){
try {
lock.lock(); //获得锁
System.out.println(Thread.currentThread().getName() + "-- method 22 -- " + System.currentTimeMillis() );
Thread.sleep(new Random().nextInt(1000));
System.out.println(Thread.currentThread().getName() + "-- method 22 -- " + System.currentTimeMillis() );
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //释放锁
}
}
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
sm1();
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
sm2();
}
};
new Thread(r1).start();
new Thread(r1).start();
new Thread(r1).start();
new Thread(r2).start();
new Thread(r2).start();
new Thread(r2).start();
}
}
如果没有Java锁的可重入性,当一个线程获取LoggingWidget的doSomething()代码块的锁后,这个线程已经拿到了LoggingWidget的锁,当调用父类中的doSomething()方法的时,JVM会认为这个线程已经获取了LoggingWidget的锁,而不能再次获取,从而无法调用Widget的doSomething()方法,从而照成死锁。
java线程是基于“每线程(per-thread)”,而不是基于“每调用的(per-invocation)”的,也就是说java为每个线程分配一个锁,而不是为每次调用分配一个锁。