Java中线程同步的两种方式

algorain

Java中线程同步的两种方式

在多线程中,需要控制线程的访问次数,频率和优先级。比如在取钱时如果多个线程同时修改了账户余额,就会出现同步错误,所以Java中提供了线程同步的概念以保证某个资源在某一时刻只能由一个线程访问,保证共享数据的一致性。

下面是一个模拟多线程存取钱时的操作。

BankAccount.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.rain.demo;
/**
* Created by w-pc on 2017/02/24.
*
* 模拟银行账户
* */
public class BankAccount {
//银行账户
private String bankNo;
//银行余额
private double balance;
public BankAccount(String bankNo,double balance) {
this.bankNo = bankNo;
this.balance = balance;
}
public void setBankNo(String bankNo) {
this.bankNo = bankNo;
}
public void setBalance(double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
public String getBankNo() {
return bankNo;
}
}

NoSynBank.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.rain.demo;
/**
* Created by w-pc on 2017/02/24.
*/
public class NoSynBank extends Thread{
private BankAccount account;
private double money;
public NoSynBank(String name,BankAccount account,double money){
super(name);
this.account = account;
this.money = money;
}
public void run(){
//获取当前金额
double d = this.account.getBalance();
//判断存取金额是否大于余额
if(money<0 && d - money < 0){
System.out.println("操作失败");
return ;
}else{
d += money;
System.out.println(this.getName()+"操作成功,当前余额:"+d);
//设定休眠一毫秒,使其他线程得以执行
try {
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
this.account.setBalance(d);
}
}
public static void main(String[] args) {
//实例化一个账户
BankAccount myAccount = new BankAccount("60001002",5000);
NoSynBank t1 = new NoSynBank("T001",myAccount,-3000);
NoSynBank t2 = new NoSynBank("T002", myAccount, -3000);
NoSynBank t3 = new NoSynBank("T003",myAccount,1000);
NoSynBank t4 = new NoSynBank("T004",myAccount,-2000);
NoSynBank t5 = new NoSynBank("T005",myAccount,2000);
//将线程全部启动
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
//使用join方法等待所有线程执行完毕
try {
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}catch (InterruptedException e){
e.printStackTrace();
}
//打印出当前账户的余额
System.out.println("账户:"+myAccount.getBankNo()+",余额:"+myAccount.getBalance());
}
}
1
2
3
4
5
6
7
运行结果:
T002操作成功,当前余额:2000.0
T003操作成功,当前余额:6000.0
T001操作成功,当前余额:2000.0
T004操作成功,当前余额:3000.0
T005操作成功,当前余额:7000.0
账户:60001002,余额:7000.0

通过运行结果可以看出,余额发生错误,有时运行也可能没有问题,但没出现问题不等于没有问题,只要出现一个错误,那就是编程问题。

线程同步通常采用以下三种方式:同步代码块,同步方法,同步锁。

同步代码块:

1
2
3
synchronized(object){
//需要同步的代码块
}
  • synchronized是同步关键字

  • object是同步监视器,线程开始执行同步代码之前,必须先获得对同步监视器的锁定,

注意:任何时候都只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完后,该线程会释放对同步监视器的锁定。

SynBlockBank.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.rain.demo;

/**
* Created by w-pc on 2017/02/24.
*/
public class NoSynBank extends Thread{
private BankAccount account;
private double money;
public NoSynBank(String name,BankAccount account,double money){
super(name);
this.account = account;
this.money = money;
}
public void run(){
synchronized (this.account) {
//获取当前金额
double d = this.account.getBalance();
//判断存取金额是否大于余额
if (money < 0 && d - money < 0) {
System.out.println("操作失败");
return;
} else {
d += money;
System.out.println(this.getName() + "操作成功,当前余额:" + d);
//设定休眠一毫秒,使其他线程得以执行
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.account.setBalance(d);
}
}
}
public static void main(String[] args) {
//实例化一个账户
BankAccount myAccount = new BankAccount("60001002",5000);
NoSynBank t1 = new NoSynBank("T001",myAccount,-3000);
NoSynBank t2 = new NoSynBank("T002", myAccount, -3000);
NoSynBank t3 = new NoSynBank("T003",myAccount,1000);
NoSynBank t4 = new NoSynBank("T004",myAccount,-2000);
NoSynBank t5 = new NoSynBank("T005",myAccount,2000);
//将线程全部启动
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
//使用join方法等待所有线程执行完毕
try {
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}catch (InterruptedException e){
e.printStackTrace();
}
//打印出当前账户的余额
System.out.println("账户:"+myAccount.getBankNo()+",余额:"+myAccount.getBalance());
}
}

运行结果:

1
2
3
4
5
6
T001操作成功,当前余额:2000.0
T005操作成功,当前余额:4000.0
T004操作成功,当前余额:2000.0
T003操作成功,当前余额:3000.0
T002操作成功,当前余额:0.0
账户:60001002,余额:0.0

同步方法

[访问修饰符] synchronized 返回类型 方法名([参数列表]){ //方法体 } * synchronized关键字修饰的实例方法无须显示的指定同步监视器,同步方法的同步监视器是this,即该方法所属的对象 * 一旦一个线程进入一个实例的任何同步方法,其他线程将不能进入该实例的所有同步方法,但该实例的非同步方法仍然能够被调用 * 使用同步方法可以非常方便的实现线程安全,一个具有同步方法的类也被称为“线程安全的类”,该类的对象可以被多个线程安全的访问,其每个线程调用该对象的方法后都将得到正确的结果

代码:

BankAccount.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.rain.demo;
/**
* Created by w-pc on 2017/02/24.
*
* 模拟银行账户
* */
public class BankAccount {
//银行账户
private String bankNo;
//银行余额
private double balance;
public BankAccount(String bankNo,double balance) {
this.bankNo = bankNo;
this.balance = balance;
}
public void setBankNo(String bankNo) {
this.bankNo = bankNo;
}
public void setBalance(double balance) {
this.balance = balance;
}
public double getBalance() {
return balance;
}
public String getBankNo() {
return bankNo;
}
public synchronized void access(double money){
if (money < 0 && balance - money < 0) {
System.out.println(Thread.currentThread().getName()+"操作失败");
return;
} else {
balance += money;
System.out.println(Thread.currentThread().getName()+"操作成功,当前余额:" + balance);
//设定休眠一毫秒,使其他线程得以执行
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

NosynBank.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.rain.demo;
/**
* Created by w-pc on 2017/02/24.
*/
public class NoSynBank extends Thread{
private BankAccount account;
private double money;
public NoSynBank(String name,BankAccount account,double money){
super(name);
this.account = account;
this.money = money;
}
public void run(){
this.account.access(money);
}
public static void main(String[] args) {
//实例化一个账户
BankAccount myAccount = new BankAccount("60001002",5000);
NoSynBank t1 = new NoSynBank("T001",myAccount,-3000);
NoSynBank t2 = new NoSynBank("T002", myAccount, -3000);
NoSynBank t3 = new NoSynBank("T003",myAccount,1000);
NoSynBank t4 = new NoSynBank("T004",myAccount,-2000);
NoSynBank t5 = new NoSynBank("T005",myAccount,2000);
//将线程全部启动
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
//使用join方法等待所有线程执行完毕
try {
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
}catch (InterruptedException e){
e.printStackTrace();
}
//打印出当前账户的余额
System.out.println("账户:"+myAccount.getBankNo()+",余额:"+myAccount.getBalance());
}
}

运行结果:

1
2
3
4
5
6
T001操作成功,当前余额:2000.0
T003操作成功,当前余额:3000.0
T005操作成功,当前余额:5000.0
T004操作成功,当前余额:3000.0
T002操作成功,当前余额:0.0
账户:60001002,余额:0.0
  • 注意:synchronized锁定的是对象,而不是方法或代码块,synchronized也可以修饰类,当用synchronized修饰类时,表示这个类的所有方法都是synchronized的
  • Title: Java中线程同步的两种方式
  • Author: algorain
  • Created at: 2017-02-24 09:46:42
  • Updated at: 2023-05-14 21:39:50
  • Link: http://www.rain1024.com/2017/02/24/java-article62/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments