为了防止对共享受限资源的争夺,我们可以通过synchronized等方式来加锁,这个时候该线程就处于阻塞状态,设想这样一种情况,线程A等着线程B完成后才能执行,而线程B又等着线程C,而线程C又等着线程A。这三个任务之间相互循环等待,但是其实没有哪个任务能够执行,这种情况就发生了死锁。

有一个经典的哲学家就餐问题,可以更清晰的理解死锁问题。有N个哲学家围绕在一张圆形餐桌前,餐桌中间有一份面条,每个哲学家都只有一根筷子,放在他的左手边。(因为餐桌是圆形的,所以就是每两个哲学家之间有一根筷子),所以共计有N个哲学家,N个筷子, 哲学们家们有时候思考,思考时不需要获取其他共享资源;有时候吃面条,吃面条时需要两根筷子,哲学家需要先拿到他right手边的筷子,然后再去拿left手边的筷子。如果此时,left手边筷子不在桌子上(被边上的哲学家拿走了)。则哲学家就把right手边的筷子拿在手中等待。 (调用wait).等待left边的筷子被放下。如果哲学家吃完面条,则放下两根筷子 ,继续思考。

我们仅仅通过逻辑思考,就可以想到如果每个哲学家都拿到了他right手边的筷子,那么此时就发生了死锁,因为实际上桌子上,每个哲学家正好拿到了一根筷子,都在等待他left手边的筷子被放下,但是不会再有筷子被放下了.

代码demo:src\thread_runnable\DeadLockingDiningPhiosophers.java

 class Chopstick{
private boolean taken = false;
//拿起筷子
public synchronized void take() throws InterruptedException{
while (taken){
wait();
}
TimeUnit.MILLISECONDS.sleep(100);
taken = true;
}
//放下筷子
public synchronized void drop(){
taken = false;
notify();
}
} //end of "class Chopstick" class Philosopher implements Runnable{
private Chopstick left;
private Chopstick right;
private final int id;
private int eatTime;
private int thinkTime;
private Random rand = new Random(42); //the Answer to Life, the Universe and Everything is 42 public Philosopher(Chopstick left, Chopstick right, int id, int eatTime, int thinkTime) {
super();
this.left = left;
this.right = right;
this.id = id;
this.eatTime = eatTime;
this.thinkTime = thinkTime;
} //思考/或者吃饭的一段时间。
private void pause(int time) throws InterruptedException{
TimeUnit.MILLISECONDS.sleep(rand.nextInt(time*20));
} @Override
public void run() {
// TODO Auto-generated method stub
try {
while (!Thread.interrupted()){
System.out.println(this + "thinking");
pause(thinkTime);
//哲学家开始吃饭了
System.out.println(this + "grabbing right");
right.take();
System.out.println(this + "grabbing left");
left.take();
System.out.println(this + "grabbing eating");
pause(eatTime);
//吃完了。可以放下筷子了
right.drop();
left.drop(); }
} catch (InterruptedException e) {
// TODO: handle exception
System.out.println(this + " exiting via interrupt");
}
} @Override
public String toString() {
return "Philosopher id=" + id + "\t";
} }//end of "class Philosopher" public class DeadLockingDiningPhiosophers {
//哲学家和筷子的数量
private static final int N = 3;
private static final int eatTime = 20;
private static final int thinkTime = 3; public static void main(String[] args) throws Exception{
ExecutorService exec = Executors.newCachedThreadPool();
int ponder = 1;
Chopstick[] sticks = new Chopstick[N]; for (int i=0; i<N; i++){
sticks[i] = new Chopstick();
} for (int i=0; i<N; i++){
exec.execute(new Philosopher(sticks[i], sticks[(i+1)%N], i, eatTime, thinkTime));
} } }

代码分析: Chopstick对象有 take(拿起)和 drop(放下)两个动作,而哲学家对象呢,不管是吃饭过程,还是思考过程,都是模拟sleep随机的时间, 吃完饭之后,放下筷子,进行思考。不间断进行循环。

在demo中,3个线程,分别执行3个哲学家的任务, 同时也只有3个筷子。
按照我们的测试,有概率会发生死锁。为了增大死锁发生的概率,便于测试,我们将拿起筷子的时间延长了。(就是在take方法中sleep(100)).

进行测试,很快就发生了死锁。
其中一次的输出结果:

从控制台可以看出,程序一直在运行,但是哲学家们却不会再吃饭和思考了。
从输出信息看出,
1,0,2号哲学家依次拿起了right边的筷子 ,然后再准备拿起left边的筷子时,因为没有筷子了,而陷入了漫长的wait()中,这个时候,死锁发生了。程序死掉了。

对于哲学家就餐问题,我们可以想出一个避免死锁的方案,比如,对于其中的某一位哲学家,限定其先拿left边的筷子,再拿right边的筷子。(和其余的哲学家正好相反)。

死锁问题最难的地方是在于它是小概率性的,并且可能隐藏相当长的时间才会发生,并且每次发生死锁时,都是不可重现的。这在实际的项目中,会引起非常难以调试的bug。
而在实际项目中,必现的bug都容易解决,小概率的,不可重现的bug那才真的让人头疼。

程序避免死锁并不是件容易的事情,但是遵循以下原则则可以尽量避免死锁。
(1),使用锁的时间尽可能的短,考虑使用同步语句块来代替同步方法。
(2),尽量避免代码在同一个时刻需要多个锁。
(3),创建和使用一个大锁来代替若干把小锁,并且用这把锁用于互斥。

总结:
多线程问题算是java当中比较高级的内容了,当然因为能力有限,我的这几篇博客写的也非常肤浅。而实际编码中,是否应该使用多线程,也应该仔细斟酌。
使用多线程应该基于以下几个原因。
(1),处理交织在一起的很多任务。
(2),更高效的应用计算机资源。(比如多核cpu,等待I/O),
(3),更好的组织代码。
(4)更好的用户体验。(比如UI界面)

但是多线程也有一些缺点要注意。
(1),等待共享资源时,降低效率。
(2),上下文切换需要耗费额外的资源。
(3),多线程也会增加代码复杂度。
(4),可能会导致一些难以调试的bug。比如死锁。
(5),平台差异性。

如果线程问题过于复杂,java的多线程机制不能满足要求,那么应该使用类似Erlang这样的 专门面向并发的的函数性语言。

这几篇java多线程文章的demo代码下载地址 http://download.csdn.net/detail/yaowen369/9786452

---

作者: www.yaoxiaowen.com
github: https://github.com/yaowen369

java多线程(八)-死锁问题和java多线程总结的更多相关文章

  1. java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)

    本篇我们将讨论以下知识点: 1.线程同步问题的产生 什么是线程同步问题,我们先来看一段卖票系统的代码,然后再分析这个问题: package com.zejian.test; /** * @author ...

  2. 【多线程】死锁与Java栈跟踪工具

    今天面试有一道题,写一个死锁的程序,自己也是短路了,没写出来,回来写下. 死锁常见的情况是A线程持有a锁.阻塞于b锁,B线程持有b锁,阻塞于a锁,形成一个循环阻塞的状态. import java.ut ...

  3. java中多线程产生死锁的原因以及解决意见

    1.  java中导致死锁的原因 多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放,而该资源又被其他线程锁定,从而导致每一个线程都得等其它线程释放其锁定的资源,造成了所有线程都无法正常结 ...

  4. Java多线程学习---------超详细总结(java 多线程 同步 数据传递 )

    目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么 ...

  5. Java基础知识笔记(五:多线程的同步问题)

    编写多线程程序往往是为了提高资源的利用率,或者提高程序的运行效率,或者更好地监控程序的运行过程等.多线程同步处理的目的是为了让多个线程协调地并发工作.对多线程进行同步处理可以通过同步方法和同步语句块实 ...

  6. Java面试题整理一(侧重多线程并发)

    1..是否可以在static环境中访问非static变量? 答:static变量在Java中是属于类的,它在所有的实例中的值是一样的.当类被Java虚拟机载入的时候,会对static变量进行初始化.如 ...

  7. Java 多线程(六)之Java内存模型

    目录 1. 并发编程的两个问题 2 CPU 缓存模型 2.1 CPU 和 主存 2.2 CPU Cache 2.3 CPU如何通过 Cache 与 主内存交互 2.4 CPU 缓存一致性问题 3 Ja ...

  8. Java 容器源码分析之HashMap多线程并发问题分析

    并发问题的症状 多线程put后可能导致get死循环 从前我们的Java代码因为一些原因使用了HashMap这个东西,但是当时的程序是单线程的,一切都没有问题.后来,我们的程序性能有问题,所以需要变成多 ...

  9. 已看1.熟练的使用Java语言进行面向对象程序设计,有良好的编程习惯,熟悉常用的Java API,包括集合框架、多线程(并发编程)、I/O(NIO)、Socket、JDBC、XML、反射等。[泛型]\

    1.熟练的使用Java语言进行面向对象程序设计,有良好的编程习惯,熟悉常用的Java API,包括集合框架.多线程(并发编程).I/O(NIO).Socket.JDBC.XML.反射等.[泛型]\1* ...

  10. Java 学习(19):Java 多线程编程

    Java 多线程编程 Java 给多线程编程提供了内置的支持.一个多线程程序包含两个或多个能并发运行的部分.程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径. 多线程是多任务的一种特 ...

随机推荐

  1. 新版netkeeper开wifi无需路由器

    谈一谈netkeeper的运行原理及如何不用路由器开启wifi.(针对重庆地区,其它地区没研究过.日期:2017.11.29) 旧版: netkeeper将用户名加密为真正的用户名进行登录,登录以后n ...

  2. 如何在Raspberry Pi 3B中安装Windows 10 IoT Core

    Windows 10 IoT Core简介 Windows 10 IoT是微软专门为物联网生态打造的操作系统,Windows 10 IoT Core则是Windows 10 IoT 操作系统的核心版本 ...

  3. 并发与并行的区别 The differences between Concurrency and Parallel

    逻辑控制流 在程序加载到内存并执行的时候(进程),操作系统会通过让它和其他进程分时段占用CPU(CPU slices)让它产生自己独占CPU的假象(同时通过虚拟内存让它产生独占内存的假象).在CPU在 ...

  4. Java开发小技巧(四):配置文件敏感信息处理

    前言 不知道在上一篇文章中你有没有发现,jdbc.properties中的数据库密码配置是这样写的: jdbc.password=5EF28C5A9A0CE86C2D231A526ED5B388 其实 ...

  5. android 读取系统文件 wpa_supplicant

    1,须要权限 <uses-permission android:name="android.permission.ACCESS_SUPERUSER" /> 2,下载 R ...

  6. KMP算法具体解释

    这几天学习kmp算法,解决字符串的匹配问题.開始的时候都是用到BF算法,(BF(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配 ...

  7. Python笔记·第四章—— 细数Python中的数据类型以及他们的方法

    一.数据类型的种类及主要功能 1.数字类型 数字类型主要是用来计算,它分为整数类型int和浮点类型float 2.布尔类型 布尔类型主要是用于判断,它分为真True和False两种 3.字符串类型 字 ...

  8. Android项目实战(三十六):给背景加上阴影效果

    圆角背景大家应该经常用: 一个drawable资源文件  里面控制corner圆角 和solid填充色 <shape xmlns:android="http://schemas.and ...

  9. ligerUI---ligerForm中下拉框使用

    写在前面: 最近项目的前框框架用的是ligerUI,一开始我是拒绝的,因为貌似ligerUI很少有人用,我真的很想问我们team的斌哥哥为什么要用ligerUI来做前端框架?????(啊哈哈哈,用什么 ...

  10. C# 杂活

    1 下拉框 List<Model.Sys.AccountModel> AcModel = BLL.Sys.AccountBLL.Instance.GetModelList("Ro ...