Java的并发编程:理解Java的重入锁,自旋锁和死锁

作者: Arvin Chen 分类: Java 来源: Break易站(www.breakyizhan.com)

这篇文章主要讲的是Java的三种锁:重入锁,自旋锁和死锁。对于最经常发生在多线程的就是Java的死锁。

我们先从定义来看一下这三个锁的概念:

  • 重入锁:也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。换一种说法:同一个线程再次进入同步代码时,可以使用自己已获取到的锁。作用是防止在同一线程中多次获取锁而导致死锁发生。
  • 自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,使用这种锁会造成busy-waiting。造成资源上面的浪费。
  • 死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

Java中的重入锁

重入锁就是在两个方法里面,都加了synchronized然后在a方法里面调用了b方法,线程在a方法的锁里面,还可以继续进入b方法的锁里面,这就是锁的重入,比如下面这个例子:


package com.breakyizhan.thread.t6;

public class Demo {
	
	
	public synchronized void a () {
		System.out.println("a");
                //a方法里面调用b方法
		b();
	}
	
	public synchronized void b() {
		System.out.println("b");
		
	}
	
	public static void main(String[] args) {
		Demo d1= new Demo();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				d1.a();
			}
		}).start();
	}

}

输出:

a
b

那么,如果上面的线程中用多个线程来访问的话,是不是会等待。就是说(同一个对象)线程1拿到锁的对象,调用a方法的时候,线程2调用b方法会不会等待。答案是会等待的。具体看下面的例子:


package com.breakyizhan.thread.t6;

public class Demo {
	
	
	public synchronized void a () {
		System.out.println("a");
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public synchronized void b() {
		System.out.println("b");
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
	}
	
	public static void main(String[] args) {
		Demo d1= new Demo();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				d1.a();
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				d1.b();
			}
		}).start();
	}

}

输出:

a
b

可以看到,a出现后等待了1秒之后,b才出现。那么,(不同对象)线程1拿到锁的对象,调用a方法的时候,线程2调用b方法会不会等待。答案是不会等待的。具体看下面的例子:


package com.breakyizhan.thread.t6;

public class Demo {
	
	
	public synchronized void a () {
		System.out.println("a");
//		b();
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public synchronized void b() {
		System.out.println("b");
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public static void main(String[] args) {
		Demo d1= new Demo();
		Demo d2= new Demo();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				d1.a();
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				d2.b();
			}
		}).start();
	}

}

输出:

a
b

可以看到a,b马上就出来了。这样就加深了对线程的理解了,只有不同对象的时候,才不会造成阻塞。

Java的自旋锁

自旋锁,顾名思义就是自旋等待,也就是说是空转CPU,在等待一个线程执行完毕之后,才会执行。具体我们可以看一下下面这个例子,我们要在所有线程执行完毕之后,打印一句,所有线程执行完毕啦,收工!下面的代码能实现么?


package com.breakyizhan.thread.t6;

import java.util.Random;

/**
 * 多个线程执行完毕之后,打印一句话,结束
 * @author worker
 *
 */
public class Demo2 {
	
	public static void main(String[] args) {
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();	

		System.out.println("所有线程执行完毕啦,收工!");
	}
}

输出:

Thread-0 线程执行...
Thread-1 线程执行...
Thread-2 线程执行...
Thread-3 线程执行...
所有线程执行完毕啦,收工!
Thread-4 线程执行...
Thread-3 线程执行完毕了...
Thread-2 线程执行完毕了...
Thread-0 线程执行完毕了...
Thread-1 线程执行完毕了...
Thread-4 线程执行完毕了...

我们可以看到,输出的结果不是我们想要的,那么,要怎么样才能输出正确的结果呢?就是要在其他5个线程执行完之前,要自旋,代码如下(实际工作中不建议用Thread.activeCount() != 1,因为会有别的线程,如GC等):

 
package com.breakyizhan.thread.t6;

import java.util.Random;

/**
 * 多个线程执行完毕之后,打印一句话,结束
 * @author worker
 *
 */
public class Demo2 {
	
	public static void main(String[] args) {
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + " 线程执行...");
				
				try {
					Thread.sleep(new Random().nextInt(2000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + " 线程执行完毕了...");
			}
		}).start();
		
		while(Thread.activeCount() != 1) {
			// 自旋
		}
		System.out.println("所有线程执行完毕啦,收工!");
	}

}

Java中的死锁

死锁是说当一个线程永远地获得一把锁,而其它线程想要去获得这把锁,确没办法获得的时候,就发生了死锁(两个或以上线程突然挤到一起了,阻塞了)。我们可以看一下下面这个例子,例子上面,console控制台没办法打印东西,处于永远等待的状态:


package com.breakyizhan.thread.t6;

public class Demo3 {
	
	private Object obj1 = new Object();
	private Object obj2 = new Object();
	
	
	public void a () {
		synchronized (obj1) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (obj2) {
				System.out.println("a");
			}
		}
	}
	
	public void b () {
		synchronized (obj2) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized (obj1) {
				System.out.println("b");
			}
		}
	}
	
	public static void main(String[] args) {
		
		Demo3 d = new Demo3();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				d.a();
			}
		}).start();
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				d.b();
			}
		}).start();
	}

}


  •   本文标题:Java的并发编程:理解Java的重入锁,自旋锁和死锁 - Break易站
    转载请保留页面地址:https://www.breakyizhan.com/java/6636.html

    发表笔记

    电子邮件地址不会被公开。 必填项已用*标注

    更多阅读