OOP19-Java多线程

多线程-线程间的同步

Java多线程ATM转账
在没有同步时数据错误
账户有转账等功能, 文件Account.java

public class Account {

    protected String id;
    protected long balance;

    public Account(String id, long balance) {
        this.id = id;
        this.balance = balance;
    }

    public long getBalance() {
        return this.balance;
    }

    public void deposit(long amount) {
        this.balance += amount;
    }

    public void withdraw(long amount) {
        this.balance -= amount;
    }

    public void transfer( Account to, long amount) {
        this.withdraw(amount);
        to.deposit(amount);
    }
}

AtmThread模仿大量并发转账, 文件AtmThread.java

public class AtmThread extends Thread {

    Account accountFrom;
    Account accountTo;

    public AtmThread(Account from, Account to) {
        this.accountFrom = from;
        this.accountTo = to;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            this.accountFrom.transfer(accountTo, 1);
        }
        System.out.println("mission complete.");
    }
}

让2个线程在2个账户之间进行多次并发转账的主函数. 文件ATMMain.java

public class ATMMain {

    public static void main(String[] args) {
        Account account1 = new Account("A001", 10000);
        Account account2 = new Account("A002", 10000);
        AtmThread atmThread1 = new AtmThread(account1, account2);
        AtmThread atmThread2 = new AtmThread(account2, account1);
        atmThread1.start();
        atmThread2.start();
        try {//主线程等待1000ms
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            System.out.println(ex);
        }
        System.out.println(account1.getBalance());
        System.out.println(account2.getBalance());
    }
}

运行结果
每次的运行结果并不相同, 特别是两个账户的余额的总和也不一定等于20000.
第1次运行结果:

mission complete.
mission complete.
9827
9779

第2次运行结果:

mission complete.
mission complete.
10045
9887

第3次运行结果:

mission complete.
mission complete.
9835
10112

恰当的同步后数据正常
方案1. 对转账的2个账户进行同步
根据账号的大小, 依次锁定转账涉及的2个账户.
如果对2各个账户同步(锁定)没有顺序, 可能发生死锁.
如果只对1个账户的锁定, 那么达不到锁定效果.

具体方案: 修改Account, 增加同步转账功能的文件Account.java

public class Account {

    protected String id;
    protected long balance;

    public Account(String id, long balance) {
        this.id = id;
        this.balance = balance;
    }

    public long getBalance() {
        return this.balance;
    }

    public void deposit(long amount) {
        this.balance += amount;
    }

    public void withdraw(long amount) {
        this.balance -= amount;
    }

    /**
     *
     * @param s1
     * @param s2
     * @return 1 : s1>s2, -1: s1<s2, 0: a1==a2
     */
    public int compare(String s1, String s2) {
        int minLength = s1.length() > s2.length() ? s2.length() : s1.length();
        for (int i = 0; i < minLength; i++) {
            if (s1.charAt(i) > s2.charAt(i)) {
                return 1;
            } else if (s1.charAt(i) < s2.charAt(i)) {
                return -1;
            }
        }
        if (s1.length() > s2.length()) {
            return 1;
        } else if (s2.length() > s1.length()) {
            return -1;
        } else {
            return 0;
        }
    }

    public void transfer(Account to, long amount) {
        Account a1;
        Account a2;
        switch (compare(this.id, to.id)) {
            case 1:
                a1 = this;
                a2 = to;
                break;
            case -1:
                a1 = to;
                a2 = this;
                break;
            case 0:
            default:
                return;
        }
        synchronized (a1) {
            synchronized (a2) {
                this.withdraw(amount);
                to.deposit(amount);
            }
        }
    }
}

运行结果:

mission complete.
mission complete.
10000
10000

方案2. 对转账的方法进行同步
在AtmThread中增加一个同步的(synchronized)的静态的(static)转账功能.
如果方法不是static的, 那么不同的线程对象有不同转账功能,达不到同步所有转账操作的作用.
这个解决方案看起来比上面的优雅. 但是转账功能1次只能被1个线程使用, 在有大量并发转账时, 难以在CPU的多个核心上用多线程转账提高效率.

具体方案:修改文件AtmThread.java, 增加方法static synchronized void transfer2(),并使用transfer2()进行转账.

public class AtmThread extends Thread {

    Account accountFrom;
    Account accountTo;

    public AtmThread(Account from, Account to) {
        this.accountFrom = from;
        this.accountTo = to;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            AtmThread.transfer2(accountFrom , accountTo, 1);
        }
        System.out.println("mission complete.");
    }
    public static synchronized void transfer2(Account from, Account to, long amount) {
        from.withdraw(amount);
        to.deposit(amount);
    }
}

运行结果:

mission complete.
mission complete.
10000
10000

方案3. 为每个对象的存和取功能增加同步
depisit()和withdraw()只涉及1个账户的操作. 不需要同步(锁定)from和to两个账户.
因此不存在死锁问题. 使用synchronized修饰deposit()函数, 是锁定当前对象(this), 执行deposit(). 为depisit()和withdraw()增加synchronized, 则对一个对象的存取一次只能执行一个方法, 不会产生多个线程同时修改balance, 导致balance的值发生错误的问题.

此种解决方案是推荐的解决方案.

具体方案: 为depisit()和withdraw()增加synchronized, 修改Account.java

public class Account {

    protected String id;
    protected long balance;

    public Account(String id, long balance) {
        this.id = id;
        this.balance = balance;
    }

    public long getBalance() {
        return this.balance;
    }

    public synchronized void deposit(long amount) {
        this.balance += amount;
    }

    public synchronized void withdraw(long amount) {
        this.balance -= amount;
    }

    public void transfer(Account to, long amount) {
        this.withdraw(amount);
        to.deposit(amount);
    }
}

运行结果:

mission complete.
mission complete.
10000
10000

transfer中包括对deposit和withdraw的调用, 如果在transfer前加上synchronized修饰, 会导致对this对象和to对象的依次锁定. 如果有另一个线程同时对这两个对象相反的调用transfer, 可能会导致两个对象依次被两个线程锁定, 互相等待要锁定对方线程已经锁定的资源, 导致死锁.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值