Java多线程 线程同步与死锁

 更新时间:2017年07月07日 09:47:27   投稿:lqh  
这篇文章主要介绍了 Java多线程 线程同步与死锁的相关资料,需要的朋友可以参考下

 Java多线程 线程同步与死锁

1.线程同步

多线程引发的安全问题

一个非常经典的案例,银行取钱的问题。假如你有一张银行卡,里面有5000块钱,然后你去银行取款2000块钱。正在你取钱的时候,取款机正要从你的5000余额中减去2000的时候,你的老婆正巧也在用银行卡对应的存折取钱,由于取款机还没有把你的2000块钱扣除,银行查到存折里的余额还剩5000块钱,准备减去2000。这时,有趣的事情发生了,你和你的老婆从同一个账户共取走了4000元,但是账户最后还剩下3000元。

使用代码模拟下取款过程:

public class ThreadTest {

  public static void main(String[] args) {
    // 创建一个账户,里面有存款5000元
    Account account = new Account(5000);
    // 模拟取钱过程
    GetMoney getMoney = new GetMoney(account);
    new Thread(getMoney, "你").start();
    new Thread(getMoney, "你老婆").start();
  }
}

class GetMoney implements Runnable {

  private Account account;

  public GetMoney(Account account) {
    super();
    this.account = account;
  }

  @Override
  public void run() {
    System.out.println(Thread.currentThread().getName() + "账户现在有"
        + account.getMoney() + "元");
    // 使效果更明显,休眠10ms
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    int money = account.getMoney() - 2000;
    account.setMoney(money);
    System.out.println(Thread.currentThread().getName() + "取了2000元,账户现在有"
        + account.getMoney() + "元");
  }

}

class Account {
  private int money;

  public Account(int money) {
    super();
    this.money = money;
  }

  public int getMoney() {
    return money;
  }

  public void setMoney(int money) {
    this.money = money;
  }
}

看下打印信息:

你账户现在有5000元
你老婆账户现在有5000元
你取了2000元,账户现在有3000元
你老婆取了2000元,账户现在有3000元

同步锁

从上面的案例可以看出,当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。
Java中可以使用synchronized关键字来取得一个对象的同步锁。

// Object可以为任何对象,表示当前线程取得该对象的锁。
synchronized (Object) {
}

修改一下上面的案例,在run方法中加入同步锁:

@Override
public void run() {
  synchronized (this) {
    System.out.println(Thread.currentThread().getName() + "账户现在有"
        + account.getMoney() + "元");
    // 使效果更明显,休眠10ms
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    int money = account.getMoney() - 2000;
    account.setMoney(money);
    System.out.println(Thread.currentThread().getName()
        + "取了2000元,账户现在有" + account.getMoney() + "元");
  }
}

看下打印信息:

你账户现在有5000元
你取了2000元,账户现在有3000元
你老婆账户现在有3000元
你老婆取了2000元,账户现在有1000元

当你取钱的时候,取款机锁定了你的账户,不允许其他人对账户进行操作,当你取完钱后,取款机释放了你的账户,你的老婆才可以取钱。

2.死锁

同步锁虽好,但也要科学使用,不然就会发生死锁,何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。

举个栗子,两个人面对面过独木桥,甲和乙都已经在桥上走了一段距离,即占用了桥的资源,甲如果想通过独木桥的话,乙必须退出桥面让出桥的资源,让甲通过,但是乙不服,为什么让我先退出去,我还想先过去呢,于是就僵持不下,导致谁也过不了桥,这就是死锁。

下面用一段简单的代码来模拟死锁:

public class DeadlockTest {

  public static void main(String[] args) {
    String str1 = new String("资源1");
    String str2 = new String("资源2");

    new Thread(new Lock(str1, str2), "线程1").start();
    new Thread(new Lock(str2, str1), "线程2").start();
  }
}

class Lock implements Runnable {

  private String str1;
  private String str2;

  public Lock(String str1, String str2) {
    super();
    this.str1 = str1;
    this.str2 = str2;
  }

  @Override
  public void run() {
    try {
      System.out.println(Thread.currentThread().getName() + "运行");
      synchronized (str1) {
        System.out.println(Thread.currentThread().getName() + "锁住"
            + str1);
        Thread.sleep(1000);
        synchronized (str2) {
          // 执行不到这里
          System.out.println(Thread.currentThread().getName()
              + "锁住" + str2);
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

看下打印信息:

线程1运行
线程2运行
线程1锁住资源1
线程2锁住资源2

第一个线程锁住了资源1(甲占有桥的一部分资源),第二个线程锁住了资源2(乙占有桥的一部分资源),线程1企图锁住资源2(甲让乙退出桥面,乙不从),进入阻塞,线程2企图锁住资源1(乙让甲退出桥面,甲不从),进入阻塞,死锁了。

死锁的产生是有规律可循的,只有同时满足以下四个条件,死锁才会产生。

1.互斥条件:一个资源每次只能被一个进程使用。独木桥每次只能通过一个人。

2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。乙不退出桥面,甲也不退出桥面。

3.不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺。甲不能强制乙退出桥面,乙也不能强制甲退出桥面。

4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。如果乙不退出桥面,甲不能通过,甲不退出桥面,乙不能通过。

知道了死锁产生的必要条件,在开发中就很容易避免死锁问题了。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

相关文章

  • 使用Java语言实现一个冒泡排序

    使用Java语言实现一个冒泡排序

    冒泡排序也是一种简单直观的排序算法,基本思想是多次遍历要排序的数组,每次比较相邻的元素,如果顺序不对就交换它们的位置,本篇文章将通过IDE使用 Java 语言实现一个冒泡排序,需要的朋友可以参考下
    2024-03-03
  • idea创建javaweb原生项目的实现示例

    idea创建javaweb原生项目的实现示例

    这篇文章主要介绍了idea创建javaweb原生项目的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • Java高性能本地缓存框架Caffeine的实现

    Java高性能本地缓存框架Caffeine的实现

    本文主要介绍了Java高性能本地缓存框架Caffeine的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • idea本地merge如何合并代码

    idea本地merge如何合并代码

    这篇文章主要介绍了idea本地merge如何合并代码问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • Java判断字符串是不是数字过程解析

    Java判断字符串是不是数字过程解析

    这篇文章主要介绍了Java判断字符串是不是数字过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • SpringBoot如何在普通类加载Spring容器

    SpringBoot如何在普通类加载Spring容器

    这篇文章主要介绍了SpringBoot如何在普通类加载Spring容器,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • Spring注解实现Bean自动装配示例详解

    Spring注解实现Bean自动装配示例详解

    这篇文章主要给大家介绍了关于Spring注解实现Bean自动装配的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • springboot项目启动,但是访问报404错误的问题

    springboot项目启动,但是访问报404错误的问题

    这篇文章主要介绍了springboot项目启动,但是访问报404错误的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • 关于Java中String类字符串的解析

    关于Java中String类字符串的解析

    这篇文章主要介绍有关Java中String类字符串的解析,在java中,和C语言一样,也有关于字符串的定义,并且有他自己特有的功能,下面就进入主题一起学习下面文章内容吧
    2021-10-10
  • 深度解析Java中volatile的内存语义实现以及运用场景

    深度解析Java中volatile的内存语义实现以及运用场景

    这篇文章主要介绍了Java中volatile的内存语义实现以及运用场景,通过JVM的机制来分析volatile关键字在线程编程中的作用,需要的朋友可以参考下
    2015-12-12

最新评论