`

线程的同步机制

 
阅读更多
线程的同步机制

(一)如果并发执行的多个线程间需要共享资源或交换数据,则这一组线程称为交互线程。
(二)交互线程并发执行时相互之间会干扰或影响其他线程的执行结果,因此交互线程之间需要有同步机制。
(三)交互线程之间存在两种关系:竞争关系和协作关系。
(四)对于竞争关系的交互线程间需要采用线程互斥方式解决共享资源冲突问题;
(五)对于协作关系的交互线程间需要采用线程同步方式解决线程间通信及因执行速度不同而引起的不同步问题。
(六)线程的同步机制包括线程互斥和线程同步,线程互斥是线程同步的特殊情况。

交互线程
无关线程与交互线程
    并发线程之间可能是无关的,也可能是交互的。
    无关的并发线程是指它们分别在不同的变量集合上操作。一个线程的执行与其他并发线程的进展无关,即一个并发线程不会改变另一个并发线程的变量值。
     交互的并发线程是指他们共享某些变量,一个线程的执行可能影响其他线程的执行结果,交互的并发线程之间具有制约关系。因此,线程的交互必须是有控制的,否则会出现不正确的结果。
并发执行的交互线程间存在与时间有关的错误
     无关线程间并发执行时不会产生与时间有关的错误。例如,奇数线程与偶数线程是两个并发执行的无关线程。程序每次运行,它们之间的执行次序可能会因线程调度而不同,但不会影响对方的结果值,奇数线程中绝不会输出偶数。
      交互的并发线程执行时,由于它们在不同时刻对同一个共享变量进行操作,线程之间相互影响、相互干扰,因此计算结果往往取决于这一组并发线程的相对速度,各种与时间有关的错误就可能出现
       例如:将对银行账户的存款、取款操作分别设计成线程,存款线程与取款线程能够对同一个账户数据进行操作,此时该账户称为共享变量,并发执行的多个存款、取款线程间可能会产生与时间有关的错误。
package com.jbx.thread.account;

public class Account {        //账户类
	private String name;     //储户姓名
	private double balance;  //账户余额
	
	public Account(String name) {
		this.name = name;
		this.balance = 0;
	}
	public String getName() {     //返回账户名
		return name;
	}

	public double getBalance() {   //查看账户余额
		return balance;
	}
	
	public void put(double value) {   //存款操作,参数为存入金额
		if(value>0)
		this.balance += value;        //存款操作使余额值增加//存款操作,参数为取款金额,返回实际取到金额
	}
	
	public double get(double value){  //取款操作,参数为取款金额,返回实际取到金额
		if(value>0){
			if(value<=this.balance)
				this.balance -= value;  //取款操作使余额值减少
			else{
				value= this.balance;    //取走全部余额
				this.balance = 0; 
			}
		    return value;              //返回实际取款额
		}
		return 0;
	}
}

class Save extends Thread{  //存款线程类
	private Account account;  //账户
	private double value;     //存款金额
	
	public Save(Account al ,double value){
		this.account = al;
		this.value = value;
	}
	public void run(){
		double howmatch = this.account.getBalance();//查看账户余额
		this.account.put(this.value);
		try {
			sleep(1);                              //花费实际,线程执行被打断
		} catch (InterruptedException e) {}
		System.out.println(this.account.getName()+"账户:现有"+howmatch+",存入"+this.value+",余额"+this.account.getBalance());
	}
}
class Fetch extends Thread{
	private Account account;  // 
	private double value;     // 
	
	public Fetch(Account al ,double value){
		this.account = al;
		this.value = value;
	}
	
	public void run(){
		double howmatch = this.account.getBalance();//查看账户余额
		this.account.put(this.value);
		try {
			sleep(1);                              //花费实际,线程执行被打断
		} catch (InterruptedException e) {}
		System.out.println(this.account.getName()+"账户:现有"+howmatch+",取走"+this.account.get(this.value)+",余额"+this.account.getBalance());
	}
	public static void main(String[] args) {
		Account wang = new Account("wang");
		(new Save(wang,100)).start();  //存100
		(new Save(wang,100)).start();  //存200
		(new Fetch(wang,100)).start();   //取300
	}
}


运行结果:
wang账户:现有0.0,存入100.0,余额300.0
wang账户:现有100.0,存入100.0,余额300.0
wang账户:现有200.0,取走100.0,余额200.0
解析:
      main方法中对wang创建并启动了2个存款线程和1个取款线程。3个线程对象能够对同一个账户对象中数据进行操作,但每次运行的结果不确定,出现与时间有关的并发执行问题。
      程序设计运行结果的讨论如下:
(1)运行结果不唯一,取决于线程调度
  如果存/取款操作花费时间较短,线程执行没有被打断,即线程体中没有sleep延时,则程序运行结果不确定。取款线程实际取到的金额可能是0、100、200或300,取决于2个存款线程是否执行完成,与线程调度有关。一种可能的运行结果如下:
wang账户:现有0.0,存入100.0,余额100.0
wang账户:现有100.0,取走100.0,余额 0.0
wang账户:现有100.0,存入200.0,余额200.0
(2)线程执行被打断时出现错误
   执行一个存/取款线程对象的run()方法,首先查看指定账户的现有金额,再进行存/取款操作,如果执行时间较长线程将会被打断。程序中用sleep(1)模拟线程执行被打断的情况,运行结果如下:
wang账户:现有0.0,存入100.0,余额100.0
wang账户:现有100.0,取走100.0,余额 0.0  //有错,三者数据不符
wang账户:现有100.0,存入200.0,余额200.0 //有错,三者数据不符
    3个线程启动后都进入运行态,每个线程在查看了账户余额后被打断,尽管sleep时间只有1ms,很短暂,但足以改变线程状态,线程被迫让出处理器,暂停运行。系统调度其他线程运行,其他并发执行线程修改了同一账户的共享数据,导致线程再次运行时,该账户余额已不是其先前查看的金额,因而运行结果中查看金额、存/取金额和剩余金额三者数据不相符,这严重破坏了数据的完整性和一致性。
    错误产生的原因是:多个线程交替访问同一个共享变量,干扰其他线程的执行结果。
    避免出现错误的办法是:多个线程串行地、互斥地访问共享变量。

     如果有两个线程同时分别对两个不同账户对象进行操作,则线程执行将不受干扰。例如:
   (new Save(wang,200)).start();
   (new Save(new Account("Li"),100)).start();

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics