java多线程回顾3:线程安全
1、线程安全问题
关于线程安全问题,有一个经典案例:银行取钱问题。
假设有一个账户,有两个线程从账户里取钱,如果余额大于取钱金额,则取钱成功,反之则失败。
下面来看下线程不安全的程序会出什么问题。
账户类:
public class Account {
public int balance = 10;//账户余额
//取钱的方法
public void draw(int money){
if (balance >= money) {
//此处让线程Thread-1睡眠1秒,是为了模拟线程不安全造成的错误
if ("Thread-1".equals(Thread.currentThread().getName())) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
balance = balance - money;
System.out.println(Thread.currentThread().getName()+"取钱成功,余额:"+balance);
}else{
System.out.println("取钱失败,余额不足。余额:"+balance);
}
}
}
取钱线程:
public class DrawThread implements Runnable{
public Account account;
public DrawThread(Account account){
this.account = account;
}
@Override
public void run() {
//写个死循环,模拟不停取钱
while(true){
try {
//此处睡眠500毫秒是为了让程序运行的慢一点,方便观察
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用取钱方法,一次取4元
account.draw(4);
}
}
}
测试类:
public class TestDraw {
public static void main(String[] args) {
//创建一个账户
Account account = new Account();
//创建两个线程,从同一个账户取钱
DrawThread dtOne = new DrawThread(account);
DrawThread dtTwo = new DrawThread(account);
//启动线程
new Thread(dtOne).start();
new Thread(dtTwo).start();
}
}
测试结果:
Thread-0取钱成功,余额:6 Thread-0取钱成功,余额:2 取钱失败,余额不足。余额:2 Thread-1取钱成功,余额:-2 取钱失败,余额不足。余额:-2 取钱失败,余额不足。余额:-2
这个结果显然是不对的,当余额小于取钱金额时,程序应该取钱失败,而不是把余额变成负数。之所以会出现这种情况,是因为当线程Thread-1通过balance >= money之后被阻塞了,这时候线程Thread-0也通过了balance >= money判断,并且把钱取走了。这之后,Thread-1重新开始运行,继续取钱,于是余额就变成负数了。
在实际的开发中,由于线程调度不可控,也可能出现类似的情况,所以对多线程操作一定要注意线程安全。
2、线程同步
为了解决线程安全问题,有三种方法:同步代码块、同步方法、同步锁。
同步代码块:
同步代码块的语法为:
synchronized (obj) {
…
//此处代码就是同步代码块
}
以上代码的obj叫做同步监视器,以上代码的含义是,线程开始执行同步代码块之前,必须获得对同步监视器的锁定。一般来说,我们把并发时共享的资源作为同步监视器,例子中账户就是共享的资源,所以写this,表示对象本身。
使用同步代码块改造的账户类如下:
//取钱的方法
public void draw(int money){
//同步代码块开始
synchronized (this) {
if (balance >= money) {
//此处让线程Thread-1睡眠1秒,是为了模拟线程不安全造成的错误
if ("Thread-1".equals(Thread.currentThread().getName())) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
balance = balance - money;
System.out.println(Thread.currentThread().getName()+"取钱成功,余额:"+balance);
}else{
System.out.println("取钱失败,余额不足。余额:"+balance);
}
}
//同步代码块结束
}
同步方法:
同步方法即使用synchronized修饰方法,不用显示指定同步监视器,其同步监视器就是this,即对象本身。
使用同步方法改造的账户类如下:
//取钱的方法
public synchronized void draw(int money){
if (balance >= money) {
//此处让线程Thread-1睡眠1秒,是为了模拟线程不安全造成的错误
if ("Thread-1".equals(Thread.currentThread().getName())) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
balance = balance - money;
System.out.println(Thread.currentThread().getName()+"取钱成功,余额:"+balance);
}else{
System.out.println("取钱失败,余额不足。余额:"+balance);
}
}
需要注意的是,synchronized不可以修饰属性和构造方法。
释放同步监视器的锁定
以下情况将释放对同步监视器的锁定:
- 同步方法(代码块)执行完毕。
- 执行中遇到return、break终止了同步方法(代码块)的执行。
- 同步方法(代码块)抛出了未处理的异常或错误。
- 调用了同步方法(代码块)的wait()方法,此时当前线程暂停,并释放对同步监视器的锁定。
以下情况不会释放对同步监视器的锁定:
- 调用sleep、yield方法,当前线程会暂停,但不会释放锁定。
- 其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放对同步监视器的锁定。注意,尽量不要使用suspend和resume方法,容易死锁。
同步锁
从JDK1.5开始,可以通过显示定义同步锁来实现线程安全。
使用方法和synchronized大同小异,基本上也是加锁—执行代码—解锁这么一个过程。
使用Lock改造的取钱方法如下:
//定义锁对象
private final Lock lock = new ReentrantLock();
//取钱的方法
public void draw(int money){
//加锁
lock.lock();
try {
if (balance >= money) {
//此处让线程Thread-1睡眠1秒,是为了模拟线程不安全造成的错误
if ("Thread-1".equals(Thread.currentThread().getName())) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
balance = balance - money;
System.out.println(Thread.currentThread().getName()+"取钱成功,余额:"+balance);
}else{
System.out.println("取钱失败,余额不足。余额:"+balance);
}
} finally {
//为了确保解锁,放在finally里
lock.unlock();
}
}
以上代码中,为了确保最后能释放锁,所以把解锁代码放在finally中。
和synchronized相比,Lock在使用上更灵活。上例中使用的是可重入锁,即线程可以对已加锁的代码再加锁。此外还有读写锁等。
3、死锁
两个线程相互等待对方释放对同步监视器的锁定,这种情况叫死锁。
java多线程回顾3:线程安全的更多相关文章
- Java多线程系列--“JUC线程池”06之 Callable和Future
概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC线程池”04之 线程池原理(三)
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
- -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中
本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait( ...
- 转:java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例
java多线程CountDownLatch及线程池ThreadPoolExecutor/ExecutorService使用示例 1.CountDownLatch:一个同步工具类,它允许一个或多个线程一 ...
- Java多线程——进程和线程
Java多线程——进程和线程 摘要:本文主要解释在Java这门编程语言中,什么是进程,什么是线程,以及二者之间的关系. 部分内容来自以下博客: https://www.cnblogs.com/dolp ...
- Java多线程之守护线程
Java多线程之守护线程 一.前言 Java线程有两类: 用户线程:运行在前台,执行具体的任务,程序的主线程,连接网络的子线程等都是用户线程 守护线程:运行在后台,为其他前台线程服务 特点:一旦所有用 ...
- Java多线程并发02——线程的生命周期与常用方法,你都掌握了吗
在上一章,为大家介绍了线程的一些基础知识,线程的创建与终止.本期将为各位带来线程的生命周期与常用方法.关注我的公众号「Java面典」了解更多 Java 相关知识点. 线程生命周期 一个线程不是被创建了 ...
随机推荐
- Vmware Ubuntu 开机蓝屏
引用:http://tieba.baidu.com/p/4898482611 1. 这是vm的一个bug!!!打开你的虚拟系统目录,编辑虚拟机文件夹下面的.vmx 用记事本打开,加入代码. cpuid ...
- HTML5 lufylegend引擎学习(一) -- 剪刀石头布小游戏
网址:http://www.lufylegend.com/ <!DOCTYPE html> <html> <head> <title>A Little ...
- Python开发【第二篇】:初始Python
Python的前世今生 Python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,中国人称他为龟叔,他在圣诞节期间在阿姆斯特丹为了打发时间,决定开发一个新的脚本 ...
- Django 官方推荐的姿势:类视图
作者:HelloGitHub-追梦人物 文中所涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 在开发网站的过程中,有一些视图函数虽然处理的对象不同,但是其大致的代码逻辑是一样的. ...
- shell数组(四)
[root@ipha-dev71- exercise_shell]# cat test.sh #!/bin/bash my_array=(a b c d) echo "第一个元素为:${my ...
- postman-windows下newman的使用
一.newman的安装 1.安装node.js下载https://nodejs.org/en/ C:\Users\iphauser>node -vv10.16.1C:\Users\iphause ...
- C#事件浅淡(1)
最近在写C#,感觉事件这个机制很好,可是怎么实现自己定义的事件呢?查了资料有的不全有的不完整,有的太深,自己写一个简单的例子. 原则 1,定义一个事件信息类(标准的都继承EventArgs) 2.定义 ...
- ArcGIS制作地形图【详细步骤】
结果图: 详细步骤: 一.在ArcSence中加载带有投影坐标系的dem数据 Dem的属性设置为下图可有上面的效果. 二.提取栅格范围 找到工具[3D Analyst][转换][栅格范围]双击打开设置 ...
- django-模型之从数据库获取数据(二)
1.获取一条数据(字段值必须唯一) 2.条件查询filter 3.排除查询exclude 4.链式查询 5.查询后进行排序order_by 6.按字段查询values 7.插入数据create 8.数 ...
- swiper轮播
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...