多线程编程是有趣的事情,当常常容易突然出现“错误的情况”,这是由于系统的线程调度具有一定的随机性。即使是程序运行期间偶尔出现的问题,那也是由于我们的编程不当所引起的。当使用多个线程来访问同一个数据时,非常容易出现线程安全问题。
关于线程安全问题,有一个经典的问题:银行取钱问题。银行取钱的基本流程可以分为如下几个步骤:
【1】用户输入账户,密码,系统判断用户的账户,密码是否匹配。
【2】用户输入取款金额。
【3】系统判断账户余额是否大于取款金额。
【4】如果余额大于取款金额,取款成功,否则取款失败。
这个流程看上去没有任何问题。但一旦将这个流程放在多线程并发的场景下,就有可能出现问题,但不是说一定。也许程序运行一百万次都没有出现问题,没有出现问题并不等于没有问题!这就是多线程访问同一数据的时候,数据安全性问题,在Java中主要有三种方法解决线程安全问题:同步代码块,同步方法,同步锁。在这里简要介绍下同步代码块方法。
同步代码块语法格式如下:
synchronized(){
...
//此处代码就是同步代码块
}
在以上语法中synchronized后括号里的obj就是同步监视器,以上代码的含义就是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。显然模拟一个取款过程可建立三个类:用户账户,取款类,测试类
用户类Account代码:
package com.thread.test;
/**
* 用户类
*
* @author wwb
*
*/
public class Account
{
/**
* 账户
*/
private String accountNo;
/**
* 账户余额
*/
private double balance;
public Account(){}
//构造器
public Account(String accountNo , double balance)
{
this.accountNo = accountNo;
this.balance = balance;
}
public void setAccountNo(String accountNo)
{
this.accountNo = accountNo;
}
public String getAccountNo()
{
return this.accountNo;
}
public void setBalance(double balance)
{
this.balance = balance;
}
public double getBalance()
{
return this.balance;
}
//下面两个方法根据accountNo来计算Account的hashCode和判断equals
public int hashCode()
{
return accountNo.hashCode();
}
public boolean equals(Object obj)
{
if (obj != null
&& obj.getClass() == Account.class)
{
Account target = (Account)obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
由于同步监视器的目的就是:阻止两条线程对同一个共享资源进行并发访问。因此通常推荐使用可能被并发访问的共享资源充当同步监视器。对于上面的取钱模拟程序,我们应该考虑使用账户作为同步监视器。因此以下的模拟取款程序代码如下:
取款类DrawThread代码:
package com.thread.test;
/**
* 取款类
*
* @author wwb
*
*/
public class DrawThread extends Thread {
// 模拟用户账户
private Account account;
// 当前取钱线程所希望取的钱数
private double drawAmount;
public DrawThread(String name, Account account,
double drawAmount) {
super(name);
this.account = account;
this.drawAmount = drawAmount;
}
// 当多条线程修改同一个共享数据时,将涉及到数据安全问题。
public void run() {
// 使用account作为同步监视器,任何线程进入下面同步代码块之前,
// 必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改它
// 这种做法符合:加锁-->修改完成-->释放锁 逻辑
synchronized (account) {
// 账户余额大于取钱数目
if (account.getBalance() >= drawAmount) {
// 吐出钞票
System.out.println(getName() + "取钱成功!吐出钞票:" + drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// 修改余额
account.setBalance(account.getBalance() - drawAmount);
System.out.println("\t余额为: " + account.getBalance());
} else {
System.out.println(getName() + "取钱失败!余额不足!");
}
}
}
}
测试类TestDraw代码:
package com.thread.test;
/**
* 测试类 *
* @author wwb
*
*/
public class TestDraw {
/**
* @param args
*/
public static void main(String[] args) {
// 创建一个账户
Account acct = new Account("1234567", 1000);
// 模拟两个线程对同一个账户取钱
new DrawThread("甲", acct, 800).start();
new DrawThread("乙", acct, 800).start();
}
}
分享到:
相关推荐
Java多线程--同步机制解决线程安全问题方式一:同步代码块
【IT十八掌徐培成】Java基础第08天-03.多线程-同步代码块-同步方法.zip
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
"Java多线程同步.pdf" Java多线程同步是指在Java语言中,如何使用synchronized关键字和其他同步机制来确保多线程程序的正确执行。在Java语言中,synchronized关键字用于对方法或者代码块进行同步,但是仅仅使用...
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
计算机后端-Java-Java核心基础-第20章 多线程 07. 同步代码块处理实现Runnable的线程安全问题.avi
计算机后端-Java-Java核心基础-第20章 多线程 08. 同步代码块处理继承Thread类的线程安
代码中给出了一种方式,去同步字符串,线程携带同一个字符串进入同步方法中,会阻塞,非同一个字符串不会阻塞,所以我们可以考虑用账户名称、或一些账户独有不会重复的属性来进行代码块同步,这样可最大程度的提高...
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
本文主要介绍java多线程-同步块的知识,这里整理了相关的详细资料及简单示例代码,有兴趣的小伙伴可以参考下
Java 线程同步机制中 synchronized 关键字的理解 Java 的线程同步机制是为了解决多个线程共享同一片存储空间所带来的访问冲突问题。其中,synchronized 关键字是 Java 语言中解决这种冲突的重要机制。 ...
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
主要介绍了java 中同步方法和同步代码块的区别是什么的相关资料,需要的朋友可以参考下
synchronized关键字可以将方法或代码块锁定,使得多个线程不能同时访问该方法或代码块。 ConcurrentHashMap ConcurrentHashMap是Java中的一种线程安全的哈希表,用于解决多线程环境下的数据共享问题。...
因为我们要用额外的代码把多个线程同时访问的数据包围起来,并获取和释放一个线程同步锁,如果我们在一个代码块忘记获取锁,就有可能造成数据损坏。 2. 使用线程同步会影响性能,获取和释放一个锁肯定是需要时间的吧...
【Java基础知识 第四节 多线程复习】中,同步代码块(synchronized关键字)的两个练习代码。
三、还有一种用信号量对象来管理线程同步的,它是在互斥的基础上建立的,但信号量增加了资源计数的功能,预定数目的线程允许同时进入要同步的代码。有点复杂,想不到在哪可以用,现在就不研究论了。 发表于 @ 2008...
同步代码块2.同步方法 1.线程操作的常用方法 序号 方法名称 描述 1 static Thread currentThread() 返回目前正在执行的线程 2 final String getName() 返回线程的名称 3 final boolean isAlive() 判断...
文章目录synchronized线程安全主要诱因互斥锁特性获取对象锁同对象,异步同对象,同步代码块同对象,非静态同步方法同对象,同步代码块 对比 非静态同步方法不同对象,同步代码块 对比 非静态同步方法获取类锁和对象...