Java多线程之线程同步

线程同步

线程安全问题

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

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

//账户类
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();
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值