Java多线程——同步(一)
来源:互联网 发布:java数据同步 编辑:程序博客网 时间:2024/04/29 23:23
可能我们在开发项目进行过程中,通常会冒出这样的困惑:应该选择效率,还是选择质量?会不会有偷懒的思维,觉得把一些摸不清头绪,不知道怎么写的代码片段去掉,可以节省许多时间,更早的完成项目计划,其实以前我也是这么想的,但最近我开始意识到,这个问题的纠结之处不在于选择困难,而在于问题本身是个伪命题。
什么是“质量”呢?对于我们程序员来说可能是测试通过率,变量命名,代码格式化,组件化,查找bug,程序测试等。也有可能是程序的可扩展性,服务延时,产品功能的完整程度。前一种围绕代码的问题可以看成“代码质量”问题,第二种可以看成“执行质量”。
从“代码质量”来看,走捷径的偷懒思维,其实是种十分短视的做法。含糊绕过某个问题,你可能一时觉得省事不少,但到头来,往往发现因此搅乱了系统而要花费更多的时间来一行行的检查代码,找出bug,甚至重新调整整理逻辑框架。所以牺牲代码质量换取速度通常是得不偿失的。
相反地,高质量的代码其实是可以帮助你节省时间的。统一代码规范和变量命名,不仅可以帮到别的程序员,还可以帮到以后的你,更好地理解你现在写下的代码,经过严密思考而设计出的轻量级代码架构,则可以让你在迭代产品的时候获得更高的效率,更清晰地了解该从何入手,而不是到数据库里找需要替代的地方,而高测试通过率还可以给你充足的自信去调整代码,减少bug数量,减少QA时间。
这是转账线程类中run方法(随机选择一个目标账户和一个随机账户调用转账方法,然后睡眠):
当它运行时,我们不清楚某一时间某一个账户有多少钱,但是,所有账户的总金额应该是保持不变的。
TransferRunnable类:
这一结构确保任何时刻只有一个线程进入。一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句。当其他线程调用lock时,它们被阻塞,直到第一个线程释放锁对象。
再次运行程序后,发现不会出现错误,线程之间不会互相影响了。
什么是“质量”呢?对于我们程序员来说可能是测试通过率,变量命名,代码格式化,组件化,查找bug,程序测试等。也有可能是程序的可扩展性,服务延时,产品功能的完整程度。前一种围绕代码的问题可以看成“代码质量”问题,第二种可以看成“执行质量”。
从“代码质量”来看,走捷径的偷懒思维,其实是种十分短视的做法。含糊绕过某个问题,你可能一时觉得省事不少,但到头来,往往发现因此搅乱了系统而要花费更多的时间来一行行的检查代码,找出bug,甚至重新调整整理逻辑框架。所以牺牲代码质量换取速度通常是得不偿失的。
相反地,高质量的代码其实是可以帮助你节省时间的。统一代码规范和变量命名,不仅可以帮到别的程序员,还可以帮到以后的你,更好地理解你现在写下的代码,经过严密思考而设计出的轻量级代码架构,则可以让你在迭代产品的时候获得更高的效率,更清晰地了解该从何入手,而不是到数据库里找需要替代的地方,而高测试通过率还可以给你充足的自信去调整代码,减少bug数量,减少QA时间。
我们现阶段还是注重好代码的质量,执行质量就不多说啦,当然这些都是题外话,想和各位分享!接下来还是进入正题。
什么是竞争条件
在大多数实际的多线程应用中,两个或两个以上的线程需要共享对同一数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用了修改该对象的方法,将会发生什么?可能就会产生错误的对象。这样的情况通常称为竞争条件。
我们来模拟一个有若干账户的银行,随机在这些账户之间转账,每一个账户一个线程。每一次交易,会从线程所服务的账户中随机转移一定的金额到另一个账户中。
这是Bank类中转账的方法:
public void transfer(int from, int to, double amount) { if (accounts[from] < amount) { return; } System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf("转账金额: %10.2f 转出账户: %d 转入账户: %d", amount, from, to); accounts[to] += amount; System.out.printf(" 最后的金额: %10.2f%n", getTotalBalance()); }
这是转账线程类中run方法(随机选择一个目标账户和一个随机账户调用转账方法,然后睡眠):
@Override public void run() { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = maxAmount * Math.random(); bank.transfer(fromAccount, toAccount, amount); try { Thread.sleep((int) (DELAY * Math.random())); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }
当它运行时,我们不清楚某一时间某一个账户有多少钱,但是,所有账户的总金额应该是保持不变的。
下面是例子的完整代码:
Bank类:
/** * @author XzZhao */public class Bank { private final double[] accounts; public Bank(int n, double initialBalance) { accounts = new double[n]; for (int i = 0; i < accounts.length; i++) { accounts[i] = initialBalance; } } public void transfer(int from, int to, double amount) { if (accounts[from] < amount) { return; } System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf("转账金额: %10.2f 转出账户: %d 转入账户: %d", amount, from, to); accounts[to] += amount; System.out.printf(" 最后的金额: %10.2f%n", getTotalBalance()); } public double getTotalBalance() { double sum = 0; for (double a : accounts) { sum += a; } return sum; } public int size() { return accounts.length; }}
TransferRunnable类:
/** * @author XzZhao */public class TransferRunnable implements Runnable { private final Bank bank; private final int fromAccount; private final double maxAmount; private final int DELAY = 10; public TransferRunnable(Bank bank, int fromAccount, double maxAmount) { super(); this.bank = bank; this.fromAccount = fromAccount; this.maxAmount = maxAmount; } @Override public void run() { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = maxAmount * Math.random(); bank.transfer(fromAccount, toAccount, amount); try { Thread.sleep((int) (DELAY * Math.random())); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }}Test:
/** * @author XzZhao */public class UnsynchBankTest { public static final int NACCOUNTS = 100; public static final double INITIAL_BALANCE = 1000; public static void main(String[] args) { Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE); for (int i = 0; i < NACCOUNTS; i++) { TransferRunnable r = new TransferRunnable(b, i, INITIAL_BALANCE); Thread t = new Thread(r); t.start(); } }}程序出错:总金额发生变化
锁对象
用ReentrantLock保护代码块的结构:
Lock myLock = new ReentrantLock(); myLock.lock(); try { // work code } finally { myLock.unlock(); }
这一结构确保任何时刻只有一个线程进入。一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句。当其他线程调用lock时,它们被阻塞,直到第一个线程释放锁对象。
使用一个锁来保护Bank类的transfer方法:
/** * @author XzZhao */public class Bank { ... private final Lock bankLock = new ReentrantLock(); public void transfer(int from, int to, double amount) { if (accounts[from] < amount) { return; } bankLock.lock(); try { System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf("转账金额: %10.2f 转出账户: %d 转入账户: %d", amount, from, to); accounts[to] += amount; System.out.printf(" 最后的金额: %10.2f%n", getTotalBalance()); } finally { bankLock.unlock(); } } ...}
再次运行程序后,发现不会出现错误,线程之间不会互相影响了。
锁是可重入的,因为线程可以重复地获得已经持有的锁。锁保持一个持有计数来跟踪lock方法的嵌套调用。线程在每一次调用lock都要调用unlock来释放锁。所以,被一个锁保护的代码可以调用另一个使用相同的锁的方法。
条件对象
我们来修改一下例子,我们使用条件对象来避免选择没有足够资金的账户作为转出账户。
Bank类:
/** * @author XzZhao */public class Bank { private final double[] accounts; private final Lock bankLock; private final Condition sufficientFunds; public Bank(int n, double initialBalance) { accounts = new double[n]; for (int i = 0; i < accounts.length; i++) { accounts[i] = initialBalance; } bankLock = new ReentrantLock(); sufficientFunds = bankLock.newCondition(); // 获取一个和该锁相关的条件对象 } public void transfer(int from, int to, double amount) throws InterruptedException { bankLock.lock(); try { while (accounts[from] < amount) { sufficientFunds.await(); // 将该线程放到条件的等待集中 } System.out.print(Thread.currentThread()); accounts[from] -= amount; System.out.printf("转账金额: %10.2f 转出账户: %d 转入账户: %d", amount, from, to); accounts[to] += amount; System.out.printf(" 最后的金额: %10.2f%n", getTotalBalance()); sufficientFunds.signalAll(); // 解除该条件的等待集中的所有线程的阻塞状态 } finally { bankLock.unlock(); } } public double getTotalBalance() { bankLock.lock(); try { double sum = 0; for (double a : accounts) { sum += a; } return sum; } finally { bankLock.unlock(); } } public int size() { return accounts.length; }}TransferRunnable类:
/** * @author XzZhao */public class TransferRunnable implements Runnable { private final Bank bank; private final int fromAccount; private final double maxAmount; private final int DELAY = 10; public TransferRunnable(Bank bank, int fromAccount, double maxAmount) { super(); this.bank = bank; this.fromAccount = fromAccount; this.maxAmount = maxAmount; } @Override public void run() { try { while (true) { int toAccount = (int) (bank.size() * Math.random()); double amount = maxAmount * Math.random(); bank.transfer(fromAccount, toAccount, amount); Thread.sleep((int) (DELAY * Math.random())); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }}
0 0
- Java多线程——同步(一)
- [java多线程]多线程同步(一)——synchronized
- Java多线程(一)--线程同步
- Java多线程——同步(二)
- 多线程同步(一)
- java技术深入(一)——java多线程(五)——线程同步(一)
- java技术深入(一)——java多线程(五)——线程同步(二)
- JAVA——多线程编程之同步:同步代码块与同步函数(二)
- 多线程和同步(一)
- java多线程—Java 多线程同步的五种方法
- Java多线程干货系列—(一)Java多线程基础
- Java笔记(十一)——多线程同步
- (新手)java多线程基础知识——调度与同步
- Java多线程(1)——同步机制
- Java多线程(五)——线程同步
- JAVA多线程编程(二)——同步与通信
- Java多线程(3)——同步与synchronized关键字
- JAVA多线程同步——学无止境
- shell中的括号
- 第18题:跳跃游戏
- 搜芯网与电子工程师的缘分
- LeetCode - Binary Tree Inorder Traversal
- Robotium--takeScreenshot(截图)
- Java多线程——同步(一)
- Java8集合中的Lambda表达式
- Python连接mssql数据库乱码(中文变问号)解决方法
- [LeetCode]35.Search Insert Position
- NFC
- Java线程的5个使用技巧
- Android中全屏 及 去掉标题栏
- 在应用中升级ARM-Linux内核映像
- Java解析JSON