线程池

 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,不进行下面的操作

  //将连接加入到读取队列

}

原文地址:https://www.cnblogs.com/enjoy-ourselves/p/3783372.html