Java 并发编程之图形界面应用程序及死锁问题

不知道为什么这本书还要讲一个界面应用程序,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锁之前,先获得这个加时赛锁。从而保证每次只有一个线程以未知的顺序获得这两个锁。

时间: 2024-10-26 11:38:55

Java 并发编程之图形界面应用程序及死锁问题的相关文章

(更新中)谈谈个人对java并发编程中(管程模型,死锁,线程生命周期等问题) 见解

之前未曾接触过多线程编程  公司的项目开始用到多线程,所以自己谈谈个人对于并发编程的见解. 并发编程会导致线程不安全,常说的线程不安全指的是  多个线程操作一个共享数据,导致线程之间的读取到的数据不一致. 并发编程导致线程不安全的根源   可见性  原子性    有序性 1 .可见性     cpu缓存导致. 一般cpu缓存中进行操作之后再将数据写到内存,在多核服务器中  每个线程都会分配一个cpu  都会在各自的cpu中进行处理再将数据统一写到内存中.每个cpu缓存中的数据都是不可见的.导致最

[Java并发编程实战]构建一个高效可复用缓存程序(含代码)

[Java并发编程实战]构建一个高效可复用缓存程序(含代码) 原文地址:https://www.cnblogs.com/chengpeng15/p/9915800.html

java进阶08 GUI图形界面

图形化用户界面(GUI) 简而言之,就是可视化编程. 要想实现可视化界面(窗口),需要用到JFrame类. package Frame; public class JFrame1 { public static void main(String[] args){ UI ui=new UI(); } } 先建一个主函数,而主函数中的操作只有一句代码.这样做,既能直观又方便后期修改. 接下来是UI类的实现 package Frame; import javax.swing.JFrame; publi

java进阶09 GUI图形界面 布局管理器之BorderLayout

前面虽然实现了窗口,但是一般的应用有很多其他东西,如按钮,输入框之类的. 而这些都是建立在一个东西上面的,那就是布局管理器. 常用的布局管理器有3个 BorderLayout:边界布局管理器 FlowLayout:流式布局管理器 GridLayout:网格布局管理器 今天先说说BorderLayout 我们先修改下UI类,这次是最后一次修改,这次会将UI类与管理器和控件彻底分开,再也不用操作UI类 package Frame; import javax.swing.JFrame; public

《java并发编程实战》笔记(一)

最近在看<java并发编程实战>,希望自己有毅力把它读完. 线程本身有很多优势,比如可以发挥多处理器的强大能力.建模更加简单.简化异步事件的处理.使用户界面的相应更加灵敏,但是更多的需要程序猿面对的是安全性问题.看下面例子: public class UnsafeSequence { private int value; /*返回一个唯一的数值*/ public int getNext(){ return value++; } } UnsafeSequence的问题在于,如果执行时机不对,那么

Java并发编程 Volatile关键字解析

volatile关键字的两层语义 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的. 2)禁止进行指令重排序. 根据volatile的语义,我们可以看到,volatile主要针对的是并发三要素(原子性,可见性和有序性)中的后两者有实际优化作用. 可见性: 线程本身并不直接与主内存进行数据的交互,而是通过线程的工作内存来完成相应的操作.

6、Java并发编程:volatile关键字解析

Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情.由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识,然后分析了volatil

7、Java并发编程:深入剖析ThreadLocal

Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用方法和实现原理.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两个应用场景. 以下是本文目录大纲: 一.对ThreadLocal的理解 二.深入解析ThreadLocal类 三.ThreadLocal的应用场景 若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者

java并发编程实战学习(3)--基础构建模块

转自:java并发编程实战 5.3阻塞队列和生产者-消费者模式 BlockingQueue阻塞队列提供可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put方法将阻塞直到空间可用:如果队列为空,那么take方法将阻塞直到有元素可用.队列可以是有界的也可以是无界的. 如果生产者生成工作的速率比消费者处理工作的速率款,那么工作项会在队列中累计起来,最终好紧内存.同样,put方法的阻塞特性也极大地简化了生产者的编码.如果使用有界队列,当队列充满时,生产者将阻