[align=center]线程的同步机制[/align]
(一)如果并发执行的多个线程间需要共享资源或交换数据,则这一组线程称为交互线程。
(二)交互线程并发执行时相互之间会干扰或影响其他线程的执行结果,因此交互线程之间需要有同步机制。
(三)交互线程之间存在两种关系:竞争关系和协作关系。
(四)对于[color=red]竞争关系[/color]的交互线程间需要采用[color=red]线程互斥[/color]方式解决共享资源冲突问题;
(五)对于[color=red]协作关系[/color]的交互线程间需要采用[color=red]线程同步[/color]方式解决线程间通信及因执行速度不同而引起的不同步问题。
(六)线程的同步机制包括线程互斥和线程同步,线程互斥是线程同步的特殊情况。
[size=x-large]交互线程[/size]
[size=large][color=red]无关线程与交互线程[/color][/size]
并发线程之间可能是无关的,也可能是交互的。
无关的并发线程是指它们分别在不同的变量集合上操作。一个线程的执行与其他并发线程的进展无关,即一个并发线程不会改变另一个并发线程的变量值。
交互的并发线程是指他们共享某些变量,一个线程的执行可能影响其他线程的执行结果,交互的并发线程之间具有制约关系。因此,线程的交互必须是有控制的,否则会出现不正确的结果。
[size=large][color=red]并发执行的交互线程间存在与时间有关的错误[/color][/size]
无关线程间并发执行时不会产生与时间有关的错误。例如,奇数线程与偶数线程是两个并发执行的无关线程。程序每次运行,它们之间的执行次序可能会因线程调度而不同,但不会影响对方的结果值,奇数线程中绝不会输出偶数。
[color=blue]交互的并发线程执行时,由于它们在不同时刻对同一个共享变量进行操作,线程之间相互影响、相互干扰,因此计算结果往往取决于这一组并发线程的相对速度,各种与时间有关的错误就可能出现[/color]。
例如:将对银行账户的存款、取款操作分别设计成线程,存款线程与取款线程能够对[color=blue]同一个账户[/color]数据进行操作,此时[color=blue]该账户称为共享变量[/color],并发执行的多个存款、取款线程间可能会产生与时间有关的错误。
运行结果:
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个线程对象能够对[color=red]同一个账户对象中数据[/color]进行操作,但每次运行的结果不确定,出现与时间有关的并发执行问题。
程序设计运行结果的讨论如下:
(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,很短暂,但足以改变线程状态,线程被迫让出处理器,暂停运行。系统调度其他线程运行,其他并发执行线程修改了同一账户的共享数据,导致线程再次运行时,该账户余额已不是其先前查看的金额,因而运行结果中查看金额、存/取金额和剩余金额三者数据不相符,这严重破坏了数据的完整性和一致性。
[color=red]错误产生的原因是:多个线程交替访问同一个共享变量,干扰其他线程的执行结果。
避免出现错误的办法是:多个线程串行地、互斥地访问共享变量。
[/color]
如果有两个线程同时分别对两个不同账户对象进行操作,则线程执行将不受干扰。例如:
(new Save(wang,200)).start();
(new Save(new Account("Li"),100)).start();
(一)如果并发执行的多个线程间需要共享资源或交换数据,则这一组线程称为交互线程。
(二)交互线程并发执行时相互之间会干扰或影响其他线程的执行结果,因此交互线程之间需要有同步机制。
(三)交互线程之间存在两种关系:竞争关系和协作关系。
(四)对于[color=red]竞争关系[/color]的交互线程间需要采用[color=red]线程互斥[/color]方式解决共享资源冲突问题;
(五)对于[color=red]协作关系[/color]的交互线程间需要采用[color=red]线程同步[/color]方式解决线程间通信及因执行速度不同而引起的不同步问题。
(六)线程的同步机制包括线程互斥和线程同步,线程互斥是线程同步的特殊情况。
[size=x-large]交互线程[/size]
[size=large][color=red]无关线程与交互线程[/color][/size]
并发线程之间可能是无关的,也可能是交互的。
无关的并发线程是指它们分别在不同的变量集合上操作。一个线程的执行与其他并发线程的进展无关,即一个并发线程不会改变另一个并发线程的变量值。
交互的并发线程是指他们共享某些变量,一个线程的执行可能影响其他线程的执行结果,交互的并发线程之间具有制约关系。因此,线程的交互必须是有控制的,否则会出现不正确的结果。
[size=large][color=red]并发执行的交互线程间存在与时间有关的错误[/color][/size]
无关线程间并发执行时不会产生与时间有关的错误。例如,奇数线程与偶数线程是两个并发执行的无关线程。程序每次运行,它们之间的执行次序可能会因线程调度而不同,但不会影响对方的结果值,奇数线程中绝不会输出偶数。
[color=blue]交互的并发线程执行时,由于它们在不同时刻对同一个共享变量进行操作,线程之间相互影响、相互干扰,因此计算结果往往取决于这一组并发线程的相对速度,各种与时间有关的错误就可能出现[/color]。
例如:将对银行账户的存款、取款操作分别设计成线程,存款线程与取款线程能够对[color=blue]同一个账户[/color]数据进行操作,此时[color=blue]该账户称为共享变量[/color],并发执行的多个存款、取款线程间可能会产生与时间有关的错误。
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个线程对象能够对[color=red]同一个账户对象中数据[/color]进行操作,但每次运行的结果不确定,出现与时间有关的并发执行问题。
程序设计运行结果的讨论如下:
(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,很短暂,但足以改变线程状态,线程被迫让出处理器,暂停运行。系统调度其他线程运行,其他并发执行线程修改了同一账户的共享数据,导致线程再次运行时,该账户余额已不是其先前查看的金额,因而运行结果中查看金额、存/取金额和剩余金额三者数据不相符,这严重破坏了数据的完整性和一致性。
[color=red]错误产生的原因是:多个线程交替访问同一个共享变量,干扰其他线程的执行结果。
避免出现错误的办法是:多个线程串行地、互斥地访问共享变量。
[/color]
如果有两个线程同时分别对两个不同账户对象进行操作,则线程执行将不受干扰。例如:
(new Save(wang,200)).start();
(new Save(new Account("Li"),100)).start();