多线程编程(四)--线程同步

当使用多个线程来访问同一个数据时,就容易出现线程安全的问题。例如,银行取钱。当我们去自动取款机取钱时,正好另一个人转账,即多个线程修改同一数据,这时就容易出现线程安全问题。

线程安全

/**
 * 账户类,该类封装了账户编号和余额两个属性
 * @author Emily-T
 *
 */
public class Account {

	//账户编号
	private String accountNo;
	//余额
	private double balance;
	public Account(){}

	//构造函数
	public Account(String accountNo,double balance){
		this.accountNo = accountNo;
		this.balance = 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;
	}

	public String getAccountNo() {
		return accountNo;
	}

	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

}

/**
 * 取钱的线程类
 *
 * @author Emily-T
 *
 */
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() {
		// 账户余额大于取钱数目
		if (account.getBalance() >= drawAmount) {

			// 吐出钞票
			System.out.println("取钱成功!吐出钞票:" + drawAmount);

//			try {
//				Thread.sleep(1);
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
			// 修改余额
			account.setBalance(account.getBalance() - drawAmount);
			System.out.println("\t余额为:" + account.getBalance());
		} else {
			System.out.println(getName() + "取钱失败!余额不足!");
		}
	}
}
/**
 * 启动两个线程
 * @author Emily-T
 *
 */
public class TestDraw {

	public static void main(String[] args){
		//创建一个账户
		Account acct = new Account("1234567",1000);
		//模拟两个线程对同一个账户取钱
		new DrawThread("甲",acct,800).start();
		new DrawThread("乙",acct,800).start();
	}
}

结果:

从结果看来,账户余额只有1000,取出了1600元,剩下-200元。出现这种结果是因为run方法的方法体不具有同步安全性,程序中有两条并发线程在修改Account对象。

线程同步

修改如下:加上同步代码块:

// 当多条线程修改同一个共享数据时,将涉及数据安全问题
	public void run() {

		// 使用account作为同步监视器,任何线程进入下面同步代码块之前,必须先获得
		// 对account账户的锁定——其他线程无法获得锁,也就是无法修改它
		// 加锁——修改完成——释放锁

		synchronized (account) {
			// 账户余额大于取钱数目
			if (account.getBalance() >= drawAmount) {

				// 吐出钞票
				System.out.println("取钱成功!吐出钞票:" + drawAmount);

				try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				// 修改余额
				account.setBalance(account.getBalance() - drawAmount);
				System.out.println("\t余额为:" + account.getBalance());
			} else {
				System.out.println(getName() + "取钱失败!余额不足!");
			}
		}
	}

结果:

任何时刻只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放了对该同步监视器的锁定。

同步监视器的目的:阻止两条线程对同一个共享资源进行并发访问。因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

可变类的线程安全是以降低程序的运行效率为代价的,为了减少程序安全所带来的负面影响,程序可以采用如下策略:

1、不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步

2、如果可变类有两种运行环境:单线程和多线程环境,则应该为该可变类提供两种版本:线程不安全版本和线程安全版本。在单线程环境中使用线程不安全版本以保证性能,在多线程环境中使用线程安全版本。

时间: 2024-12-13 17:56:16

多线程编程(四)--线程同步的相关文章

Linux程序设计学习笔记----多线程编程之线程同步之条件变量

转载请注明出处:http://blog.csdn.net/suool/article/details/38582521. 基本概念与原理 互斥锁能够解决资源的互斥访问,但是在某些情况下,互斥并不能解决问题,比如两个线程需 要互斥的处理各自的操作,但是一个线程的操作仅仅存在一种条件成立的情况下执行,一旦错过不可再重现,由于线程间相互争夺cpu资源,因此在条件成立的时候,该线程不一定争夺到cpu而错过,导致永远得不到执行..... 因此需要某个机制来解决此问题,更重要的是,线程仅仅只有一种情况需要执

.NET面试题解析(07)-多线程编程与线程同步

系列文章目录地址: .NET面试题解析(00)-开篇来谈谈面试 & 系列文章索引 关于线程的知识点其实是很多的,比如多线程编程.线程上下文.异步编程.线程同步构造.GUI的跨线程访问等等,本文只是从常见面试题的角度(也是开发过程中常用)去深入浅出线程相关的知识.如果想要系统的学习多线程,没有捷径的,也不要偷懒,还是去看专业书籍的比较好. 常见面试题目: 1. 描述线程与进程的区别? 2. 为什么GUI不支持跨线程访问控件?一般如何解决这个问题? 3. 简述后台线程和前台线程的区别? 4. 说说常

C# 多线程编程第二步——线程同步与线程安全

上一篇博客学习了如何简单的使用多线程.其实普通的多线程确实很简单,但是一个安全的高效的多线程却不那么简单.所以很多时候不正确的使用多线程反倒会影响程序的性能. 下面先看一个例子 : class Program { static int num = 1; static void Main(string[] args) { Stopwatch stopWatch = new Stopwatch(); //开始计时 stopWatch.Start(); ThreadStart threadStart

Delphi中线程类TThread实现多线程编程(线程同步技术、Synchronize、WaitFor……)

接着上文介绍TThread. 现在开始说明 Synchronize和WaitFor 但是在介绍这两个函数之前,需要先介绍另外两个线程同步技术:事件和临界区 事件(Event)与Delphi中的事件有所不同.从本质上讲,Event其实就相当于一个全局的布尔变量.它有两个赋值操作:Set和ReSet,相当于把它设置为 True或False.而检查它的值是通过WaitFor操作进行.对应在Windows平台上,是三个API函数:SetEvent.ResetEvent.WaitForSignalObje

iOS多线程编程:线程同步总结 NSCondtion

1:原子操作 - OSAtomic系列函数 iOS平台下的原子操作函数都以OSAtomic开头,使用时需要包含头文件<libkern/OSBase.h>.不同线程如果通过原子操作函数对同一变量进行操作,可以保证一个线程的操作不会影响到其他线程内对此变量的操作,因为这些操作都是原子式的.因为原子操作只能对内置类型进行操作,所以原子操作能够同步的线程只能位于同一个进程的地址空间内. 2:锁 - NSLock系列对象 iOS平台下的锁对象为NSLock对象,进入锁通过调用lock函数,解锁调用unl

七. 多线程编程8.线程同步

当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用.达到此目的的过程叫做同步(synchronization).像你所看到的,Java为此提供了独特的,语言水平上的支持. 同步的关键是管程(也叫信号量semaphore)的概念.管程是一个互斥独占锁定的对象,或称互斥体(mutex).在给定的时间,仅有一个线程可以获得管程.当一个线程需要锁定,它必须进入管程.所有其他的试图进入已经锁定的管程的线程必须挂起直到第一个线程退出管程.这些其他的线程被称为等待管程.一个

廖雪峰Java11多线程编程-2线程同步-2synchronized方法

1.Java使用synchronized对一个方法进行加锁 class Counter{ int count = 0; public synchronized void add(int n){ count += n; } public synchronized void dec(int n){ count -= n; } public int get(){//读取一个int类型是原子操作,不需要同步 return count; } } class AddThread extends Thread

Android多线程编程之线程池学习篇(一)

Android多线程编程之线程池学习篇(一) 一.前言 Android应用开发中多线程编程应用比较广泛,而应用比较多的是ThreadPoolExecutor,AsyncTask,IntentService,HandlerThread,AsyncTaskLoader等,为了更详细的分析每一种实现方式,将单独成篇分析.后续篇章中可能涉及到线程池的知识,特此本篇分析为何使用线程池,如何使用线程池以及线程池的使用原理. 二.Thread Pool基础 进程代表一个运行中的程序,一个运行中的Android

JAVA并发编程4_线程同步之volatile关键字

上一篇博客JAVA并发编程3_线程同步之synchronized关键字中讲解了JAVA中保证线程同步的关键字synchronized,其实JAVA里面还有个较弱的同步机制volatile.volatile关键字是JAVA中的轻量级的同步机制,用来将变量的更新操作同步到其他线程.从内存可见性的角度来说,写入volatile变量相当于退出同步代码块,读取volatile变量相当于进入同步代码块. 旧的内存模型:保证读写volatile都直接发生在main memory中. 在新的内存模型下(1.5)

C#多线程编程实例 线程与窗体交互

代码: public partial class Form1 : Form { //声明线程数组 Thread[] workThreads = new Thread[10]; public Form1() { InitializeComponent(); } //此委托允许异步的调用为Listbox添加Item delegate void AddItemCallback(string text); //这种方法演示如何在线程安全的模式下调用Windows窗体上的控件. private void