线程属性
- 线程优先级
每一个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。可能用setPriority方法提高或降低任何一个线程的优先级。可以将优先级设置为在MIN_PRIORITY(在Thread类中定义1)与MAX_PRIORITY(定义 为10)之间的任何值。NORM_PRIORITY被定义为5。线程的优先级是依赖于系统的。每当调度器决定运行一个新线程时,首先会在具有高优先级的线程中进行选择,尽管可能使低优先级的线程完全饿死。
java.lang.Thread
- void setPriority(int newPpiority)
设置线程的优先级。优先级必须在Thread.MIN_PRIORITY与Thread.MAX_PRIORITY之间。一般使用Thread.NORM_PRIORITY优先级。
- static int MIN_PRIORITY
线程的最小优先级。最小优先级值 为1
- static int NORM_PRIORITY
线程的默认优先级。默认优先级为5.
- static int MAX_PRIORITY
线程的最高优先级。最高优先级的值为10。
- static void yield()
导致当前执行中处于让步状态。如果有其他的可运行线程具有至少与此线程同样高的优先级,那么这些线程接下来会被调度。这是一个静态的方法
- 守护线程
可以通过调用t.setDaemon(true);将线程转换为守护线程(daemon thread)。守护线程的唯一用途是为其他的线程服务。当只剩下守护线程时,虚拟机就退出了。守护线程应该永远不要去访问固有的资源,如文件、数据库。因为它会在任何时候甚至在一个操作的中间发生中断。
- void setDaemon(boolean isDaemon)
标识该线程为守护线程或用户线程。这一方法必须在线程启动之前调用。
- 未捕获异常处理器
线程的run方法不能抛出任何被检测的异常,但是,不被检测的异常会导致线程终止。这种情况下,线程就死亡了。未捕获异常处理器可以在线程死亡之前,获得传递的异常。
该处理器必须实现一个Thread.UncaughtExceptionHandler接口的类。这个接口只有一个方法。
void uncaughtException(Thread t,Throwable e)
可用setUncaughtExceptionHandler方法为任何线程安装一个处理器。也可用Thread类的静态方法setDefaultUncaughtExceptionHandler为所有线程安装一个默认处理器。
java.lang.Thread
- static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler)
- static void Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() 设置未捕获的异常处理器
- void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler)
- Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()
设置或获取未捕获的异常的处理器。如果没有安装处理器,则将线程组对象作为处理器。
java.lang.Thread.UncaughtExceptionHandler
- void uncaughtException(Thread t, Throwable e)
当一个线程因未捕获异常而终止,按规定要将客户报告记录到日志 中。
参数:t 由于未捕获异常而终止的线程
e 未捕获的异常对象
同步
- 锁对象
锁是可重入的,因为线程可以重复的获得已经持有的锁。锁保持一个持有计数来跟踪对lock方法的嵌套调用。线程在每一次调用lock都要调用unlock来释放锁。
java.util.concurrent.locks.Lock
- void lock()
获取这个锁;如果锁同时被另一个线程拥有则发生阻塞。
- void unlock()
释放这个锁。
java.util.concurrent.locks.ReentranLock
- ReentrantLock()
构建一个带有公平策略的锁。一个公平锁偏爱等待时间最长的线程。使用公平锁比使用常规锁要慢很多。
synchronized关键字
- 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
- 锁可以管理试图进入被保护代码段的线程。
- 锁可以拥有一个或多个相关的条件对象。
- 每个条件对象管理那些已经进入被保护代码段但还不能运行的线程。
java.lang.Object
- void notifyAll()
解除那些在该对象上调用wait方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
- void notify()
随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。该方法只能在一个同步方法或同步块中调用。如果当前线程不是对象锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
- void wait()
导致线程进入等待状态直到它被通知。该方法只能在一个同步方法中调用。如果当前线程不是锁的持有者该方法抛出一个IllegalMonitorStateException异常。
- void wait(long millis)
- void wait(long millis,int nanos)
Volatile域
有时,仅仅为了读写一个或两个实例域就使用同步,显得开销过大。
- 多处理器计算 机能够暂时在寄存器或本地内存缓冲区中保存内存中的值。结果是,运行在不同处理器上的线程可能在同一个内存位置取到不同的值。
- 编译器可以改变指令执行的顺序以使吞吐量最大化。这种这种顺序上的变化不会改变代码语义,但是编译器假定内存的值仅仅在代码中有显式的修改指令时才会改变。然而,内存的值可以被另一个线程改变。
- 同步格言:如果向一个变量写入值,而这个变量接下来可能会被另一个线程读取,或者,从一个变量读值,而这个变量可能是之前被另一个线程写入的,此时必须使用同步。
volatie关键字为实例的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
例:一个对象有一个布尔标记done,它的值被一个线程设置却被另一个线程查询,可以使用锁:
或许使用内部锁不是个好主意。如果另一个线程已经对该对象加锁,isDone 和 setDone方法可能阻塞。在这种情况下,将域声明为volatile是合理的:
final变量
除非使用锁或者volatile修饰符,否则无法从多个线程安全地读取一个域。
还有一种情况可以安全地访问一个共享域,退这个域声明为final时。其它线程会在构造函数完成构造之后才看到这个变量。如果不使用final,就不能保证其它线程看到的是更新后的值。但这并不是线程安全的,如果多个线程读写这个变量,仍然需要进行同步。
读/写锁
java.util.concurrent.locks包定义了两个锁类,ReentrantLock类和ReentrantReadWriteLock类。如果很多线程从一个数据结构读取数据而很少线程修改其中数据的话,后者是十分有用的。
下面是读写锁的必要步骤:
1)构造一个ReentrantReadWriteLock对象:
private ReentrantReadWriteLock rwl=new ReentrantReadWriteLock();
2)抽取读锁和写锁:
private Lock readLock=rwl.readLock(); private Lock writeLock=rwl.writeLock();
3)对所有的获取方法加读锁:
public double getToalBalance(){ readLock.lock(); try{...} finally{ readLock.unlock(); } }
4)对所有修改方法加写锁
public void transfer(...){ writeLock.lock(); try{...} finally{
writeLock.unlock();
} }
Lock readLock() //得到一个可以被多个读操作共用的读锁,但会排斥所有写操作。
Lock writeLock() //得到一个写锁,排斥所有其他的读操作和写操作。
为什么弃用stop和suspend方法
初始的java版本定义了一个stop方法用来终止一个线程,以及一个suspend方法用来阻塞一个线程直到另一个线程调用resume。stop和suspend方法有一个共同点:都试图控制一个给定线程的行为。这两个方法已经弃用。stop方法天生就不安全,经验证明suspend方法会经常导致死锁。
stop方法终止所有未结束的方法,包括run方法。当线程终止,立即释放被它锁信的所有对象锁。这会导致对象处于不一致的状态。例如,假定 在从一个账户向另一个账户转账的过程中被终止,钱款已经转出,却没有转入目标账户,现在银行对象就被破坏了。因为锁已经被释放,这种破坏会被其他尚未停止的线程观察到。
当线程要终止另一个线程时,无法知道什么时候调用stop方法是安全的,什么时候导致对象被破坏。因此,该方法被弃用了。
suspend方法不会破坏对象。但是,如果用suspend方法的线程试图获得同一个锁,那么程序死锁:被挂起的线程等着被恢复,而将其挂起的线程等待获得锁。程序被终结了。分配线程不能继续运行,因为锁由一个被挂起的线程所持有。