多线程编程是有趣的事情,当常常容易突然出现“错误的情况”,这是由于系统的线程调度具有一定的随机性。即使是程序运行期间偶尔出现的问题,那也是由于我们的编程不当所引起的。当使用多个线程来访问同一个数据时,非常容易出现线程安全问题。
关于线程安全问题,有一个经典的问题:银行取钱问题。银行取钱的基本流程可以分为如下几个步骤:
【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编程中,"线程同步--生产者消费者问题"是一个经典的多线程问题,它涉及到如何有效地在多个线程之间共享资源。这个问题通常用于演示和理解线程间的协作机制,如互斥锁、条件变量等。在此,我们将深入探讨这个...
当一个方法或代码块被`synchronized`修饰时,同一时间只有一个线程能执行这段代码,防止数据竞争。在示例中,`synchronized`关键字用于保护共享资源,避免并发访问导致的问题。 除了`synchronized`,Java还提供了...
本教程将深入讲解Java中的多线程以及同步控制机制,特别是同步代码块和同步方法。 首先,我们要理解什么是线程。线程是程序执行的最小单位,一个进程中可以有多个线程并发执行。在Java中,可以通过实现`Runnable`...
总结来说,Java中的同步代码块是一种有效的解决线程安全问题的手段,通过限制对共享资源的并发访问,可以保证数据的一致性和线程的安全性。然而,过度使用同步可能会降低程序的性能,因此需要合理设计同步范围,确保...
它可以修饰方法或代码块,当一个线程进入同步块/方法后,其他线程必须等待其释放锁才能进入。 - 同步锁机制通过监视器锁(Monitor)实现,每个对象都有一个内置锁,即监视器。 3. **Java并发工具包JUC**: - JUC...
// 在此代码块内,只有当前线程可以执行,其他线程被阻塞 // 对共享资源的操作 } int main() { std::thread t1(thread_function); std::thread t2(thread_function); t1.join(); t2.join(); return 0; } `...
在"进程与线程--小练习"这个主题中,可能包含了一些实践性的例子,比如创建和管理进程、线程的示例,或者展示了如何使用操作系统提供的API来实现进程间通信和线程同步。这些练习可以帮助学习者更好地理解和掌握进程...
压缩包中的"线程同步"文件很可能包含了使用C++或者C语言实现的这些同步机制的实例代码。这些实例可能包括创建、等待、释放等操作,以及如何处理各种同步异常情况,比如死锁。通过学习这些实例,开发者可以更好地理解...
当一个线程进入同步代码块时,其他试图进入相同同步代码块的线程会被阻塞,直到该线程完成其执行并释放锁。 同步块通常使用`synchronized`关键字来实现。它的基本语法结构如下: ```java synchronized(对象引用) {...
在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。
"Java多线程同步.pdf" Java多线程同步是指在Java语言中,如何使用synchronized关键字和其他同步机制来确保多线程程序的正确执行。在Java语言中,synchronized关键字用于对方法或者代码块进行同步,但是仅仅使用...
- **synchronized关键字**:可以用于方法或代码块,确保同一时刻只有一个线程能执行特定的代码。 - **volatile关键字**:保证变量的可见性和有序性,防止多个线程间的数据不一致。 - **Lock接口及其实现**:如`...
- **创建线程**:提供接口创建新的线程对象,执行特定的代码块。 - **线程通信**:可能包含事件驱动的机制,使得线程间能够交换信息。 - **线程同步**:确保对共享资源的访问是安全的,防止竞态条件。 - **线程...
4. **临界区(Critical Section)**:临界区是最简单的线程同步方式,它定义了一段代码,同一时间只有一个线程可以执行。VC++的`EnterCriticalSection`和`LeaveCriticalSection`函数用于进入和离开临界区。 在这个...
1. **临界区(Critical Section)**:临界区是一种简单的线程同步机制,用于保护一小段代码,使得在同一时间只有一个线程可以执行这段代码。例如,在公交车上,当司机启动车辆时(临界区),售票员不应同时开启车门...
- **同步块**:使用`synchronized`关键字包围代码块,指定要锁定的对象,例如: ```java synchronized (object) { // ... } ``` - **wait(), notify(), notifyAll()**:这些方法属于`Object`类,用于线程间的...
- **同步方法**:同步方法是一种特殊的同步代码块,其同步代码块范围为整个方法。方法的调用对象被自动当作锁对象。 ### 线程状态 Java线程在生命周期内会经历六种状态: 1. **NEW**:线程已创建,但尚未启动。即...
2. **同步代码块**:使用`{}`包裹需要同步的代码,前缀是`synchronized (object)`,其中`object`是同步监视器,通常是共享资源的对象。 ### 二、wait-notify 机制 wait、notify和notifyAll是Object类的三个方法,...