zoukankan      html  css  js  c++  java
  • JAVAAndroid 多线程实现方式及并发与同步

    转载:https://blog.csdn.net/csdn_aiyang/article/details/65442540

    概述
        进程是系统的执行单位,  一般一个应用程序 即是一个进程,程序启动时系统默认有一个主线程,即是UI线程,我们知道不能做耗时任务,否则ANR程序无响应。
       这时需要借助子线程实现,即多线程。 由于线程是系统CPU的最小单位,用多线程其实就是为了更好的利用cpu的资源。
     
    问1.线程状态
    1、wait()。 使一个线程处于等待状态,并且释放所有持有对象的lock锁,直到notify()/notifyAll()被唤醒后放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)。
    2、sleep()。 使一个线程处于睡眠状态,是一个静态方法,调用此方法要捕捉Interrupted异常,醒来后进入runnable状态,等待JVM调度。
    3、notify()。使一个等待状态的线程唤醒, 注意并不能确切 唤醒等待状态线程,是由JVM决定且不按优先级。
    4、notifyAllAll()。使所有等待状态的线程唤醒,注意并不是给所有线程上锁,而是让它们竞争。
    5、join()。 使一个线程中断,IO完成会回到Runnable状态,等待JVM的调度。
    6、Synchronized()。 使Running状态的线程加同步锁使其进入(lock blocked pool ),同步锁被释放 进入可运行状态(Runnable)。
     
    注意:当线程在runnable状态时是处于被调度的线程,此时的调度顺序是不一定的。
    Thread类中的yield方法可以让一个running状态的线程转入runnable。
     
    基础概念
    1、 并行。多个cpu实例或多台机器同时执行一段代码,是真正的同时。
    2、并发。通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。
    3、线程安全。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。
           线程不安全就意味着线程的调度顺序会影响最终结果,比如某段代码不加事务去并发访问。
    4、线程同步。  指的是通过人为的控制和调度,保证共享资源的多线程访问成为 线程安全,来保证结果的准确。
               如某段代码加入@synchronized关键字。线程安全的优先级高于性能优化。
    5、原子性。一个操作或者一系列操作,要么全部执行要么全部不执行。  数据库中的“事物”就是个典型的院子操作。
    6、可见性。当一个线程修改了共享属性的值,其它线程能立刻看到共享属性值的更改。
            比如 JMM分为 主存和工作内存,  共享属性的修改过程是在主存中读取并复制到工作内存中,在工作内存中修改完成之后,再刷新主存中的值。
    若线程A在工作内存中修改完成但还来得及刷新主存中的值,这时线程B访问该属性的值仍是旧值。这样可见性就没法保证。
    7、有序性。 程序运行时代码逻辑的顺序,在实际执行中不一定有序,为了提高性能,编译器和处理器 都会对代码进行重新排序。
    前提是,重新排序的结果要 和单线程执行程序顺序一致。
     
     
    问1. 常见多线程方式
     
    1、继承Thread类,重写run函数方法:
      class xx extends Thread{
        public void run(){
            Thread.sleep(1000);    //线程休眠1000毫秒,sleep使线程进入Block状态,并释放资源
        }
    }
    xx.start();    //启动线程,run函数运行
     
    2、实现Runnable接口,重写run函数方法:
    Runnable run =new Runnable() {
        @Override
        public void run() {
            
        }
    }
    3、实现Callable接口,重写call函数方法:
    Callable call =new Callable() {
        @Override
        public Object call() throws Exception {
            return null;
        }
    }
      小结:Callable 与 Runnable 对比。
    相同:都是可被其它线程执行的任务。
    不同:
         ①Callable规定的方法是call(), 而Runnable规定的方法是run().
         ②Callable的任务执行后可返回值,而Runnable的任务是不能返回值的
         ③call()方法可抛出异常,而run()方法是不能抛出异常的。
         ④运行Callable任务可拿到一个 Future对象,Future表示异步计算的结果。 通过Future对象可了解任务执行情况, 可取消任务的执行。
     
    问2、HandlerThread:
           handlerThread = new HandlerThread("MyNewThread");//自定义线程名称  
            handlerThread.start();  
            mOtherHandler = new Handler(handlerThread.getLooper()){  
                @Override  
                public void handleMessage(Message msg){  
                    if (msg.what == 0x124){  
                        try {  
                            Log.d("HandlerThread", Thread.currentThread().getName());  
                            Thread.sleep(5000);//模拟耗时任务  
                        } catch (InterruptedException e) {  
                            e.printStackTrace();  
                        }  
                    }  
                }  
            };  
    HandlerThread的好处是代码看起来没前面的版本那么乱,相对简洁一点。
    还有一个好处就是 通过handlerThread.quit() 或者 quitSafely(),使线程结束自己的生命周期。
     
    问3、IntentService:
     最后是IntentService,相信很多人也不陌生,它是Service的子类,用法跟Service也差不多,就是实现的方法名字不一样,
    耗时逻辑应放在onHandleIntent(Intent intent)的方法体里,它同样有着退出启动它的Activity后不会被系统杀死的特点,
    而且当任务执行完后会自动停止,无须手动去终止它。
    例如在APP里我们要实现一个下载功能,当退出页面后下载不会被中断,那么这时候IntentService就是一个不错的选择了。
     
    问4.线程各种同步的含义: 
     
    Synchronized 同步:
    由于java的每个对象都有一个内置锁,当用此关键字修饰方法时, 内置锁会保护整个方法。 在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
    补充: synchronized关键字也可以修饰   静态方法,此时如果调用该静态方法,将会锁住整个类。
     
    1、方法同步。
     给方法增加synchronized修饰符就可以成为同步方法,可以是静态方法、非静态方法,但不能是抽象方法、接口方法。
    public synchronized void aMethod() { 
        // do something 
    public static synchronized void anotherMethod() { 
        // do something 
    使用详解:
    线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法 并从中退出,从而导致它释放了该对象的同步锁之后。
    在一个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有非同步方法的。
     
    2、块同步。   
    同步块是通过锁定一个指定的对象,来对块中的代码进行同步;
    同步方法和 同步块之间的相互制约只限于同一个对象之间, 静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例没有关系。
    如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。
     
       使用方法同步保护共享数据。示例:
    public class ThreadTest  implements Runnable{
    public synchronized void run(){
      for(int i=0;i<10;i++) {
        System.out.print(" " + i);
      }
    }
     
    public static void main(String[] args) {
      Runnable r1 = new ThreadTest(); 
      Runnable r2 = new ThreadTest();
      Thread t1 = new Thread(r1);
      Thread t2 = new Thread(r2);
      t1.start();
      t2.start();
    }}
    示例详解:
    代码中可见,run()被加上了synchronized 关键字,但保护的并不是共享数据。
    因为程序中两个线程对象 t1、t2 其实是另外两个线程对象 r1、r2 的线程,这个听起来绕,但是一眼你就能看明白;因为不同的线程对象的数据是不同的, 即 r1,r2 有各自的run()方法,所以输出结果就无法预知。
    这时使用 synchronized 关键字可以让某个时刻,只有一个线程可以访问该对象synchronized数据。每个对象都有一个“锁标志”,当这个对象的一个线程访问这个对象的某个synchronized 数据时,这个对象的所有被synchronized 修饰的数据将被上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的synchronized 数据时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问synchronized 数据。
     
    接下来,我们把 r2 给注释掉, 即只保留一个 r 对象。如下:
     
    public class ThreadTest implements Runnable{
     
    public synchronized void run(){
      for(int i=0;i<10;i++){
        System.out.print(" " + i);
      }
    }
    public static void main(String[] args){
      Runnable r = new ThreadTest();
      Thread t1 = new Thread(r);
      Thread t2 = new Thread(r);
      t1.start();
      t2.start();
    }} 
    示例详解:
    如果你运行1000 次这个程序,它的输出结果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因为这里的synchronized 保护的是共享数据。
    t1,t2 是同一个对象(r)的两个线程,当其中的一个线程(例如:t1)开始执行run()方法时,由于run()受synchronized保护,所以同一个对象的其他线程(t2)无法访问synchronized 方法(run 方法)。只有当t1执行完后t2 才有机会执行。
     
     
    4、使用块同步,示例:
    public class ThreadTest implements Runnable{
       public void run(){
          synchronized(this){  //与上面示例不同于关键字使用
               for(int i=0;i<10;i++){
                   System.out.print(" " + i);
               }
          } 
       }
       public static void main(String[] args){
           Runnable r = new ThreadTest();
           Thread t1 = new Thread(r);
           Thread t2 = new Thread(r);
           t1.start();
           t2.start();
       }
    示例详解:
    这个与上面示例的运行结果也一样的。这里是把保护范围缩到最小,this 代表 ‘这个对象’  。没有必要把整个run()保护起来,run()中的代码只有一个for循环,所以只要保护for 循环就可以了。
     
    最后,再看一个示例:
    public class ThreadTest implements Runnable{
     
    public void run(){
      for(int k=0;k<5;k++){
        System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);
      }
     
    synchronized(this){
      for(int k=0;k<5;k++) {
        System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);
      }} }
     
    public static void main(String[] args){
      Runnable r = new ThreadTest();
      Thread t1 = new Thread(r,"t1_name");
      Thread t2 = new Thread(r,"t2_name");
      t1.start();
      t2.start();
    } } 
    //运行结果:
    t1_name : for loop : 0
    t1_name : for loop : 1
    t1_name : for loop : 2
    t2_name : for loop : 0
    t1_name : for loop : 3
    t2_name : for loop : 1
    t1_name : for loop : 4
    t2_name : for loop : 2
    t1_name : synchronized for loop : 0
    t2_name : for loop : 3
    t1_name : synchronized for loop : 1
    t2_name : for loop : 4
    t1_name : synchronized for loop : 2
    t1_name : synchronized for loop : 3
    t1_name : synchronized for loop : 4
    t2_name : synchronized for loop : 0
    t2_name : synchronized for loop : 1
    t2_name : synchronized for loop : 2
    t2_name : synchronized for loop : 3
    t2_name : synchronized for loop : 4
    示例详解:
    第一个for 循环没有受synchronized 保护。对于第一个for 循环,t1,t2 可以同时访问。运行结果表明t1 执行到了k=2 时,t2 开始执行了。t1 首先执行完了第一个for 循环,此时t2还没有执行完第一个for 循环(t2 刚执行到k=2)。t1 开始执行第二个for 循环,当t1的第二个for 循环执行到k=1 时,t2 的第一个for 循环执行完了。t2 想开始执行第二个for 循环,但由于t1 首先执行了第二个for 循环,这个对象的锁标志自然在t1 手中(synchronized 方法的执行权也就落到了t1 手中),在t1 没执行完第二个for 循环的时候,它是不会释放锁标志的。所以t2 必须等到t1 执行完第二个for 循环后,它才可以执行第二个for 循环。
     
    Volatile 同步:
        a. volatile关键字为域变量的访问,提供了一种免锁机制。
        b.使用volatile修饰域相当于告诉虚拟机,该域可能会被其他线程更新
        c.因此每次使用该域就要重新计算,而不是使用寄存器中的值。
        d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量 。
        例如: 
            在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。 
        代码实例: 
            //只给出要修改的代码,其余代码与上同
            class Bank {
                //需要同步的变量加上volatile
                private volatile int account = 100;
     
                public int getAccount() {
                    return account;
                }
                //这里不再需要synchronized 
                public void save(int money) {
                    account += money;
                }
            }
     
        注:多线程中的非同步问题,主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。
    用final域,有锁保护的域 和volatile域 可以避免非同步的问题。 
     
    原子变量同步:
    需要使用线程同步的根本原因在于对普通变量的操作不是原子的。那么什么是原子操作呢?
    原子操作就是指将 读取变量值、修改变量值、保存变量值看成一个整体来操作即-这几种行为要么同时完成,要么都不完成。
    在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。
    其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;
    可扩展Number,允许那些  处理--机遇数字类的工具和实用工具 进行统一访问。
    AtomicInteger类常用方法:
    AtomicInteger(int initialValue) : 创建具有给定初始值的新的
    AtomicInteger.addAddGet(int dalta) : 以原子方式 将给定值与当前值相加
    get() : 获取当前值
    class Bank {
        private AtomicInteger account = new AtomicInteger(100);
        public AtomicInteger getAccount() {
            return account; 
        } 
        public void save(int money) {
            account.addAndGet(money);
        }
    }
     
    补充--原子操作主要有:  
    对于引用变量和大多数原始变量(long和double除外)的读写操作;  
    对于所有使用volatile修饰的变量(包括long和double)的读写操作。另外,可以使用线程池进行管理及优化。
    我的相关文章推荐链接地址点击跳转:线程优化及线程池管理。
     
    阻塞队列同步:
        前面同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。
    使用javaSE5.0版本中新增的java.util.concurrent包,将有助于简化开发。本小节主要是使用LinkedBlockingQueue<E>来实现线程的同步。
    LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO)。
    LinkedBlockingQueue 类常用方法 LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue 。
    put(E e) : 在队尾添加一个元素,如果队列满则阻塞;
    size() : 返回队列中的元素个数  ;    take() : 移除并返回队头元素,如果队列空则阻塞;
    代码实例: 实现商家生产商品和买卖商品的同步。
    注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:
      add()方法会抛出异常
      offer()方法返回false
      put()方法会阻塞。
     
    重入锁同步:
    在 JavaSE5.0中 新增了一个 java.util.concurrent 包来支持同步。 
    ReentrantLock类是 可重入、互斥、实现了Lock接口的锁,   它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
    ReenreantLock类的常用方法有:
    ReentrantLock() : 创建一个ReentrantLock实例 
    lock() : 获得锁 ;        unlock() : 释放锁 ;
     
    注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用 。例如: 
            class Bank {
                private int account = 100;   
                private Lock lock = new ReentrantLock(); //需要声明这个锁
                public int getAccount() {
                    return account;
                }
                
                public void save(int money) { //这里不再需要synchronized 
                    lock.lock();
                    try{
                        account += money;
                    }finally{
                        lock.unlock();
                    }
                }
            }
        注:关于Lock对象和synchronized关键字的选择: 
            a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。 
            b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 
            c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 
     
    局部变量同步:
        如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,
    而不会对其他线程产生影响。
    ThreadLocal 类的常用方法:
    ThreadLocal() : 创建一个线程本地变量 
    get() : 返回此线程局部变量的当前线程副本中的值 
    initialValue() : 返回此线程局部变量的当前线程的"初始值" 
    set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
      
    public class Bank{
                //使用ThreadLocal类管理共享变量account
                private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                    @Override
                    protected Integer initialValue(){
                        return 100;
                    }
                };
                public void save(int money){
                    account.set(account.get()+money);
                }
                public int getAccount(){
                    return account.get();
                }
            }
        注:ThreadLocal与同步机制;
            a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
            b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式
     
     
     
    4、AsyncTask:
    具体的使用代码就不贴上来了,可以去看我的一篇博文。但值得一说的是,上面说过HandlerThread只开一条线程,任务都被阻塞在一个队列中,那么就会使阻塞的任务延迟了,而AsyncTask开启线程的方法asyncTask.execute()默认是也是开启一个线程和一个队列的,不过也可以通过asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)开启一个含有5个新线程的线程池,也就是说有个5个队列了,假如说你执行第6个耗时任务时,除非前面5个都还没执行完,否则任务是不会阻塞的,这样就可以大大减少耗时任务延迟的可能性,这也是它的优点所在。当你想多个耗时任务并发的执行,那你更应该选择AsyncTask。
    --------------------- 
      

      什么是原子操作? Java Concurrency API中有哪些原子操作类?

        原子操作是执行单个任务单元的操作,这个操作不需要干扰其他操作,可以理解为当前情况下不可再分的操作,远在操作是多线程环境下避免数据不一致而存在的必需品。

    int++就不是原子操作,如果一个线程读取它的值并行+1操作,而另外一个线程读取了旧的值,则会导致错误的结果。为了解决这个问题,

    我们需要确保递增操作是原子的,可以使用同步原语(synchronization),也可以使用Java5 包装的AtomicInteger直接完成原子操作。

    在包java.util.concurrent.atomic下面的一Atomic开头的类都是原子类。例如AtomicIntegerAtomicReference

    什么是Java Cuncurrency API中Lock 接口?  相对于同步(synchronization)有什么优势?

    首先我们看下Lock接口的定义:
    Lock是一个控制多线程访问共享资源的工具类,比使用synchronized方法或者语句有了更加扩展性的操作,结构灵活,可以有完全不同的属性,

    也可以支持多个相关类的条件对象。使用方法如下:

      {@code
       Lock l = ...;
       l.lock();
       try {
       // 访问锁保护的共享资源
        } finally {
       l.unlock();
      }}
    
    • 相对于synchronization 有如下优点:
    • 可以使锁更公平
    • 可以使线程在等待锁的时候响应中断;
    • 可以让线程尝试获取锁,并在无法获取锁的时候立即返回 或者等待一段时间
    • 可以在不同的作用域,以不同的顺序获取和释放锁

    什么是阻塞队列(BlockingQueue)?如何使用阻塞队列实现生产者—消费者问题?

    BlockingQueue是一个阻塞队列,顾名思义,当进行检索和删除的时候,如果队列为空,则阻塞等待直至队列非空,当添加元素到队列的时候,如果没有空间会阻塞等待到队列有空间为止。接口定义如下图所示:

    BlockingQueue不接受Null值,如果存储Null会抛出空指针异常。BlocingQueue的实现都是线程安全的,使用内部锁或者其他的并发控制方法。

    BlockingQueue定义的常用方法如下:

    • add(anObject):把anObject加到BlockingQueue里,如果BlockingQueue可以容纳,则返回true,否则抛出异常。
    • offer(anObject):表示如果可能的话,将anObject加到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false。
    • put(anObject):把anObject加到BlockingQueue里,如果BlockingQueue没有空间,则调用此方法的线程被阻断直到BlockingQueue里有空间再继续。
    • poll(time):取走BlockingQueue里排在首位的对象,若不能立即取出,则可以等time参数规定的时间,取不到时返回null。
    • take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的对象被加入为止。

    BlockingQueue有四个具体的实现类,根据不同需求,选择不同的实现类:

    • ArrayBlockingQueue:规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小。其所含的对象是以FIFO(先入先出)顺序排序的。
    • LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定。其所含的对象是以FIFO顺序排序的。
    • PriorityBlockingQueue:类似于LinkedBlockingQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数所带的Comparator决定的顺序。
    • SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。

    LinkedBlockingQueueArrayBlockingQueue比较起来,它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue。
    实现生产者—消费者代码如下:Message.java

    public class Message {
        private String msg ;
    
        public String getMsg() {
            return msg;
        }
    
        public void setMsg(final String msg) {
            this.msg = msg;
        }
    
        public Message(final String msg) {
            this.msg = msg;
        }
    }
    

    Producer.java

    import java.util.concurrent.BlockingQueue;
    
    public class Producer implements Runnable {
        private BlockingQueue<Message> messageBlockingDeque;
    
        public Producer(final BlockingQueue<Message> messageBlockingDeque) {
            this.messageBlockingDeque = messageBlockingDeque;
        }
    
        @Override
        public void run() {
            for (int i = 0; i <100 ; i++) {
               Message msg = new Message(" "+i);
                try {
                    Thread.sleep(i);
                    messageBlockingDeque.put(msg);
                    System.out.println("Produced:"+msg.getMsg());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Message msg = new Message("exit");
            try {
                messageBlockingDeque.put(msg);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    Consumer.java

    public class Consumer implements Runnable {
        private BlockingQueue<Message> blockingDeque;
    
        public Consumer(final BlockingQueue<Message> blockingDeque) {
            this.blockingDeque = blockingDeque;
        }
    
        @Override
        public void run() {
             Message message;
            try {
                while((message= blockingDeque.take()).getMsg()!="exit"){
                    Thread.sleep(10);
                    System.out.println("Consumed:"+message.getMsg());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    ProductConsumerService.java

    public class ProductConsumerService {
        public static void main(String[] args) {
            BlockingQueue<Message> queue = new ArrayBlockingQueue<Message>(10);
            Producer producer = new Producer(queue);
            Consumer consumer = new Consumer((queue));
    
            ExecutorService executorService = Executors.newCachedThreadPool();
            executorService.submit(producer);
            executorService.submit(consumer);
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            executorService.shutdown();
        }
    }

    什么是Executors类?

    Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法。

    Executors可以用于方便的创建线程池。

    什么是Executors框架?

    在Java5中, Executor框架随同java.util.concurrent.Executor接口一同被引入,Executor框架标准化了线程的调用,调度,执行以及异步任务的控制。
    无节制的线程创建会引起内存溢出,创建有限线程数的线程池(ThreadPool)是一个好的选择,线程可以预先创建,回收再利用。

    Executor框架促进了Java中线程池的创建,示例代码如下:

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class SimpleThreadPool {
    
        public static void main(String[] args) {
            ExecutorService executor = Executors.newFixedThreadPool(5);
            for (int i = 0; i < 10; i++) {
                Runnable worker = new WorkerThread("" + i);
                executor.execute(worker);
              }
            executor.shutdown();
            while (!executor.isTerminated()) {
            }
            System.out.println("Finished all threads");
        }
    
    }

    什么是并发集合类?

    Java集合类都是快速失效的,这就意味着当集合被改变且一个线程在使用迭代器遍历集合的时候,迭代器的next()方法

    将抛出ConcurrentModificationException异常。
    并发容器支持并发的遍历和并发的更新。主要的类有ConcurrentHashMapCopyOnWriteArrayList 和CopyOnWriteArraySet

    Java 8中,Concurrency API有哪些改进?

    一些改进如下:
    ConcurrentHashMap 改进了compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() 和 search() 方法.
    CompletableFuture that may be explicitly completed (setting its value and status).
    Executors 的newWorkStealingPool() 方法创建一个 work-stealing 线程池,使用目前机器上可用的处理器作为它的并行级别。

    什么是FutureTask 类?

    FutureTask是一个可取消的异步计算类,它实现了Future接口,因为可以在Executors中使用,执行异步处理操作,大多数情况下,我们不需要FutureTask类。
    仅仅当我们打算重写Future接口的一些方法并保持原来基础的实现是,它就变得非常有用。我们可以仅仅继承于它并重写我们需要的方法。

    什么是Callable和Future?

    Java 5在concurrency包中引入了java.util.concurrent.Callable 接口,它和Runnable接口很相似,但它可以返回任何一个对象或者抛出一个异常。

    Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法去在线程池中执行Callable内的任务。由于Callable任务是并行的,我们必须等待它返回的结果。java.util.concurrent.Future应运而生。在线程池提交Callable任务后返回了一个Future对象,使用它我们可以知道Callable任务的状态和得到Callable返回的执行结果。Future提供了get()方法让我们可以等待Callable结束并获取它的执行结果,示例代码如下:

    public class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            Thread.sleep(1000);
            return Thread.currentThread().getName();
        }
    
        public static void main(String[] args) {
            ExecutorService service = Executors.newFixedThreadPool(10);
            List<Future<String>> list = new ArrayList<Future<String>>();
            Callable<String> callable = new MyCallable();
            for (int i = 0; i < 10; i++) {
                Future<String> future = service.submit(callable);
                list.add(future);
            }
    
            for (Future<String> fut : list) {
                try {
                    System.out.println(new Date() + "::" + fut.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
    
            service.shutdown();
        }
    }
    

    运行结果:

    Mon Oct 06 17:24:52 CST 2014::pool-1-thread-1
    Mon Oct 06 17:24:53 CST 2014::pool-1-thread-2
    Mon Oct 06 17:24:53 CST 2014::pool-1-thread-3
    Mon Oct 06 17:24:53 CST 2014::pool-1-thread-4
    Mon Oct 06 17:24:53 CST 2014::pool-1-thread-5
    Mon Oct 06 17:24:53 CST 2014::pool-1-thread-6
    Mon Oct 06 17:24:53 CST 2014::pool-1-thread-7
    Mon Oct 06 17:24:53 CST 2014::pool-1-thread-8
    Mon Oct 06 17:24:53 CST 2014::pool-1-thread-9
    Mon Oct 06 17:24:53 CST 2014::pool-1-thread-10

  • 相关阅读:
    【python-leetcode107-树的宽度遍历】二叉树的层次遍历Ⅱ
    Spring依赖
    Dubbo依赖
    免费内网穿透
    Oracle分析函数Over()
    测压工具
    编译器版本
    修改gridfilters.js源码,往后台多传递一个参数,并设置NumericFilter、StringFilter默认提示信息
    网站10分钟不操作会话退出
    win10彻底关闭windows defender,解决无故占用大量CPU问题
  • 原文地址:https://www.cnblogs.com/awkflf11/p/10782917.html
Copyright © 2011-2022 走看看