在学习Java多线程相关的知识时,通常都会涉及锁这个概念,常见的synchronized、Lock均为可重入锁。为了更好的理解可重入锁,需要先理解一下几个问题:
1、谁持有了锁?
2、锁的对象是谁?
3、可重入锁的可重入是什么意思?
一、synchronized关键字
synchronized关键字可有效解决线程安全问题,其内部原理也是通过对所作用的代码块加锁,synchronized关键字可以作用在:
1、作用于代码块上,=》锁的对象是括号内的对象
2、作用于成员函数上,=》锁的对象是调用该方法的对象(对象锁)
3、作用与静态方法上,=》锁的对象是该类的字节码文件对象(类锁)
public class SynDemo {
public static void main(String[] args) {
Demo obj = new Demo();
//线程1--同步代码块
new Thread(new Runnable() {
@Override
public void run() {
//synchronized代码块,锁的对象为后面括号内的对象参数
synchronized (Demo.class){ //obj
for(int i=0;i<100;i++) {
System.out.println("i="+i);
}
}
}
}).start();
//线程2--对象的成员方法
new Thread(new Runnable() {
@Override
public void run() {
obj.method();
}
}).start();
//线程3--类的静态方法
new Thread(new Runnable() {
@Override
public void run() {
Demo.staticMethod();
}
}).start();
}
}
class Demo{
//synchronized作用在成员方法上,锁的对象就是调用该成员方法的对象实例
public synchronized void method() {
for(int i=0;i<100;i++) {
System.out.println("......j"+i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//synchronized作用在静态方法上,锁的对象就是对应类的字节码文件对象 Demo.class
public static synchronized void staticMethod() {
for(int i=0;i<100;i++) {
System.out.println("----------------z"+i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上述代码运行后,线程1持有的锁为:Demo.class对象
线程2持有的锁为obj对象;
线程3持有的锁为Demo.class对象
所以,运行后,线程1与线程3不会交替执行,线程2与线程1、线程3会交替执行
i=17
......j0
i=18
.
.
.
i=97
i=98
i=99
----------------z0
......j1
----------------z1
----------------z2
......j2
----------------z3
二、可重入锁
结合例子,可以回答上面的问题:
谁持有锁==>线程持有锁
锁的对象是谁==>锁的对象是对象或者类
什么是可重入锁?==>可以被同一个线程多次进入(多次持有)
public class SynDemo {
public static void main(String[] args) {
SynDemo obj= new SynDemo();
obj.m1();
}
public synchronized void m1() {
//主线程持有了锁,锁的对象是obj,在m1()方法内,执行m2()方法,主线程再次获得该锁,即重入
m2();
System.out.println("m1....");
}
public void m2() {
synchronized (this) {
System.out.println("m2...");
}
}
}
主线程两次获得了同一个锁,即重入,synchronized锁即为可重入锁,随着其作用代码块的入口开始获得锁,在执行完后释放锁,获得锁的次数与释放锁的次数是对应的;
m2...
m1....
三、可重入锁的多次重入,不释放,释放多次
synchronized锁的持有与释放时完整的统一的,进入多少次便会释放多少次,然而对于ReentrantLock这种需要手动释放的锁就不一定的保证获得次数与释放次数的对等了,那么分析一下这两种情况:
1、线程两次获得锁,却只释放了一次
//可重入锁的释放问题
import java.util.concurrent.locks.ReentrantLock;
public class ThreadReleaseMany {
public static void main(String[] args) throws Exception
{
ReentrantLock lock = new ReentrantLock();
Thread t = new Thread(new Runnable()
{
@Override
public void run()
{
lock.lock();
System.out.println("子线程执行........");
lock.unlock();
}
});
//主线程去获得了锁,然后获得了两次,随后t.start()住现场开启了t线程,然后主线程和t线程可以同时执行
//但是,t线程执行时尝试去获取锁lock,发现一直被主线程持有,所以一直等待
//知道主线程lock.unlock()释放了一次,但是这时候t线程仍不能获得该锁,主线程也还持有锁的,一直未释放
//----线程持有锁,锁被主线程锁持有,其他线程就不能获取锁
lock.lock();
lock.lock();
t.start();
Thread.sleep(100);
System.out.println("主线程执行........");
lock.unlock(); //主线程释放锁,仅仅释放了一次
}
}
主线程一直持有锁不释放,子线程一直不能获取该锁,所以程序一直等待下去(ps: 从死锁的概念上说,这种情况应该不能叫死锁)
2. 锁被多次释放
package com.qiuyang.thread;
//可重入锁的释放问题
import java.util.concurrent.locks.ReentrantLock;
public class ThreadReleaseMany {
public static void main(String[] args) throws Exception
{
ReentrantLock lock = new ReentrantLock();
Thread t = new Thread(new Runnable()
{
@Override
public void run()
{
lock.lock();
System.out.println("子线程执行........");
lock.unlock();
}
});
//主线程去获得了锁,然后获得了两次,随后t.start()住现场开启了t线程,然后主线程和t线程可以同时执行
//但是,t线程执行时尝试去获取锁lock,发现一直被主线程持有,所以一直等待
//知道主线程lock.unlock()释放了一次,但是这时候t线程仍不能获得该锁,主线程也还持有锁的,一直未释放
//----线程持有锁,锁被主线程锁持有,其他线程就不能获取锁
lock.lock();
lock.lock();
t.start();
Thread.sleep(100);
System.out.println("主线程执行........");
lock.unlock(); //主线程释放锁,仅仅释放了一次
lock.unlock();
lock.unlock(); //主线程释放了3次锁
}
}
执行结果:
Exception in thread "main" 主线程执行........
子线程执行........
java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(Unknown Source)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(Unknown Source)
at java.util.concurrent.locks.ReentrantLock.unlock(Unknown Source)
at com.qiuyang.thread.ThreadReleaseMany.main(ThreadReleaseMany.java:36)
可以看出,在主线程第二次释放锁的时候,子线程就获取了锁,并开始执行子线程的打印代码,执行完后释放锁,主线程再次释放锁,则出现异常,故一个锁不能被多次释放。
该过程为:主线程执行之后,打印语句执行,释放锁两次之后,子线程t获得锁并执行,执行打印语句,主线程第3次执行unlock(),抛出异常;
总结:对于可重入锁,每个线程都可以多次去获得该锁(同一个线程多次获得该锁),在线程获得锁后,可再去获得此锁多次,但是释放的时候,一定要释放同样的次数,否则,释放少于获得次数,将导致别的等待该锁的线程长期等待;如果线程多次释放锁(大于获得次数),或者释放别的线程的锁,将会出现异常。