线程池
1 package concurrentStudy; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 public class ThreadPoolImpl1 { 7 public static void main(String[] args) { 8 ExecutorService ec = Executors.newFixedThreadPool(4); 9 for(int i=0;i<10;i++){ 10 ec.execute(new WorkerThread("start"+i));//提交10次任务立即执行完 11 } 12 System.out.println("任务提交完毕"); 13 ec.shutdown(); //拒绝接受新任务,尝试关闭线程,不会影响正在执行的线程,不取消等待队列中的任务 14 System.out.println("尝试关闭线程已开启"); 15 while(!ec.isTerminated()){ //等待所有任务执行完毕(包括正在执行和已经提交的) 16 17 } 18 System.out.println("all threads finished"); 19 } 20 } 21 class WorkerThread implements Runnable{ 22 private String command; 23 24 25 public WorkerThread(String command) { 26 super(); 27 this.command = command; 28 } 29 30 31 @Override 32 public void run() { 33 System.out.println(Thread.currentThread().getName()+"Start.command="+command); 34 try { 35 Thread.sleep(5000); 36 } catch (InterruptedException e) { 37 // TODO Auto-generated catch block 38 e.printStackTrace(); 39 } 40 System.out.println(Thread.currentThread().getName()+"end"); 41 } 42 43 }
1.固定线程池,上例中最大只有4个线程提供服务,对应的是无界队列。
2.Runnable与Callback均是任务接口,但Runnable无返回值,不可抛出可检查异常
结果:
1 pool-1-thread-1Start.command=start0 2 pool-1-thread-3Start.command=start2 3 pool-1-thread-2Start.command=start1 4 pool-1-thread-4Start.command=start3 5 任务提交完毕 6 尝试关闭线程已开启 7 pool-1-thread-1end 8 pool-1-thread-2end 9 pool-1-thread-2Start.command=start5 10 pool-1-thread-4end 11 pool-1-thread-3end 12 pool-1-thread-3Start.command=start7 13 pool-1-thread-4Start.command=start6 14 pool-1-thread-1Start.command=start4 15 pool-1-thread-1end 16 pool-1-thread-2end 17 pool-1-thread-3end 18 pool-1-thread-4end 19 pool-1-thread-2Start.command=start9 20 pool-1-thread-1Start.command=start8 21 pool-1-thread-1end 22 pool-1-thread-2end 23 all threads finished
二.线程基础回顾
1.线程的五种状态
准备状态:即线程对象刚new出来时
就绪状态(runnable):调用了线程的start()方法,此时线程有机会分配到cpu
执行状态(running):线程分配到了cpu,正在执行
阻塞状态(blocked):执行中的线程由于一些原因放弃cpu分配的机会,进入阻塞状态的线程必须再次进入就绪状态才有权利分配到cpu
阻塞状态分三种:1.在线程中调用某对象的wait()方法,导致该线程进入那个对象的wait pool
2.为了获得某个对象的同步锁,进入了那个对象的lock pool
3.其它阻塞状态:调用Thread.sleep(long time)方法进入休眠,调用其它线程的join()方法,发起了I/O请求,且该I/O模型为阻塞的。
死亡状态(dead):线程的run方法执行完毕,或者线程在wait/sleep/join阻塞期间,在另一个线程中调用了这个线程的interrupt()方法导致这个线程抛出InterruptException,这两种方式都会导致线程结束死亡。
2.wait(),notify(),notifyAll()这三个方法必须在同步代码块中使用,也就是说要执行这三个方法,必须获得对应的obj锁。
如果线程B处于wait/sleep/join阻塞状态时,另一个线程调用了线程B的interrupt()方法,则线程B会抛出 InterruptException,这样也能实现线程安全的结束。
wait与notify本质上是为了实现多个线程之间协调工作;
A线程调用了wait(),当B线程调用了notify或者notifyAll,B线程在退出同步块以后被唤醒的线程A才有机会获得锁继续执行。也就是说,并不是notify后A线程一定会马上执行,还必须等B先释放锁。
深入理解notifyAll:
1 public class SimpleLock { 2 private boolean isLocked = false; 3 4 public synchronized void lock(){ 5 while(isLocked){ //自旋锁 6 try { 7 System.out.println(Thread.currentThread()+"即将开始wait--"+isLocked); 8 wait(); 9 System.out.println(Thread.currentThread()+"结束wait,从wait方法返回"); 10 } catch (InterruptedException e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 } 14 } 15 isLocked = true; 16 } 17 18 public synchronized void unlock(){ 19 isLocked = false; 20 System.out.println(Thread.currentThread()+"unlock中的"+isLocked); 21 notifyAll();//notify(); 22 } 23 }
如上例,多个线程阻塞在第8行的wait(),当其中一个线程调用unlock()执行notifyAll()后,剩下的线程全部被唤醒,它们都会去执行wait()后边的代码,此时isLocked的值理论上为false。
但是实际情况是,只有竞争到锁的那个线程才会去读取成员变量isLocked的新值false,所以它会退出while循环继续执行;而其它没有竞争到锁的线程保留的成员变量isLocked的拷贝的值没有更新,依然为true,所以它们会继续执行wait()。
当21行改为notify()时,则只会有一个线程被唤醒,被唤醒的那个线程直接获得锁,执行wait()后的代码,且会读取isLocked的新值,而其它的线程根本就不会执行wait()后的代码。
注意notifyAll与notify在这个细节上的区别,notifyAll会导致所有线程执行一遍wait()后的代码。
3.关于synchronized关键字的使用
当修饰非静态方法时,其锁定的是this对象,等价于对一个方法的所有代码使用synchronized(this){...},一旦线程进入同步方法,则其它的线程在调用本类的所有同步方法时将阻塞,等待获得对象锁。
1 public void handleVariableA(){ 2 synchronized (this) {//此同步只会影响其它线程对本类所有同步方法的访问 3 System.out.println("进入了同步A方法,a的值为:"+a); 4 a += 1; 5 try { 6 Thread.sleep(4000); 7 } catch (InterruptedException e) { 8 // TODO Auto-generated catch block 9 e.printStackTrace(); 10 } 11 System.out.println("A方法将要执行完毕"); 12 } 13 } 14 15 public void ordinaryMethod(){ //如果加上synchronized,则会受到影响 16 System.out.println("a的值为:"+a); 17 }
如上例,A线程与B线程共享这个类的一个实例,A线程进入handleVariableA()的同步代码块中,在A还没有退出同步块时,B线程可以正常的访问ordinaryMethod()方法。
如果ordinaryMethod()方法有synchronized修饰,情况则不同,在A释放对象锁之前,B线程调用ordinaryMethod()会阻塞。
4.重入锁的一个实现
public class ReentrantLock { private boolean isLocked = false; private Thread lockedBy = null; private int lockedCount = 0; public synchronized void lock(){ Thread currentThread = Thread.currentThread(); while(isLocked && currentThread!=lockedBy){ //如果已持有锁的线程尝试重入,则允许再次获得锁 try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } isLocked = true; lockedBy = currentThread; lockedCount++; } public synchronized void unlock(){ Thread currentThread = Thread.currentThread(); if(currentThread==lockedBy){ lockedCount--; if(lockedCount==0){ isLocked = false; //当锁计数器为0时,真正的释放锁并唤醒一个其它线程来获得锁。 notify(); } } } }
尽量如下使用锁:
1 public void useLock(){ 2 lock.lock(); 3 try{ 4 //... 5 }finally{ 6 lock.unlock(); 7 } 8 }
抛出异常后,最终记得释放锁,避免申请锁的其它线程一直阻塞。
5.信号量Semaphore的作用:用来限制可以访问某些资源的线程数目。
AtomicInteger,AtomicBoolean等可用作标记状态,它们支持原子的修改。
(1)如在自增运算count++中,实际是两个操作获取值,增加值,这是一个事务,实际上count++这样的语句是线程不安全的,在没有同步的情形下可能有这样的情形:A线程取得值为5,还没有自增,切换到B线程,取值为5,自增为6,A线程继续执行,还是在原来5的基础上加1,count最终的值就还是6,实际上正确的值应该是7。所以这种情况下可以使用AtomicInteger的getAndIncrement()方法,这个方法可以保证这两个操作是原子执行,所以B线程操作count时,它看见的count已经是6了,不可能存在中间状态。
而getAndIncrement()与incrementAndGet()的区别就与count++, ++count是一样的,前者返回的是以前的值,后者返回的是增加后的值。
(2)在NIO框架中,private AtomicBoolean inRead = new AtomicBoolean(false);// 读取通信信号量
当inRead.compareAndSet(false,true)返回值为true时,则表示该连接还没有被加入到读取队列,或者没有正在读取,后边的操作则是将这个连接加入到readPool中。
if(inRead.compareAndSet(false,true)){ //只有一个线程能成功的将inRead修改为true,其它的线程执行时,由于inRead的值已经是true,所以compareAndSet(false,true)返回的值为false,不进行下面的操作
//将连接加入到读取队列
}