什么是线程同步?为什么需要线程同步?如何实现线程的同步?这是在学线程时,学生们经常会困惑的问题。我们从一个例子说明,这三个问题以及解决方案:
我们知道一般我们在银行开设一个帐户,银行会给我们一个存折和一个借记卡。我们可以分别拿卡或者存折来对一个帐户进行存取款操作。假设一个帐户上有余额5000元,而记录这个信息的数据库服务器经常不在你所做操作的银行,这样就存在先从服务器上获取余额数据,然后再进行存取款操作,第三步将存取款后的结果写入数据库(当然实际的操作可能有更复杂的业务逻辑)。我们如果同时使用存折和借记卡分别在不同的银行进行存取款操作,如果没有同步,那么将会出现错误。
一个银行帐户中有余额:5000
第一步:一个人使用银行存折在一个银行进行存款3000的操作:
这个业务先要提取到余额5000,然后进行累加,变成8000。
第二步:另一个人使用借记卡在另一个银行的ATM机上取款2000的操作(假设是同时进行)。这个业务也先提取到余额5000,然后减去2000,变成3000。
第三步:ATM操作较快,将3000的余额写入数据库服务器。
第四步:第一步的存款操作这时候操作完毕,将8000写入数据库服务器。
很显然经过这个过程我们逻辑上的余额应该是6000才对。但是最后的结果却是8000。如果使用银行存折进行操作的数据8000先写入数据库,而ATM的操作后的数据5000再写入数据库的话就变成3000了。总之不会出现正确的结果,这就需要同步,也就是说如果一个人对这个帐户存款时不能让其他人再对这个账户进行存款或者取款操作,这样就不会出现上述的错误了。
分析一下这个过程:
一个资源对象:一个银行账户
一个线程:使用存折进行存款操作
另一个线程:使用借记卡进行取款操作
使用代码模拟一下没有同步会出现错误的现象:
//定义一个账户类进行资源对象的定义
publicclass
Account {
privatelong
accNum
;
privatedouble
money
;
private
String
password
;
public
Account(
long
accNum,
double
money, String password) {
super
();
this
.
accNum
= accNum;
this
.
money
= money;
this
.
password
= password;
}
//
存款方法
publicvoid
saveMoney(
double
money){
System.
out
.println(
"
存款操作开始
..."
);
double
tmoney=
this
.
money
;
//
拿到数据库上的帐户余额
//
进行数据操作
tmoney+=money;
//
模拟操作延时
try
{
Thread.sleep(200);
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
//
将修改的数据写入帐户
this
.
money
=tmoney;
System.
out
.println(
"
存款操作完毕,目前帐户余额:
"
+
this
.
money
);
}
//
取款方法
publicvoid
getMoney(
double
money){
System.
out
.println(
"
取款操作开始
..."
);
double
tmoney=
this
.
money
;
//
拿到数据库上的帐户余额
//
进行数据操作
tmoney-=money;
//
模拟操作延时
try
{
Thread.sleep(200);
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
//
将修改的数据写入帐户
this
.
money
=tmoney;
System.
out
.println(
"
取款操作完毕,目前帐户余额:
"
+
this
.
money
);
}
}
//定义存款线程
publicclass
SaveMoneyPerson
extends
Thread{
private
Account
acc
;
public
SaveMoneyPerson(Account acc,String personName) {
this
.setName(personName);
this
.
acc
= acc;
}
//
执行存款
publicvoid
run(){
//
存款
3000
acc
.saveMoney(3000);
}
}
//定义取款线程
publicclass
GetMoneyPerson
extends
Thread{
private
Account
acc
;
public
GetMoneyPerson(Account acc,String personName) {
this
.setName(personName);
this
.
acc
= acc;
}
//
执行取款
publicvoid
run(){
//
取款
3000
acc
.getMoney(2000);
}
}
//执行
publicclass
TestGetSaveMoney {
publicstaticvoid
main(String[] args) {
Account acc=
new
Account(1001,5000,
"123"
);
GetMoneyPerson gThread=
new
GetMoneyPerson(acc,
"getMoneyPerson"
);
SaveMoneyPerson sThread=
new
SaveMoneyPerson(acc,
"saveMoneyPerson"
);
gThread.start();
sThread.start();
}
}
//执行结果有两种:
1-
取款操作开始
...
存款操作开始
...
取款操作完毕,目前帐户余额:
3000.0
存款操作完毕,目前帐户余额:
3000.0
2-
取款操作开始
...
存款操作开始
...
取款操作完毕,目前帐户余额:
8000.0
存款操作完毕,目前帐户余额:
8000.0
无论那一次都不对。
那么什么是线程同步?
线程同步:线程对于同一个资源对象使用的串行使用,而不是同时使用。这就是线程同步。
为什么需要线程同步?
很显然如果没有同步就会出现上述错误。
如果实现同步?
1-
通过在资源对象上定义同步方法就可以实现
publicclass
Account {
privatelong
accNum
;
privatedouble
money
;
private
String
password
;
public
Account(
long
accNum,
double
money, String password) {
super
();
this
.
accNum
= accNum;
this
.
money
= money;
this
.
password
= password;
}
//
存款方法
使用关键字
synchronized
定义同步方法
publicsynchronizedvoid
saveMoney(
double
money){
System.
out
.println(
"
存款操作开始
..."
);
double
tmoney=
this
.
money
;
//
拿到数据库上的帐户余额
//
进行数据操作
tmoney+=money;
//
模拟操作延时
try
{
Thread.sleep(200);
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
//
将修改的数据写入帐户
this
.
money
=tmoney;
System.
out
.println(
"
存款操作完毕,目前帐户余额:
"
+
this
.
money
);
}
//
取款方法
使用关键字
synchronized
定义同步方法
publicsynchronizedvoid
getMoney(
double
money){
System.
out
.println(
"
取款操作开始
..."
);
double
tmoney=
this
.
money
;
//
拿到数据库上的帐户余额
//
进行数据操作
tmoney-=money;
//
模拟操作延时
try
{
Thread.sleep(200);
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
//
将修改的数据写入帐户
this
.
money
=tmoney;
System.
out
.println(
"
取款操作完毕,目前帐户余额:
"
+
this
.
money
);
}
}
//
正确结果
取款操作开始
...
取款操作完毕,目前帐户余额:
3000.0
存款操作开始
...
存款操作完毕,目前帐户余额:
6000.0
2-
通过定义同步语句块实现
//
定义资源对象
publicclass
Account {
privatelong
accNum
;
privatedouble
money
;
private
String
password
;
public
Account(
long
accNum,
double
money, String password) {
super
();
this
.
accNum
= accNum;
this
.
money
= money;
this
.
password
= password;
}
publiclong
getAccNum() {
return
accNum
;
}
publicvoid
setAccNum(
long
accNum) {
this
.
accNum
= accNum;
}
publicdouble
getMoney() {
return
money
;
}
publicvoid
setMoney(
double
money) {
this
.
money
= money;
}
public
String getPassword() {
return
password
;
}
publicvoid
setPassword(String password) {
this
.
password
= password;
}
}
//
取款线程
publicclass
GetMoneyPerson
extends
Thread{
private
Account
acc
;
public
GetMoneyPerson(Account acc,String personName) {
this
.setName(personName);
this
.
acc
= acc;
}
//
执行取款
publicvoid
run(){
synchronized
(
acc
) {
System.
out
.
println
(
"
取款操作开始
..."
);
double
tmoney =
acc
.getMoney();
//
拿到数据库上的帐户余额
//
进行数据操作
tmoney -= 2000;
//
模拟操作延时
try
{
Thread.sleep(200);
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
//
将修改的数据写入帐户
acc
.setMoney(tmoney);
System.
out
.println(
"
取款操作完毕,目前帐户余额:
"
+
acc
.getMoney());
}
}
}
//
存款线程
publicclass
SaveMoneyPerson
extends
Thread{
private
Account
acc
;
public
SaveMoneyPerson(Account acc,String personName) {
this
.setName(personName);
this
.
acc
= acc;
}
//
执行存款
publicvoid
run(){
synchronized
(
acc
){
System.
out
.println(
"
存款操作开始
..."
);
double
tmoney=
acc
.getMoney();
//
拿到数据库上的帐户余额
//
进行数据操作
tmoney+=3000;
//
模拟操作延时
try
{
Thread.sleep(200);
}
catch
(InterruptedException e) {
//
TODO
Auto-generated catch block
e.printStackTrace();
}
//
将修改的数据写入帐户
acc
.setMoney(tmoney);
System.
out
.println(
"
存款操作完毕,目前帐户余额:
"
+
acc
.getMoney());
}
}
}
//
执行
publicclass
TestGetSaveMoney {
publicstaticvoid
main(String[] args) {
Account acc=
new
Account(1001,5000,
"123"
);
GetMoneyPerson gThread=
new
GetMoneyPerson(acc,
"getMoneyPerson"
);
SaveMoneyPerson sThread=
new
SaveMoneyPerson(acc,
"saveMoneyPerson"
);
gThread.start();
sThread.start();
}
}
//
结果
取款操作开始
...
取款操作完毕,目前帐户余额:
3000.0
存款操作开始
...
存款操作完毕,目前帐户余额:
6000.0
我们会发现一般定义同步方法是在资源对象中定义,而同步语句块是在线程中定义,这两种方法都可以实现同步。