java线程

java线程: boolean isAlive()判断一个线程是不是活动的,线程启动并不意味着线程正在运行或可以运行 ----------------------------------------------------- synchronized: 使用synchronized保证同时只有单个线程执行这个方法,从而实现线程安全(但是会造成阻塞,甚至死锁) 同步的使用场景:当实现SOCKET连接的时候经常用到.Socket连接一般使用异步通信方式。 被synchronized声明的方法被称为同步方法,被其修饰的代码块称为同步语句 同步方法尽量不要使用,比如: java.util.Hashtable和java.util.Vector中提供的方法都是受互斥保护的,因此除非确实需要使用它们,否则尽量不用。开发者只需要使用java.util.HashMap和java.util.ArrayList即可。当然,java.util.Collections中的同步方法也使用了synchronized关键字。 ------------------------------------------------------------ 获取当前线程的对象的方法是:Thread.currentThread(); 不能使用来达到同步的方法有: 1)sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。 sleep()是静态方法,只能控制当前正在运行的线程。 2)当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。 Java 支持 10 个优先级,基层操作系统支持的优先级可能要少得多,这样会造成一些混乱。因此,只能将优先级作为一种很粗略的工具使用。最后的控制可以通过明智地使用 yield() 函数来完成。通常情况下,请不要依靠线程优先级来控制线程的状态。 3)yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。 4)调用join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。 比如在主线程main中加入t.join().则会保证main线程停止执行,知道main线程所加入的t线程完成为止。 -------------------------------------------------------------------- 一、同步问题提出 对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。 状态依赖: 只要看到这种必须使用一种方法的结果作为另一种讲法的输入条件的样式,它就是一个 状态依赖,就必须保证至少在调用这两种方法期间元素的状态没有改变。一般来说,做到这一点的唯一方法在调用第一个方法之前是独占性地锁定对象,一直到调用了后一种方法以后。在上面的迭代 Vector 元素的例子中,您需要在迭代过程中同步 Vector 对象。 Volatile 比同步更简单,只适合于控制对基本变量(整数、布尔变量等)的单个实例的访问。当一个变量被声明成 volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,而任何对该变量的读取也都绕过高速缓存,直接取自主内存。这表示所有线程在任何时候看到的 volatile 变量值都相同。 ------------------------------------------------------------------ 附:Volatile的使用: --------------------------------------------------------------------- 如 java.util 中的 Collection 类,不在内部使用同步,通过每次访问共享集合中的方法时使用同步,可以在多线程应用程序中使用 Collection 类。对于任何给定的集合,每次必须用同一个锁进行同步。通常可以选择集合对象本身作为锁。 通常适当的同步并不只是意味着同步每个方法。 Collections 类提供了一组便利的用于 List、Map 和 Set 接口的封装器。您可以用 Collections.synchronizedMap 封装 Map,它将确保所有对该映射的访问都被正确同步。 如果类的文档没有说明它是线程安全的,那么您必须假设它不是。 简单的线程安全的高速缓存:以后的访问就会从高速缓存中检索它,而不是每次都全部地装入它。对共享高速缓存的每个访问都受到 synchronized 块保护。高速缓存?????? public class SimpleCache { private final Map cache = new HashMap(); public Object load(String objectName) { // load the object somehow } public void clearCache() { synchronized (cache) { cache.clear(); } } public Object getObject(String objectName) { synchronized (cache) { Object o = cache.get(objectName); if (o == null) { o = load(objectName); cache.put(objectName, o); } } return o; } } 只要在几个线程之间共享非 final 变量,就必须使用 synchronized(或 volatile)以确保一个线程可以看见另一个线程做的更改。 final变量没有被更改,就不必要使用synchronized. 因为不可改变的类使并发编程变得非常简单。因为不能更改它们的字段,所以就不需要担心把状态的更改从一个线程传递到另一个线程。在正确构造了对象之后,可以把它看作是常量。比如缓存中的词典,若不要进行修改,则将词典缓存定义为常量。 使用 volatile 还不够 ?????如何使用volatile.,如果正在保护一个基本变量(如一个整数),有时只使用 volatile 就可以侥幸过关 public class Counter { private int counter = 0; public int get() { return counter; } public void set(int n) { counter = n; } public void increment() { set(get() + 1); } } 说明:同步get,set,或者将counter改为volatile,都无法实现线程安全,应该,get,set,increment都实现线程安全。因为如果变量的新值派生自以前的值,就必须使用同步。 当循环遍历 Vector 的元素时,同样如此。即使同步了 Vector 的方法,但在循环遍历时,Vector 的内容仍然会更改。如果要确保 Vector 的内容在循环遍历时不更改,必须同步整个代码块。 不需要使用同步:???????????????????? 1)由静态初始化器(在静态字段上或 static{} 块中的初始化器)初始化数据时 2)访问 final 字段时 3)在创建线程之前创建对象时 4)线程可以看见它将要处理的对象时 同步的准则: 1)使代码块保持简短。Synchronized 块应该简短 ― 在保证相关数据操作的完整性的同时,尽量简短。把不随线程变化的预处理和后处理移出 synchronized 块。 2)不要阻塞。不要在 synchronized 块或方法中调用可能引起阻塞的方法,如 InputStream.read()。 3)在持有锁的时候,不要对其它对象调用方法。这听起来可能有些极端,但它消除了最常见的死锁源头。 synchronized的4种用法 1. 方法声明时使用,线程获得的是对象锁. 2. 对某一代码块使用,synchronized后跟括号,括号里是变量,线程获得的是成员锁. 3.synchronized后面括号里是一对象,此时,线程获得的是对象锁. 4.synchronized后面括号里是类,此时,线程获得的是对象锁. ---------------------------------------------------------------------------------------------------- 信号量: 有效地将这些固定数目的数据库连接分配给大量的线程 class Semaphore { private int count; public Semaphore(int n) { this.count = n; } public synchronized void acquire() { while(count == 0) { try { wait(); } catch (InterruptedException e) { //keep trying } } count--; } public synchronized void release() { count++; notify(); //alert a thread that's blocking on this semaphore } } ----------------------------------------------------------------------------------------------------- wait,notify,notifyAll的使用: 用 notify() 来代替 notifyAll() 是有风险的。除非您确实知道正在做什么,否则就使用 notifyAll()。 遇到死锁:大多数程序应该完全避免更改线程优先级。因为更改优先级如何映射到底层操作系统调度程序取决于实现。在某些实现中,多个 ― 甚至全部 ― 优先级可能被映射成相同的底层操作系统优先级。 解决死锁的方案: 在所有的线程中按相同的次序获取所有资源锁。(如果有四个资源 ―A、B、C 和 D ― 并且一个线程可能要获取四个资源中任何一个资源的锁,则请确保在获取对 B 的锁之前首先获取对 A 的锁,依此类推。如果“线程 1”希望获取对 B 和 C 的锁,而“线程 2”获取了 A、C 和 D 的锁,则这一技术可能导致阻塞,但它永远不会在这四个锁上造成死锁。) 避免死锁: 1)让所有的线程按照同样的顺序获得一组锁。这种方法消除了 X 和 Y 的拥有者分别等待对方的资源的问题。 class Broken{ Object lock1 = new Object(); Object lock2 = new Object(); void a(){ synchronized(lock1&&lock2){ // do something } } void b(){ synchronized(lock2&&lock3) } } 2)将多个锁组成一组并放到同一个锁下。前面死锁的例子中,可以创建一个银器对象的锁。于是在获得刀或叉之前都必须获得这个银器的锁。 3)将那些不会阻塞的可获得资源用变量标志出来。当某个线程获得银器对象的锁时,就可以通过检查变量来判断是否整个银器集合中的对象锁都可获得。如果是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。 4)最重要的是,在编写代码前认真仔细地设计整个系统。多线程是困难的,在开始编程之前详细设计系统能够帮助你避免难以发现死锁的问题。 ------------------------------------------------------------------------------------------ 解决多线程程序可能耗尽资源问题: 如果线程数相当大,或者某个资源的侯选线程数远远超过了可用的资源数,则最好使用 资源池。 ------------------------------------------------------------------------------------------------ 无法访问的线程:有时候虽然获取对象锁没有问题,线程依然有可能进入阻塞状态。在 Java 编程中 IO 就是这类问题最好的例子。当线程因为对象内的 IO 调用而阻塞时,此对象应当仍能被其他线程访问。该对象通常有责任取消这个阻塞的 IO 操作。造成阻塞调用的线程常常会令同步任务失败。如果该对象的其他方法也是同步的,当线程被阻塞时,此对象也就相当于被冷冻住了。其他的线程由于不能获得对象的锁,就不能给此对象发消息(例如,取消 IO 操作)。必须确保不在同步代码中包含那些阻塞调用,或确认在一个用同步阻塞代码的对象中存在非同步方法。尽管这种方法需要花费一些注意力来保证结果代码安全运行,但它允许在拥有对象的线程发生阻塞后,该对象仍能够响应其他线程。 取消这个阻塞的IO操作的方法: 1)使用yield。最好不在同步方法中调用 yield 方法。将那些需要同步的代码包在一个同步块中,里面不含有非同步的方法,并且在这些同步代码块之外才调用 yield 。 yield() 方法:yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。yield()只是使当前线程重新回到可执行状态 文章出处:DIY部落(http://www.diybl.com/course/3_program/java/javajs/20090407/164526.html) 2)调用 wait() 方法,使处理器放弃它当前拥有的对象的锁。如果对象在方法级别上使同步的,这种方法能够很好的工作。因为它仅仅使用了一个锁。如果它使用 fine-grained 锁,则 wait() 将无法放弃这些锁。此外,一个因为调用 wait() 方法而阻塞的线程,只有当其他线程调用 notifyAll() 时才会被唤醒。 -------------------------------------------------------------- 多线程输出到日志中: log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n 一个日期戳、日志记录级别指标、线程 ID、当前所在的行号,以及日志消息 (1)info 将表示诸如方法入口/出口之类的东西,以及关于成功操作的更新。 (2)debug 倾向于用于表示诸如变量在某个时刻的值等内容。 (3)warn 可用于表示某些可重试的事情已失败的情形;例如,它可以警告某个连接在第一次尝试时没有成功建立,并且系统将再次尝试。 (4)error 表示某个地方出错了。 (5)fatal 表示某个错误阻止了进一步的处理。 如果有多个人负责编写向相同日志写出内容的代码,那么每个人都知道该准则是很重要的。 需要记录的有用信息: 方法的入口/出口 入口处的变量值 在何处对外发出调用(对于远程调用) 每个远程调用之前和之后的时间(时间戳可以显示该调用花了多长时间) 每个线程的开始和结束 使用log4j进行日志记录需要注意的地方: (1)从一开始就计划日志记录。事后添加日志记录功能要困难得多,并且您在最初开始调试代码时就会需要它。 (2)挑选一种满足需要的机制,但是不要让事情变得对您更复杂。如果其他某人编写了一个免费、开放源代码、得到维护、执行良好、特性丰富的选项……那么请 使用它。 (3)应该预先决定将要使用何种日志级别,并确保编程团队中的每个人都知道您希望他们使用的约定。 (4)如果您是在从头开始,那就添加端到端的处理 ID。通过您的程序传递这些 ID 确实是值得付出的努力。 (5)了解 UNIX 文本处理命令的强大能力。我在本文中是从命令行使用它们的,不过您也可以在脚本中使用它们。 (6)您应该记录可能会遇到的尽可能多的内容,不过不要忘了保持性能。特别要注意对远程调用进行日志记录,以及在线程分支、开始和结束的位置进行日志记录。日志中的信息越多,您所能实现数据挖掘就越强有力。 ------------------------------------------------------------------------- 有时一个程序因为有大量的线程在运行而极难调试。在这种情况下,下面的这个类可能会派上用场: public class Probe extends Thread { public Probe() {} public void run() { while(true) { Thread[] x = new Thread[100]; Thread.enumerate(x); for(int i=0; i queue = new LinkedList<string>(); private final Object lock = new Object(); public void enqueue(String str) { synchronized (lock) { queue.addLast(str); lock.notifyAll(); } } public void work() { String current; synchronized(lock) { if (queue.isEmpty()) { try { lock.wait(); } catch (InterruptedException e) { assert (false); } } if(!queue.isEmpty()){ current = queue.removeFirst(); } } System.out.println(current); } public static void main(String[] args) { final PrintQueue pq = new PrintQueue(); Thread producer1 = new Thread() { public void run() { pq.enqueue("anemone"); pq.enqueue("tulip"); pq.enqueue("cyclamen"); } }; Thread producer2 = new Thread() { public void run() { pq.enqueue("iris"); pq.enqueue("narcissus"); pq.enqueue("daffodil"); } }; Thread consumer1 = new Thread() { public void run() { pq.work(); pq.work(); pq.work(); pq.work(); } }; Thread consumer2 = new Thread() { public void run() { pq.work(); pq.work(); } }; producer1.start(); consumer1.start(); consumer2.start(); producer2.start(); } } ConTest 是用于测试、调试和测量并行程序的范围的工具</string>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值