wangjiong
发贴: 17
积分: 17
|
于 2005-11-09 09:08
在新的JDK5.0中,对thread做了一些改进,我通过阅读一些资料写下了下面的总结,请大家看看。
1.创建线程 在java中实现多线程有两种方法,一个是直接继承Thread类,一个是实现Runnable接口,但是推荐的是第二种。因为在逻辑上应该要把一个线程要做的事情以及做这个事情的方法分开;对于Thread来讲,它只负责线程的操作,而具体要做的事情就应该放在Runnable中。但不管是那种方式,都要实现public void run()方法,但启动线程用start而不是run。
2.终止线程 在1.0中,可以用stop方法来终止,但是现在这种方法已经被禁用了,改用interrupt方法。interrupt方法并不是强制终止线程,它只能设置线程的interrupted状态,而在线程中一般使用一下方式: while (!Thread.currentThread().isInterrupted() && more work to do) { do more work } 而被block的线程在被调用interrupt时会产生InterruptException,此时是否终止线程由本线程自己决定。程序的一般形式是: public void run() { try { . . . while (!Thread.currentThread().isInterrupted() && more work to do) { do more work } } catch(InterruptedException e) { // thread was interrupted during sleep or wait } finally { cleanup, if required } // exiting the run method terminates the thread }
Thread.sleep方法也会产生InterruptedException,因此,如果每次在做完一些工作后调用了sleep方法,那么就不用检查isInterrupted,而是直接捕捉InterruptedException。
在捕捉到InterruptedException时,如果没有其他的事情可做,最好做一下处理,不能用{} 1) void mySubTask() { . . . try { sleep(delay); } catch (InterruptedException e) { Thread().currentThread().interrupt(); } . . . } 或是 2) void mySubTask() throws InterruptedException { . . . sleep(delay); . . . }
3.线程状态 New:当使用new创建一个线程时 Runnable: 调用start或是从blocked状态出来时 Blocked:sleep, block on input/output, try to acquire lock, suspend, wait. Dead: 运行完成或有exception产生。
4.线程优先级 可以设置线程优先级,但是不能保证高优先级的线程就会被先运行
5.线程组 可以把多个线程加到一个线程组里面去,这样可以对这些线程进行一些统一的操作,例如 ThreadGroup g = new ThreadGroup(groupName) ... g.interrupt(); // interrupt all threads in group g
6.为Uncaught Exceptions设置Handlers 在java 5.0中,可以为线程中产生的unchecked exception设置一个处理器,这个处理器必须实现UncaughtExceptionHandler接口。 可以调用线程实例的setUncaughtExceptionHandler方法为每个线程设置一个处理器,也可以调用Thread.setDefaultUncaughtExceptionHandler来为所有的线程设置一个默认的处理器。如果没有给每一个线程设置处理器,那线程会首先使用线程组的处理器,如果还没有再使用默认的处理器。
7.Synchronization 多线程很重要的一个问题就是同步的问题,如果不解决好同步的问题一个是可能会引起数据的混乱,而且还有可能造成线程的死锁。在Java 5.0之前,用synchronized来解决这个问题,在5.0中加入了一个新的类:ReentrantLock
使用lock的基本形式是: myLock.lock(); // a ReentrantLock object try { critical section } finally { myLock.unlock(); // make sure the lock is unlocked even if an exception is thrown }
这个锁被称为Reentrant的原因是在一个线程中可以重复多次申请同一个锁,系统会保留加锁的次数,而在解锁的时候也就必须执行相同次数。
在一个线程已经得到锁可以执行程序的时候,可能会发现需要的条件还不能满足,这时他就必须等待直到条件满足。但是因为它已经对所需要操作的东西加了锁,其他的线程不能访问,因此它又可能会永远等待下去。现在可以用Condition Object来避免这种情况。 sufficientFunds = bankLock.newCondition(); 如果条件不满足: sufficientFunds.await(); 这时线程就会释放锁并进入blocked状态,其他线程就有机会执行操作。当其他线程执行完后,就可通知等待的线程继续执行它的操作了: sufficientFunds.signalAll(); 当然也可以调用singal方法,这样效率会高一些,但是有一定的危险性,因为它的唤醒具有随机性。
在5.0之前,采用的是synchronized关键字来进行同步,但是和lock相比它有一些局限性: 1. 申请锁的线程不能被interrupt 2. 没有timeout设置 3. 只有一个隐性的condition条件
另外,在申请锁的时候可以用tryLock方法,它会返回一个bool值来表示锁是否申请成功,如果没有成功,程序就可以做其他的事情了。
tryLock, await方法都可以被interrupt。
java.util.concurrent.locks包中提供了两种锁,一个就是ReentrantLock,另一个是ReentrantReadWriteLock,一般用于多操作远远多于写操作的时候: private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private Lock readLock = rwl.readLock(); private Lock writeLock = rwl.writeLock();
8. Callables and Futures 实现多线程时一般用的是Runnable接口,但是他有一个问题就是他没有参数和返回值,所以当执行一个线程需要返回一个值的时候就不是很方便了。Callable接口和Runnable差不多,但是他提供了参数和返回值: public interface Callable<V> { V call() throws Exception; } 而Future接口可以保留异步执行的值: public interface Future<V> { V get() throws . . .; V get(long timeout, TimeUnit unit) throws . . .; void cancel(boolean mayInterrupt); boolean isCancelled(); boolean isDone(); }
FutureTask可以很方便的把Callable转换成Future和Runnable: Callable<Integer> myComputation = . . .; FutureTask<Integer> task = new FutureTask<Integer>(myComputation); Thread t = new Thread(task); // it's a Runnable t.start(); . . . Integer result = task.get(); // it's a Future
9.用Executors创建线程池 用线程池有两个好处:1. 减少创建线程的开销。2. 控制线程的数量。 EXecutors提供了一些方法可以很方便的创建线程池: newCachedThreadPool New threads are created as needed; idle threads are kept for 60 seconds. newFixedThreadPool The pool contains a fixed set of threads; idle threads are kept indefinitely. newSingleThreadExecutor A "pool" with a single thread that executes the submitted tasks sequentially. newScheduledThreadPool A fixed-thread pool for scheduled execution. newSingleThreadScheduledExecutor A single-thread "pool" for scheduled execution.
在使用Executors时,先调用这些静态方法创建线程池,得到一个ExecutorService对象,然后用这个对象的submit方法提交你的Runnable或是Callable对象。 Future<?> submit(Runnable task) Future<T> submit(Runnable task, T result) Future<T> submit(Callable<T> task) 如果不再需要任何提交,就用shutdown方法来关闭线程池。
10.在界面中使用多线程 对于GUI设计来说,很重要的一个原则就是要及时的给用户反馈,就算是不能立即得到结果,界面也不能停在那里,是用户不知道发生了什么事情,必须让用户随时知道程序在坐什么。所以当程序要执行一段需要消耗比较长时间的操作时,就要使用多线程。
但是,有些界面控件并不是线程安全的,在使用这些控件时就要特别注意。在API doc中这些都有注明,使用的时候就可以查一下。
如果想在自己另外所创建的线程执行过程中随时更新界面来表示执行进程,要注意的一点是,这个线程并不能直接调用界面控件的方法,而要采用EventQueue类的invokeLater,invokeAndWait方法: EventQueue.invokeLater(new Runnable() { public void run() { label.setText(percentage + "% complete"); } });
|