多个线程各自占有一些公共资源,并且互相等待其他线程占用的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题
死锁的代码示例如下所示:
package com.rgf;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持。
public class DeadLock {
public static void main(String[] args) {
Makeup G1 = new Makeup(0,"佳佳");
Makeup G2 = new Makeup(1,"蕾蕾");
G1.start();
G2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Makeup extends Thread{
//需要的资源只有一份,用static来保证只有一份。
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
int choice; //选择
String girlName; //使用化妆品的人
Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
//化妆
makeup();
}
//化妆,互相持有对方的锁,就是需要拿到对方的资源
private void makeup(){
if (choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girlName+"获得口红的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror){//一秒钟后想获得镜子
System.out.println(this.girlName+"获得镜子的锁");
}
}
}
else{
synchronized (mirror){//获得镜子的锁
System.out.println(this.girlName+"获得镜子的锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick){//一秒钟后想获得口红
System.out.println(this.girlName+"获得口红的锁");
}
}
}
}
}
运行界面如下所示:

解决办法如下所示:
可以直接把代码块的锁拿出来即可,即放到同步外面,不让两个人同时抱一把锁,即两个人抱两把锁。
代码示例如下所示:
package com.rgf;
//死锁:多个线程互相抱着对方需要的资源,然后形成僵持。
public class DeadLock {
public static void main(String[] args) {
Makeup G1 = new Makeup(0,"佳佳");
Makeup G2 = new Makeup(1,"蕾蕾");
G1.start();
G2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Makeup extends Thread{
//需要的资源只有一份,用static来保证只有一份。
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
int choice; //选择
String girlName; //使用化妆品的人
Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
//化妆
makeup();
}
//化妆,互相持有对方的锁,就是需要拿到对方的资源
private void makeup(){
if (choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girlName+"获得口红的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (mirror){//一秒钟后想获得镜子
System.out.println(this.girlName+"获得镜子的锁");
}
}
else{
synchronized (mirror){//获得镜子的锁
System.out.println(this.girlName+"获得镜子的锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (lipstick){//一秒钟后想获得口红
System.out.println(this.girlName+"获得口红的锁");
}
}
}
}
运行界面如下所示:

示例二:
package com.ypl.ticket;
/**
* 演示线程的死锁问题
* 死锁的理解:
* 1.不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
* 2.说明:
* (1)出现思索后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
* (2)我们使用同步时,要避免出现死锁。
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1=new StringBuffer();
StringBuffer s2=new StringBuffer();
new Thread() {
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
我们运行之后出现如下所示:

此时程序既不会运行也不会死亡,处于阻塞状态。
示例三:
package com.ypl.ticket;
//死锁的演示
class A{
public synchronized void foo(B b){ //同步监视器:A类的对象:a
System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了A实例的foo方法");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用B实例的last方法");
b.last();
}
public synchronized void last(){ //同步监视器:A类的对象:a
System.out.println("进入了A类的last方法内部");
}
}
class B{
public synchronized void bar(A a){//同步监视器:B类的对象:b
System.out.println("当前线程名:"+Thread.currentThread().getName()+"进入了B实例的bar方法");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程名:"+Thread.currentThread().getName()+"企图调用A实例的last方法");
a.last();
}
public synchronized void last(){ //同步监视器:B类的对象:b
System.out.println("进入了B类的last方法内部");
}
}
public class DeadLock implements Runnable{
A a=new A();
B b=new B();
public void init(){
Thread.currentThread().setName("主线程");
//调用a对象的foo方法
a.foo(b);
System.out.println("进入了主线程之后");
}
@Override
public void run() {
Thread.currentThread().setName("副线程");
//调用b对象的bar方法
b.bar(a);
System.out.println("进入了副线程之后");
}
public static void main(String[] args) {
DeadLock d1 = new DeadLock();
new Thread(d1).start();
//调用run方法:里面再调类B的方法bar,B类的bar方法再调A类的last方法,A类的last方法再调B类的last方法
d1.init();
//调用init方法:里面调用a对象的foo方法,a对象的foo方法调B类的last方法
}
}
运行之后如下所示:

死锁避免方法:
产生死锁的四个必要条件:
1.互斥条件:一个资源每次只能被一个进程使用
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3.不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生。
解决方法:
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
Lock(锁)
从JDK5.0开始,Java提供了更强大的线程同步机制----通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。synchronized是隐式的定义。
java.util.concurrent,locks.Lock接口是控制多个线程对共享资源进行访问的工具,是并发编程的领域。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock (可重入锁)类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
package com.rgf;
import java.util.concurrent.locks.ReentrantLock;
//测试Lock锁
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
TestLock2 testLock3 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable {
int ticketNums = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();//加锁
if (ticketNums > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
} else {
break;
}
}
finally{
//解锁
lock.unlock();
//如果同步代码有异常,要将unlock()写入finally语句块
}
}
}
}
运行界面如下所示:

示例二:
package com.ypl.ticket;
import java.util.concurrent.locks.ReentrantLock;
/**
* 解决线程安全问题的方式三:Lock锁---JDK5.0新增
*/
class Window implements Runnable{
private int ticket=100;
//1.实例化ReentrantLock
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.调用锁定方法:lock()
lock.lock();
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket);
ticket--;
}else {
break;
}
}finally {
//3.调用解锁方法:unlock().
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
运行之后如下所示:
synchronized与Lock的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
本文详细介绍了Java中死锁的概念、产生原因和一个典型的死锁示例,以及如何通过调整代码顺序避免死锁。同时,展示了使用Lock锁(如ReentrantLock)来替代synchronized关键字以提高并发性能和灵活性,并给出了Lock锁的使用示例,强调了Lock锁在处理线程安全问题时的优势和注意事项。
1341

被折叠的 条评论
为什么被折叠?



