JAVA多线程(二)竞态条件、死锁及同步机制
来源:互联网 发布:郎咸平小三 知乎 编辑:程序博客网 时间:2024/04/28 10:01
4 多线程的安全问题及解决方案
这一篇博客中,我会列出JAVA多线程编程过程中,容易出现的安全问题(竞态条件、死锁等),以及相应的解决方案,例如同步机制等。
究竟什么是线程安全?简单的说,如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。
4.1 竞态条件(racing condition)与多线程同步机制
4.1.1 竞态条件的概念
我们前面已经说过,线程之间共享堆空间,在编程的时候就要格外注意避免竞态条件。危险在于多个线程同时访问相同的资源并进行读写操作。当其中一个线程需要根据某个变量的状态来相应执行某个操作的之前,该变量很可能已经被其它线程修改。这里看个简单的例子:
class MyThread extends Thread{ public static int index; public void run(){ for(int i=0;i<10;i++){ System.out.println(getName()+":"+index++); } }}public class Test { public static void main(String[] args){ new MyThread().start(); new MyThread().start(); }}
运行结果是:
Thread-0:0
Thread-0:2
Thread-1:1
Thread-0:3
Thread-0:5
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9
Thread-0:10
Thread-0:11
Thread-1:4
Thread-1:12
Thread-1:13
Thread-1:14
Thread-1:15
Thread-1:16
Thread-1:17
Thread-1:18
Thread-1:19
在这个例子中,2个线程都会去访问静态变量index,他们获取系统时间片的时刻是不确定的,因此它们对index的访问和修改总是穿插进行的。
4.1.2多线程同步
我们需要想办法解决上面的竞态条件,当多个线程需要访问同一资源的时候,它们需要以某种顺序来确保该资源在某一时刻只能被一个线程使用,否则程序的运行结果将不可预料。也就是说,当线程A需要使用某个资源,如果该资源正被线程B使用,同步机制就会让线程A一直等待下去,直到线程B结束对该资源的使用,线程A才能使用。
要想实现同步操作,必须获得每一个线程对象的锁(lock)。获得锁可以保证同一时刻只有一个线程进入临界区(访问互斥资源的代码块),并且在这个锁被释放之前,其它线程都不能再进入这个临界区。如果还有其他线程想要获得该对象的锁,只能先进入等待队列。当拥有该对象锁的线程退出临界区,锁才会被释放,等待队列中优先级最高的线程才能获得该锁,从而进入共享代码区。
实现同步的方式有以下三种:
4.1.3 synchronized
其中synchronized块的结构是:
synchronized(syncObject){ //访问syncObject的代码}
接下来我们看看在使用同步代码块之后,上面提到的竞态条件有没有得到解决。
class MyThread extends Thread{ public static int index; public static Object obj=new Object(); public void run(){ synchronized(obj){ for(int i=0;i<10;i++){ System.out.println(getName()+":"+index++); } } }}public class Test { public static void main(String[] args){ new MyThread().start(); new MyThread().start(); }}
这次的运行结果是:
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-0:10
Thread-0:11
Thread-0:12
Thread-0:13
Thread-0:14
Thread-0:15
Thread-0:16
Thread-0:17
Thread-0:18
Thread-0:19
可以看到,在使用同步之后,线程会按照顺序访问静态变量,也就是说,同步机制通过“锁”解决了竞态条件。
了解同步机制之后,我们需要考虑的就是,到底把哪部分代码放到同步当中?粒度必须足够大,才能将必须视为原子的操作封装在此区域中;然而粒度如果过大,就会导致并发性能降低。因此,应该根据实际业务需求确定锁的粒度大小。
4.1.4 Lock vs synchronized
二者都是常用的同步方法,那么,它们有什么区别呢?
- 用法不同。synchronized既可以加在方法上,也可以加在特定代码块中,括号表示需要锁的对象。Lock需要显式地指定起始位置和终止位置。synchronized托管给JVM执行,Lock的锁定是通过代码实现的,有比synchronized更精准的线程语义;
- 性能不同。JDK5中新加入了一个Lock接口的实现类ReetrantLock. 它不仅拥有和synchronized相同的并发性和内存语义,还多了锁投票、定时锁、等候和中断锁等。竞争不是很激烈的时候,synchronized性能优于ReetrantLock;但是资源竞争激烈的时候,synchronized性能下降很快,ReetrantLock性能基本不变;
- synchronized自动解锁;Lock需要手动解锁,而且必须在finally块中释放,否则会引起死锁(4.2部分会讲到)
下面举一个使用Lock的例子:
import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReetrantLock;public class Test{ public static void main(String[] args) throws InterruptedException{ final Lock lock=new ReetrantLock(); lock.lock(); Thread t1=new Thread(new Runnable){ public void run(){ try{ lock.lockInterruptibly(); } catch(InterruptedException e){ System.out.println(" interrupted."); } } }); t1.start(); t1.interrupt(); Thread.sleep(!); }}
程序的运行结果是:
interrupted.
4.2 死锁(deadlock)
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们将一直互相等待而无法推进下去。也就是说,死锁会让你的程序挂起无法完成任务。
另一种与之相近的概念被称为“活锁”。活锁与死锁的主要区别是,活锁进程的状态可以改变(死锁不能改变),但是和死锁一样无法继续执行。活锁可以理解为在狭小的山道,两辆车相向而行,为了避让而同时往一个方向转头,结果谁都过不去。
究竟什么情况下会发生死锁呢? 死锁必须同时满足以下条件
既然死锁的发生必须同时满足这几个条件,我们只需要破坏其中之一就可以避免死锁。通常我们选择阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。
这里我们举个例子说明死锁和避免死锁(程序引用自How to avoid deadlock)。下面的程序会造成死锁,因为如果线程1在执行method1()的时候,获取String对象的锁,而线程2在执行method2()的时候获取Integer对象的锁,双方就会进入无休止的互相等待状态,因为双方都想获取对方已获取的对象锁。
按照上面我们说的,对程序做如下更改,就可以避免死锁的发生。当线程1获取Integer对象的锁的时候,线程2就会等待线程1释放锁之后才会执行,反之亦然。
说明
本人水平有限,不当之处希望各位高手指正。如有转载请注明出处,谢谢。
- JAVA多线程(二)竞态条件、死锁及同步机制
- JAVA多线程(二)竞态条件、死锁及同步机制
- 多线程(二)--同步及死锁
- [Java]多线程之同步及死锁
- java多线程同步死锁
- java 多线程 同步机制 总结(二)
- Java多线程同步死锁例程
- Java 多线程同步与死锁
- JAVA - 多线程 - 同步与死锁
- Java 多线程同步、死锁问题
- java多线程-同步和死锁
- 多线程的同步机制 - 死锁篇
- Java多线程技术研究(二)-线程同步,通信及ThreadLocal
- JAVA多线程机制之死锁
- java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)
- java多线程同步以及线程间通信详解&消费者生产者模式&死锁&Thread.join()(多线程编程之二)
- java多线程同步机制
- java多线程同步机制
- [SCU 4514] Simple dp (XJBLG法)
- ios之GCD学习笔记(1)
- STL快速入门
- TortoiseSVN中分支和合并实践
- Greendao的使用
- JAVA多线程(二)竞态条件、死锁及同步机制
- windows下免安装版的mysql的正确安装,折腾出来的方法
- 一种通过自动脚本抓取Android 手机log的方法
- ios之GCD学习笔记(2)
- Mysql 5.7 information_schema 的status和variables表deprecated
- 指尖上的电商---(6)solrconfig.xml配置详解
- ios之GCD学习笔记(3)
- 关于gsoap工具soapcpp2.exe无法通过头文件生成wsdl接口描述语言文件的原因
- 指尖上的电商---(7)Solr索引基本操作