您的位置:首页 > 编程语言 > Java开发

Java并发编程札记-(一)基础-05线程安全问题

2017-11-20 21:10 615 查看
在多线程编程中,可能会出现多个线程访问一个资源的情况,资源可以是同一内存区(变量,数组,或对象)、系统(数据库,web services等)或文件等等。如果不对这样的访问做控制,就可能出现不可预知的结果。这就是线程安全问题,常见的情况是“丢失修改”、“不可重复读”、“读‘脏’数据”等等。

目录

线程安全问题

线程安全的实现

线程安全问题

上面简单介绍了什么是线程安全问题,下面具体说下什么是“丢失修改”,其他的问题有兴趣可以自己去了解。

丢失修改

两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的修改被丢失。

拿火车票订票系统举例:

一号窗口读出某班次的火车票余票A,设A=1;

二号窗口读出同一班次的火车票余票B,当然也为1;

一号窗口判断出余票A=1>0,卖出一张火车票,修改余票A←A-1,A为0,把A写回数据库;

二号窗口判断出余票B=1>0,也卖出一张火车票,修改余票B←B-1,B为-1;

余票只有一张,但最后卖出了两张火车票。在程序中,没有对两个窗口对余票的访问做控制,所以造成了这个错误。

例1:火车票订票系统-线程不安全版

public class SellTickets {

public static void main(String[] args) {
TicketsWindow tw = new TicketsWindow();
Thread t1 = new Thread(tw, "一号窗口");
Thread t2 = new Thread(tw, "二号窗口");
t1.start();
t2.start();
}
}

class TicketsWindow implements Runnable {
private int tickets = 1;

@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "还剩余票:" + tickets + "张");
tickets--;
System.out.println(Thread.currentThread().getName() + "卖出一张火车票,还剩" + tickets + "张");
} else {
System.out.println(Thread.currentThread().getName() + "余票不足,暂停出售!");
try {
Thread.sleep(1000 * 60 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}


运行结果为

一号窗口还剩余票:1张
二号窗口还剩余票:1张
一号窗口卖出一张火车票,还剩0张
二号窗口卖出一张火车票,还剩-1张
一号窗口余票不足,暂停出售!
二号窗口余票不足,暂停出售!


这明显不是我们想要的结果。

线程安全问题解决方法

上面的问题归根结底是由于两个线程访问相同的资源造成的。对于并发编程,需要采取措施防止两个线程来访问相同的资源。

一种措施是当资源被一个线程访问时,为其加锁。第一个访问资源的线程必须锁定该资源,是其他任务在资源被解锁前不能访问该资源。

基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案。即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问。

 

在Java多线程编程当中,提供了以下几种方式来实现线程安全:

内部锁(Synchronized)和显式锁(Lock):属于互斥同步方法,是重量级的多线程同步机制,可能会引起上下文切换和线程调度,它同时提供内存可见性、有序性和原子性。

volatile:轻量级多线程同步机制,不会引起上下文切换和线程调度。仅提供内存可见性、有序性保证,不提供原子性。

CAS原子指令:属于非阻塞同步方法,轻量级多线程同步机制,不会引起上下文切换和线程调度。它同时提供内存可见性、有序性和原子化更新保证。

本文就讲到这里,想了解更多内容请参考:

Java并发编程札记-目录

END.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: