(1) 一个程序要同时执行多个任务,就需要采用多线程,一个任务对应一个线程。
进程拥有自己的一套变量,而线程是共享数据。
实例1:
public class Test {
public static void main( String[] args ) {
PrintRunInfo hxp = new PrintRunInfo( "hxp" );
Thread hxpT = new Thread( hxp );
hxpT.start();// 不要调用Runnable 和Thread的run()方法,他不会新建一个线程,而是在当前线程执行。即run()方法执行完后,才会执行后面的代码。
PrintThreadInfo wwT = new PrintThreadInfo( "ww" );
wwT.start();
}
}
//实现Runnable 实现线程, 实现run()
class PrintRunInfo implements Runnable {
private String info;
PrintRunInfo( String info ) {
this.info = info;
}
public void run() {
for( int i = 0; i < 18; i++ ) {
System.out.println( info );
try{
Thread.sleep( 300 );
} catch( Exception e ) { e.printStackTrace(); }
}
}
}
//继承Thread 实现线程, 必须覆盖run()
class PrintThreadInfo extends Thread {
private String info;
PrintThreadInfo( String info ) {
this.info = info;
}
public void run() {
for( int i = 0; i < 18; i++ ) {
System.out.println( info );
try{
Thread.sleep( 200 );
} catch( Exception e ) { e.printStackTrace(); }
}
}
} //end 实例1
(2)中断线程
*自动中断:当线程run()方法执行完或出现未捕获的异常,线程将终止;
*请求中断:interrupt() 中断标志将置位。每个线程应该不时检查是否中断来完成run():
Thread.currentThread().isInterrupted().阻塞状态的线程无法被中断。静态 interrupted() 返回值与isInterrupted()一样,不同的是它会清除中断标志。
*强制中断:被弃用的 suspend() 和 stop() 方法可以强制终止线程;
(3)线程状态:JDK5- getState() 取得当前状态。
*新生 : 刚开始创建的线程,还没开始运行线程中的代码;
*可运行 : 调用了start()后,处于可运行状态。该状态下的线程不一定是正在运行的。
*被阻塞 :暂不活动。
*等待 :暂不活动。
*终止 :当线程run()方法执行完或出现未捕获的异常,线程将终止。
(4)线程优先级(1-10):线程优先级高度依赖于系统。如windows上有些优先级映射到同样的操作系统优先级上; sun的linux上则忽略优先级。
// Thread.NORM_PRIORITY = 5
// Thread.MIN_PRIORITY = 1
// Thread.MAX_PRIORITY = 10
setPriority( int ) 尝试不管用。网上看到一句话:“setPriority 告诉JVM这个线程的优先级,但JVM是否按你请求 (注意是请求,不是要求)办,那要看它的心情.”
(5)守护进程:唯一用途是为其他线程服务。当所有线程运行完后只剩下守护线程时,JVM就退出了。
守护进程应该永远不要去访问固有资源。如果文件数据库之类的。
setDaemon( true ) 必须在线程执行之前调用。
(6)未被捕获的异常处理器:
由于run()方法不能抛出任何被检查的异常。在出现异常时会被传递一个实现了Thread.UncaughtExceptionHandler接口的类(未被捕获的异常处理器)。如果不设置处理器则默认为该线程的ThreadGroup对象。
(7)同步:当多个线程对共享的同一数据进行存取时,需要实现同步处理。
竞争条件
两种机制实现: 关键字synchronized(常规锁) 和 类ReentrantLock(公平锁).
公平锁实例如下:
public class Test {
public static void main( String[] args ) {
ReentrantLock lock = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
PrintRunInfo hxp = new PrintRunInfo( "hxp", lock );
Thread hxpT = new Thread( hxp );
hxpT.start();
PrintThreadInfo wwT = new PrintThreadInfo( "ww", lock );//如果用lock2则两个线程会同时执行。
wwT.start();
}
}
//实现Runnable 实现线程, 实现run()
class PrintRunInfo implements Runnable {
private String info;
private ReentrantLock lock;
PrintRunInfo( String info, ReentrantLock lock ) {
this.info = info;
this.lock = lock;
}
public void run() {
lock.lock();
try {
for( int i = 0; i < 8; i++ ) {
testLock();// 可以再调用对lock 加了锁的方法(锁是可以重入的,它本身保持一个持有计数器)
System.out.println( info );
try{
Thread.sleep( 300 );
} catch( Exception e ) { e.printStackTrace(); }
}
} finally {
lock.unlock();
}
}
private void testLock() {
lock.lock();
try {
System.out.println( "testLock:" + info );
} finally {
lock.unlock();
}
}
}
//继承Thread 实现线程, 必须覆盖run()
class PrintThreadInfo extends Thread {
private String info;
private ReentrantLock lock;
PrintThreadInfo( String info, ReentrantLock lock ) {
this.info = info;
this.lock = lock;
}
public void run() {
lock.lock();
try {
for( int i = 0; i < 18; i++ ) {
System.out.println( info );
try{
Thread.sleep( 300 );
} catch( Exception e ) { e.printStackTrace(); }
}
} finally {
lock.unlock();
}
}
}
(8)条件对象(条件变更):java用条件对象来管理进入临界区,但因为某些条件不满足而不能做有用工作的线程。
一个锁对象(ReentrantLock)可以有一个或多个相关的条件对象( newCondition() )。
ReentrantLock lock = new ReentrantLock();
Condition con = lock.newCondition();
con.await();//阻塞当前线程,并释放锁。 需要signalAll()之类的方法激活,否则成死锁。
con.signal();//激活因为这一条件等待的一个进程
con.signalAll();//激活因为这一条件等待的所有进程
锁与条件对象总结:
* 锁可以保护代码片段,任何时刻只能有一个线程执行被保护代码片段;
* 锁可以管理进入代码片段的线程;
* 锁可以拥有一个或多个条件对象;
* 条件对象保护那些进入保护代码片段但不能运行的线程;
(9)synchronized:java中的每个对象都有一个内部锁,如果用synchroniczed 修饰了某个方法,那么调用此方法的线程必须获得对象的内部锁。
内部对象锁有一个相关的条件对象。this.wait(); this.notifyAll(); 相当于条件对象的await() 和signalAll()。
类的静态方法加的synchronized,加锁是用的类对象的内部锁。
内部锁和条件的局限:
* 不能中断一个试图获取锁的线程;
* 试图取锁不能设置超时;
* 每个锁只有一个条件锁,可能是不够的;
Lock 、 Condition 和同步方法使用建议:
* 最好都不使用,在许多情况下可以使用java.util.concurrent包中的一种机制实现;
* 尽量使用 synchronized;
* 实在需要时使用 Lock/Condition;
(10)同步阻塞:又称为客户端锁,不推荐使用。
synchronized( obj ) {...} //取得obj的锁
(11)监视器:研究人员努力寻求一种方法让程序员不考虑如何加锁就能够实现多线程。最成功的解决方案之一:监视器(monitor)。
* 监视器只含有私有域的类;
* 每个监视类有一个相关类的锁;
* 使用该锁对所有方法加锁;
* 该锁可以有任意多个条件;
如果一个方法被声明为synchronized,那么它表现得就象一个监视器方法。通过wait、notifyAll访问内部条件变量。
(12)volatile: 为实例的同步访问提供了一种免锁的机制(同步访问)。 如果用它声明一个域,编译器和虚拟机知道该域可能被另一个线程同步访问,但该域不同提供原子性。
总之:在下边3种条件下并发访问域是安全的。
* 域是final声明的,并在构造器调用完后访问;
* 对域的访问由公有锁加锁;
* 域是volatile的;
(13)读写锁(ReentrantReadWriteLock):如果很多线程只是从一个数据结构中读数据而很少修改数据,该锁是非常有用的。
使用的必要步骤:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); //构造
Lock rLock = null, wLock = null;
try {
rLock = lock.readLock();// 抽取读锁
wLock = lock.writeLock();//抽取写锁
} finally {
rLock.unlock();
wLock.unlock();
}
(14)阻塞队列(未详看):我们应尽量少使用底层结构实现并发,使用并发处理专业人士实现的较高层次的结构来实现要好的多。如阻塞队列。
java.util.concurrent 包提供了几种阻塞队列的变种。如LinkedBlockingDeque(无上界), .ArrayBlockingQueue(指定容量), PriorityBlockingQueue(优先级)
(15)线程安全的集合:
* 高效的映像、有序集、队列:使用复杂的算法使线程并发的访问数据结构中的不同部分来使竞争最小化。
java.util.concurrent.ConcurrentHashMap
java.util.concurrent.ConcurrentSkipListSet
java.util.concurrent.ConcurrentLinkedQueue
* CopyOnWriteArraySet, CopyOnWriteArrayList是线程安全的集合:所有修改的线程都会对底层数组做复制。如果构建一个老的迭代,当集合变化后,迭代指向之前老的集合。
* 老的线程安全集合:
Vector, Hashtable 早期的线程安全的动态数组和散列表的实现,1.2后被弃用。ArrayList和HashMap取代(不是线程安全)。
集合库提供了同步包装器实现线程安全:集合的方法是同步的。
List synList = Collections.synchronizedList( new ArrayList() );
Map synMap = Collections.synchronizedMap( new HashMap() );
如果另一个线程可能修改时并进行迭代,仍需要使用“客户端”封锁:
List synList = Collections.synchronizedList( new ArrayList() );
synchronized( synList ) {
Iterator it = synList.iterator();
while( it.hasNext() ) {...}
}
总结:尽量使用java.util.concurrent提供的线程安全集合,不使用同步包装器。有一个例外,如果有一个经常被修改的数组,那同步一个ArrayList可以胜过CopyOnWriteArrayList.