不知道为什么这本书还要讲一个界面应用程序,Java的界面做的很糟糕,效率低下,而且界面是java的弱项,可能是因为这里边是有一些并发编程的知识吧。
为什么GUI是单线程的
无论是Swing还是AWT都是单线程的。但它不仅限于在java中,在Qt,NexiStep,macOs CoCoa X windows以及其它环境中的GUI框架都是单线程的,许多人都曾经尝试编写多线程的GUI框架,但最终都由于竞态条件和死锁导致的稳定性问题而又重新回到单线程的事件队列模型:采用一个专门的线程从队列中抽取事件,并将它们转发到应用程序定义的事件处理器。
这些问题主要发生在由于锁顺序问题而引发的死锁:
比如:修改程序背景色:
顺序是 应用程序发出修改背景请求————组件类————操作系统进行绘制。
然后 操作系统绘制组件类————组件类————刷新应用程序界面。
另一个方面又要确保每个对象都是线程安全的,从而导致锁顺序的不一致而引发死锁。
关于界面应用程序就写这么多吧,因为觉得这个实用不是实用性很大,知道上面这个知识就能在开发GUI框架的时候少走很多弯路了。
避免活跃性危险
安全性和活跃性是相对的,我们用加锁机制确保线程安全性的同时也可能因为死锁等原因产生活跃性问题
死锁
有一个很典型的哲学家进餐问题用来描写死锁。
5个哲学家坐一桌,桌上只有5根筷子,他们吃饭要用一双筷子,他们时而思考,时而吃饭,当一个在思考的时候 ,他旁边的人就可以吃饭了,下面死锁的现象就是,他们每个人都快速的抓住左边的筷子,然后每个人都在等待右边的筷子,然而他们谁也没吃上饭,所以谁都不会放下筷子,就这么僵持着,这就是死锁了。
在数据库系统的设计中考虑了监测死锁以及从死锁中恢复。在执行事务时,如果在某个事务上发生了死锁的问题,那么 它会先中止这个事务,然后执行其它的,当其它的事务都执行完毕的时候 ,回来再重新执行这个刚才 被抛弃的事务。
不过JVM没有这套系统,当一组java线程发生死锁时,这些线程将永不能使用了。根据线程的工作不同,应用程序可能完全停止。唯一的解决办法就是重启。
线顺序死锁
举个很简单的栗子:哈哈想了很多办法才让这个锁能锁上,就是放大两个对象之间的获取时间。
public class LeftRightDealLock { private final Object right = new Object(); private final Object left = new Object(); public void leftRight() { synchronized (left) { try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (right) { dosomething(); } } } public void Rightleft() { synchronized (right) { try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } synchronized (left) { dosomething(); } } } private void dosomething() { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { System.out.println("dosomething" + i); } } public static void main(String[] args) { final LeftRightDealLock lrdl = new LeftRightDealLock(); new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub lrdl.leftRight(); } }).start(); new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub lrdl.Rightleft(); } }).start(); } }
动态的锁顺序死锁
以转账操作为例
public void transferMoney(Account formaccaount, Account toaccount, DollarAmount amount) { synchronized (formaccaount) { synchronized (toaccount) { if (formaccaount.getBalance.compareTo(amount) < 0) { throw new InsufficientFundsExcetion(); } else { fromaccount.debit(amount); toaccount.credit(amount); } } } }
看起来似乎没有什么问题,如果两个人互相往对方的账户里面转账的时候,那么就又变成了上面那个RIghtleftDeadLock的问题了。
解决办法:通过锁顺序来避免死锁,解铃还需系铃人嘛,既然是锁顺序引起的,那么规定一个固定的锁顺序就能解决这个问题了。
public class TestLock { private static final Object tieLock = new Object(); private Account fromacct; private Account toacct; public void transferMoney(final Account fromacct, final Account toacct, final DollarAmount amount) { this.fromacct = fromacct; this.toacct = toacct; class Helper { public void transfer() { if (fromacct.getBalance().compareTo(amount) < 0) { throw new InsufficientFundsExcetion(); } else { fromacct.debit(amount); toacct.credit(amount); } } } int fromHash = System.identityHashCode(fromacct); int toHash = System.identityHashCode(toacct); if (fromHash < toHash) { synchronized (fromacct) { synchronized (toacct) { new Helper().transfer(); } } } else if (fromHash > toHash) { synchronized (toacct) { synchronized (fromacct) { new Helper().transfer(); } } } else { synchronized (tieLock) { synchronized (fromacct) { synchronized (toacct) { new Helper().transfer(); } } } } } private class DollarAmount { } private abstract class Account { Comparable<DollarAmount> getBalance() { return new Comparable<DollarAmount>() { @Override public int compareTo(DollarAmount o) { // TODO Auto-generated method stub return 0; } }; } abstract void debit(DollarAmount a); abstract void credit(DollarAmount a); } }
改了一点只保证放在EClipse里不出红字。不过那个异常没有写。主要是看代码嘛、
System.identityHashCode方法就是获得object里面hashcode的结果。极少数情况下,两个对象可能有相同的散列值。采用加时赛锁。在获得两个Account锁之前,先获得这个加时赛锁。从而保证每次只有一个线程以未知的顺序获得这两个锁。