• 线程池


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

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

    }

  • 相关阅读:
    Node.js安装及环境配置之Windows篇
    C++ STL中Map的按Key排序和按Value排序
    设计模式之观察者模式(c++)
    C/C++回调函数
    c++ string详解 assign
    C++ unsigned long 转化为 unsigned char*
    unsigned char 与unsigned long互换
    微信小程序的登陆流程详解
    谷歌帮:中国最牛的创业帮派
    创业公司打造顶级团队的七个方法
  • 原文地址:https://www.cnblogs.com/enjoy-ourselves/p/3783372.html
Copyright © 2020-2023  润新知