对于熟悉java多线程并发的人来说,java锁机制是不可逃避的话题。那么什么是java锁机制,以及什么时候使用java的锁呢?
让我们看看以下几种场景吧!
一、同步锁
案例:假设现在我们现在有很多人去商店买衣服,因为我们每个人买衣服是可能同时执行的,不可能商店要求同一时刻只能有一个顾客买衣服,因此我们需要为每一个顾客买衣服设置一个线程;假设买衣服的整个过程有“挑选”、“试衣服”、“结账”三个过程,那么初步代码实现是这样的:
public class Shopping {
public static void main(String[] args) {
Task task = new Task();
new Thread(task).start();
new Thread(task).start();
}
}
class Task implements Runnable{
public void run(){
System.out.println("顾客正在挑选衣服...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客正在试衣服...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("顾客正在结账...");
}
}
执行结果:
顾客正在挑选衣服...
顾客正在挑选衣服...
顾客正在试衣服...
顾客正在试衣服...
顾客正在结账...
顾客正在结账...
这样的结果大家是不是觉得有点别扭呢?我们挑选衣服、结账还可以一起,那没问题,但是试衣服就有点尴尬了,万一大家性别不同呢?。。。所以这时候要是有一把锁在我们换衣服的时候能够把试衣间锁上,一个人试完了再到下一个那样就不会乱套了,那么怎么做呢?其实java提供了一个关键字synchronized来修饰我们多个线程需要同步执行的代码段或方法,所谓同步就是一个一个排队干的意思,具体语法如下:
synchronized(监视器){
需要执行的代码
}
//监视器:java里面的任意一个对象,但是要求该对象对于需要同步执行的线程来说是同一个对象即可。
那么以上程序就可以这么干了:
public class Shopping {
public static void main(String[] args) {
Task task = new Task();
new Thread(task).start();
new Thread(task).start();
}
}
class Task implements Runnable{
public void run(){
......................................
synchronized(this){
System.out.println("顾客正在试衣服...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
............................
}
}
结果:
顾客正在挑选衣服...
顾客正在挑选衣服...
顾客正在试衣服...
顾客正在结账...
顾客正在试衣服...
顾客正在结账...
是不是发现试衣服这件事错开了呢?那么这就是java里面的同步锁机制:当一个线程执行synchronized修饰的代码段时,会给监视器上一把锁,当另一个线程执行同样的被synchronized修饰的任务时,该线程就需要关注该监视器有没有上锁,没有才能执行,否则就阻塞。
二、互斥锁
案例:假设有这样一个场景,对于一家人来说每个人都可以同时吃饭同时看电视,但是有一个家规就是吃饭的时候谁也不准看电视,那么怎样实现代码模拟呢?看下面:
public class Shopping {
public static void main(String[] args) {
Task task = new Task();
new Thread(task).start();
new Thread(task).start();
}
}
class Task implements Runnable{
public void run(){
synchronized(this){
System.out.println("吃饭...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized(this){
System.out.println("看电视...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果:
吃饭...
看电视...
吃饭...
看电视...
执行后会发现即使是不同的线程吃饭和看电视也是交替而不是同时执行的。这就是典型的互斥锁:当两个synchronized锁的对象(监视器)是同一个,修饰的代码片段不同时,那么这些代码片段所代表的任务是互斥的。
三、死锁
概念:当每个线程都持有自己的锁,但是都在请求对方释放锁时,就会造成死锁现象。
案例:
public class Test {
public static void main(String[] args) {
Task task = new Task();
Thread t1 = new Thread(){
public void run(){
task.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
task.methodB();
}
};
t1.start();
t2.start();
}
}
class Task{
private Object a = new Object();
private Object b = new Object();
public void run(){
}
public void methodA(){
System.out.println("开始执行A方法");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(a){
System.out.println("对A方法上锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始调用B方法");
methodB();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A方法执行完毕");
}
public void methodB(){
System.out.println("开始执行B方法");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(b){
System.out.println("对B方法上锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始调用A方法");
methodA();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("B方法执行完毕");
}
}
结果:
开始执行A方法
开始执行B方法
对A方法上锁
对B方法上锁
开始调用B方法
开始执行B方法
开始调用A方法
开始执行A方法
执行到这里后两个线程t1和t2会进入僵持状态,由于双方都是在持有自己锁的同时请求对方释放锁,因此这就形成了典型的死锁现象:这里是因为滥用锁对象造成的,大家可以将两个锁对象(监视器)换成同一个对象,程序就会向下继续运行,但是该程序继续向下运行并没有什么意义,这里只是为了说明死锁形成的简单场景。
以上就是java里面典型的各种锁,有兴趣可以研究一下。