您的位置:首页 > 编程语言 > Java开发

Java多线程之线程同步

2016-07-03 21:13 495 查看

线程同步

线程安全问题

多线程情况下,当多个线程访问同一个数据时,很容易出现线程安全问题。

经典的问题——银行取钱问题。几个人同时取一个帐号里的钱,就可能出现问题。下面模拟一下。

//账户类
public class Account {
private String accountNo;
private double balance;

public Account() {
}

public Account(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}

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;
}

@Override
public int hashCode() {
return accountNo.hashCode();
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj != null && obj.getClass() == Account.class) {
Account target = (Account) obj;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}

//模拟取钱线程
public class DrawThread extends Thread {

private Account account;

private double withDraw;

public DrawThread(String name,Account account, double withDraw) {
super(name);
this.account = account;
this.withDraw = withDraw;
}

@Override
public void run() {
//账户余额大于取钱数目
if(account.getBalance() >= withDraw){
System.err.println(getName() + "取钱成功!吐出:" +withDraw+" 文");
//让线程切换
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}

//减去取出的钱
account.setBalance(account.getBalance() - withDraw);

System.out.println("\t 余额为: " + account.getBalance());

} else {
System.out.println(getName() + "你的余额不足!");
}
}
}

//测试
public class DrawTest {

public static void main(String[] args) {
Account account = new Account("12345678",10000);

new DrawThread("A", account, 8000).start();
new DrawThread("B", account, 8000).start();
}
}


结果:



原因:

- run()线程执行体不具有同步安全性

- 程序中有两个并发线程在修改Account对象

同步监视器

为解决上述问题,Java多线程引入同步监视器。其目的:

阻止两个线程对同一个共享资源并发访问

通常,推荐使用可能被并发访问的共享资源充当同步监视器。

流程:

加锁->修改->释放锁

同步代码块

使用同步监视器的通用方法就是同步代码块。其含义是:线程开始执行同步代码块前,必须先获得对同步监视器的锁定。因此,修改线程类,使用同步代码块给Account对象上锁。

public class DrawThread extends Thread {
...
@Override
public void run() {

synchronized (account) {
// 账户余额大于取钱数目
if (account.getBalance() >= withDraw) {
System.err.println(getName() + "取钱成功!吐出:" + withDraw + " 文");

try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}

// 减去取出的钱
account.setBalance(account.getBalance() - withDraw);

System.out.println("\t 余额为: " + account.getBalance());

} else {
System.out.println(getName() + "你的余额不足!");
}
}
}
...


同步方法

与同步代码块对应,还有同步方法。使用sychronized关键字修饰方法,无须显示指定同步监视器,同步方法的同步监视器就是this,就是调用该方法的对象。

那么,可以修改Account类,提供一个线程安全的draw方法

...
public synchronized void draw(double withDraw){
// 账户余额大于取钱数目
if (balance >= withDraw) {
System.err.println(Thread.currentThread().getName() + "取钱成功!吐出:" + withDraw + " 文");

try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}

// 减去取出的钱
balance = balance - withDraw;

System.out.println("\t 余额为: " + balance);

} else {
System.out.println(Thread.currentThread().getName() + "你的余额不足!");
}
}
...


同步锁Lock

JDK 1.5开始,Java提供了更强大的线程同步机制

- 显式定义同步锁对象来实现同步

- 同步锁由Lock对象充当

与synchronized方法,synchronized代码块相比的优势:

- 更广泛的锁定操作

- 实现更灵活的的结构

- 可以具有差别很大的属性

- 支持多个相关的Condition对象

Lock是控制多个线程对共享资源访问的工具。锁提供了独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源前,须先获得Lock对象

JDK 1.5 提供了Lock,ReadWriteLock两个根接口,ReentrantLock可重入锁是Lock的实现类,ReentrantReadWriteLock是ReadWriteLock的实现类。

JDK 1.8 提供StampedLock类,大多数情况下,可替代传统的ReentrantReadWriteLock.

ReentrantReadWriteLock为读写操作提供3种锁模式:

- Writing

- ReadingOptimistic

- Reading

下面以ReentrantReadWriteLock为例,修改Account类

public class Account {

private final ReentrantLock lock = new ReentrantLock();
...

public void draw(double withDraw){

// 加锁
lock.lock();
try {
// 账户余额大于取钱数目
if (balance >= withDraw) {
System.err.println(Thread.currentThread().getName() + "取钱成功!吐出:" + withDraw + " 文");

try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}

// 减去取出的钱
balance = balance - withDraw;

System.out.println("\t 余额为: " + balance);

} else {
System.out.println(Thread.currentThread().getName() + "你的余额不足!");
}
} finally {
// 解锁
lock.unlock();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程