《Core Java 2》读书笔记(一)
1,一个线程可以调用interrupt方法来请求终止另一个线程的运行,也就是说线程的 run方法应该不时检查一下它是否应该退出。 线程不应该连续不断地工作,应该偶尔进入睡眠或等待状态,以便其他线程由机会得以执行。但当一个线程进入睡眠状态时,它就无法检查自己是否应该终止运行。当另一个线程对当前被中断运行的线程对象调用interrupt方法时,中断调用(sleep或wait)将被InterruptedException终止。但还有种情况要考虑,若线程并未处于睡眠或等待状态,则另一个线程对其调用interrupt方法,则不会产生InterruptedException,对于此,线程应该在下一轮工作之前通过interrupted方法来确认它最近是否被其他线程给请求终止了。整体的框架如下: 复制代码 public void run() { while (!interrupted()) { try { …… sleep(50); } catch(InterruptedException ex) { Thread.currentThread.interrupt(); } } } 复制代码 Interrupted方法是个静态方法,用来检查当前线程是否已经被中断,此外,它会清除线程的“中断”状态。isInterrupted方法是个实例方法,用来检查任何一个线程是否已经被中断,它不会改变线程的“中断”状态。 2,Sleep对应于经过规定的毫秒数才退出中断状态。如果一个当前已经中断的线程上再次调用sleep,则会抛出IllegalThreadStateException。wait和notifyAll是一对,是Object类的方法。 3,守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。 4,下面这段代码在本地线程机制下不会有什么问题,但若是在绿色线程机制下,则会发现利己线程会占据整个应用程序。 复制代码 public void run() { try { for (int i = 1; i <= 1000; i++) { b.move(); if (selfish) { // busy wait for 5 milliseconds long t = System.currentTimeMillis(); while (System.currentTimeMillis() < t + 5) ; } else sleep(5); } } catch (InterruptedException exception) { } } 复制代码 5,银行账户这个例子中,当账户中没有足够的金额时,我们必须等待,直到另外某个线程增加了金额为止。但transfer已经进行了同步,当前线程已经获得了对银行对象的专用访问权,因此没有其他线程有机会进行存款操作。因此我们想在synchronized方法中等待,就可以使用Object类的wait方法。于是当前线程将被中断运行,并且放弃对该对象的锁,这可以使另一个线程启动。当该线程在等待过程中被中断运行时,wait方法会抛出一个InterruptedException异常,此时,可以使用interrupt去打开“中断”标志,也可以传递该异常,这由调用线程来决定如何处理,所以在例子中给transfer 方法加入了throws InterruptedException。wait方法是Object类的一个方法,而不是Thread类的方法,当调用它时,银行对象打开自己的锁,并且中断当前线程的运行。 复制代码 public synchronized void transfer(int from, int to, int amount) throws InterruptedException { while (accounts[from] < amount) wait(); accounts[from] -= amount; accounts[to] += amount; ntransacts++; notifyAll(); if (ntransacts % NTEST == 0) test(); } 复制代码 等待进入synchronized方法的线程和已经调用了wait方法的线程之间有根本区别。一旦线程调用了wait方法,便进入了该对象的等待列表。此时线程被中断,在该线程没有从等待列表中删除之前,调度程序将忽略该线程,并且该线程将没有机会继续运行。要从等待列表中删除该线程,其他线程必须调用同一个对象上的notifyAll或notify方法。前者是将对象等待列表中的所有线程全部删除,后者是删除任意一个选择的线程。当线程从等待列表中删除后,它们就再次成为可运行的线程,并且调度程序最终会重新激活它们。然后,它们会试图重新进入该对象。一旦可以使用该对象锁时,其中的一个线程将锁定该对象,并且从它上次调用wait方法后的位置开始继续运行。 调用notifyAll方法的原则是每当一个对象的状态改变对各个等待线程有利时,就可以调用.例如在例子中,当完成资金转账时,就调用notifyAll方法,使得等待线程有机会查看其余额。若此时足够用于资金转账,则执行转账,否则再次调用wait进入等待列表。 调用notifyAll方法并不会立即激活某个等待线程,而只是撤消等待线程的中断状态,使其在当前线程退出synchronized方法后,与其他线程进行竞争,争取进入该对象。 如果一个线程必须等待某个对象的状态出现变更,那么它应该在对象的内部等待,而不是在外边等待,这通过进入一个synchronized方法,并且在其中调用wait方法来实现。 6,调用一个同步静态方法将会锁定类对象,如下例所示,将会锁定Singleton.class,因此,如果一个线程调用一个类的静态synchronized方法,那么该类的所有静态synchronized方法将会被锁定,直到第一个调用返回为止。 复制代码 public class Singleton { private static Singleton instance = null; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } 复制代码 7,stop方法来停止线程不安全,当一个线程被停止时,它会立即释放它锁定的所有对象的锁,这会使各个对象处于不一致的状态之中。例如,在取款后和存款前,线程在将资金从一个账户转入另一个账户的中间停止了运行,则此时银行对象就损坏了。 如果需要安全地停止一个线程的运行,可以让该线程定期检查一个变量,此变量负责指明是否有停止线程的请求。还有种特殊情况要考虑,若线程处于封锁状态(比如调用了wait方法),则线程只有在解除封锁状态后才能终止运行,我们可以通过interrupt方法来强制线程退出封锁状态,基本框架如下: 复制代码 public class MyThread extends Thread { private boolean isStop; public void run() { while (!isStop) { try { wait(); } catch(InterruptedException ex) { if (isStop == true) return; } } } public void requestStop() { isStop = true; interrupt(); } } 复制代码 8,suspend方法也被废弃了,它不会损坏对象,但如果挂起了一个拥有某个对象锁的线程,则在该线程恢复之前,将无法使用该对象。如果调用suspend方法的线程试图在调用resume方法之前获得同一个对象的锁,那么程序会死锁。因为被挂起的线程要等待恢复运行,并且挂起的线程要等待该对象被撤销锁定状态。 如果想要安全地暂停线程的运行,应该使用变量suspendrequested,并且在run方法中一个安全的位置上检查此变量。框架如下代码: 复制代码 class SuspendRequestor { private boolean suspendrequested; public synchronized void set(boolean flag) { suspendrequested = flag; notifyAll(); } public synchronized void waitForResume() throws InterruptedException { while (suspendrequested) wait(); } } class MyThread extends Thread { private SuspendRequestor suspender = new SuspendRequestor(); public void requestSuspend() { suspender.set(true); } public void requstResume() { suspender.set(false); } public void run() { try { while (more work to do) { suspender.waitForResume(); do work; } } catch(InterruptedException ex) { } } } 复制代码 也可以像下面这样使用一个无用对象作为同步机制 复制代码 class MyThread extends Thread { private boolean suspendRequested; private Integer dummy = new Integer(1); public void reuqestSuspend() { suspendRequested = true; } public void requestResume() { suspendRequested = false; synchronized(dummy) { dummy.notifyAll(); } } public void waitForResume() { synchronized(dummy) { while (suspendRequested) dummy.wait(); } } public void run() { try { while (more work to do) { waitForResume(); do work; } } catch(InterruptedException ex) { } } } 复制代码 9,有两个wait方法带有超时参数,当wait方法返回时,若你需要知道是因为超时还是得到了通知,可以计算调用wait方法前后的时间差。 long before = System.currentTimeMills(); wait(delay); long after = System.currentTimeMills(); if (after - before > delay) //超时了 此外,也可以让负责通知的线程设置一个标志。 为了始终强制使用超时,可以将封锁操作放入第二个线程,然后使用join方法来使得当前线程被封锁,直到第二个线程运行结束或经过规定的毫秒数为止。 复制代码 Thread t = new Thread() { public void run() { //封锁操作 } }; t.start(); t.join(mills); 复制代码 10,Swing大多数方法不是同步的,这就使得我们以前常用的在工作线程中通过指向主界面的指针来更新主界面元素的思路不可行,即在工作线程中不能接触用户界面,例如显示线程的进度百分比,就不能在工作线程中调用lable.setText,而应该使用EventQueue类的invokeLater和invokeAndWait方法,从而在事件调度线程中执行该调用程序。 复制代码 public void run() { try { while (!interrupted()) { EventQueue.invokeLater(new Runnable() { public void run() { int i = Math.abs(generator.nextInt()); if (i % 2 == 0) combo.insertItemAt(new Integer(i), 0); else if (combo.getItemCount() > 0) combo.removeItemAt(i % combo.getItemCount()); } }); Thread.sleep(1); } } catch (InterruptedException exception) {} } 复制代码 在事件被移到事件队列后,invokeLater立即返回。Run方法以异步方法执行。invokeAndWait方法将进行等待,直到run方法确实已经执行为止。EventQueue类负责处理具体的同步操作。需要注意的是,这里run方法的代码不是在新线程中执行的,而是在事件调度程序中执行的。 11,管道用于线程间的通信,适用于线程间低层次的通信,其他情况下可以使用队列。 本文转自Phinecos(洞庭散人)博客园博客,原文链接:http://www.cnblogs.com/phinecos/archive/2009/09/22/1572058.html,如需转载请自行联系原作者