线程

线程

一、概述

1、概念

进程:程序的动态执行过程,是资源(CPU、内存等)分配的基本单位。

线程:进程的一个执行流,是程序执行时的最小单位,CPU调度和分派的基本单位。

线程对象:可以产生线程的对象,比如在Java平台中Thread对象,Runnable对象。

多线程:多个线程,多线程不是为了提高程序执行速度(性能甚至更低),而是提高应用程序的使用效率

并行:多条指令在多个处理器上同时执行。

并发:同一时刻只有一条指令执行,但多个进程指令被快速轮换执行,宏观上具有多个进程同时执行的效果。

高并发:通过设计保证系统能够同时并行处理很多请求。

进程和线程的关系

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
  2. 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量
  3. 处理机分给线程,即真正在处理机上运行的是线程。
  4. 不同进程的线程间要利用消息通信的办法实现同步。

多线程编程优点

  1. 线程可以共享内存,效率高。
  2. 创建线程代价小。
  3. JAVA内置多线程功能支持。
2、多线程适用情景

(1)高并发

系统接受实现多用户多请求的高并发时,通过多线程来实现。(秒杀等)

(2)线程后台处理大任务

一个程序是线性执行的。如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的。那用户就不得不等待它执行完。

这时候可以开线程把花大量时间处理的任务放在线程处理,这样线程在后台处理时,主程序也可以继续执行下去,用户就不需要等待。线程执行完后执行回调函数。(聊天socket)

(3)大任务

大任务处理起来比较耗时,这时候可以起到多个线程并行加快处理(分片上传)。

二、线程的创建

1、继承 Thread类
//(1)定义子类,重写run()方法(线程执行体)
class SomeThead extends Thraad   { 
    public void run()   { 
     //do something here 
     this.setPriority(pro);
    }  
 } 
 
public static void main(String[] args){
  //(2)创建线程对象(类实例)
 SomeThread oneThread = new SomeThread();   
  //(3)调用start()方法启动线程
 oneThread.start(); 
}

用this即可获得线程对象,线程之间无法共享线程类的实例变量。

2、实现 Runnable接口
//(1)定义接口实现类,重写run()方法
class SomeRunnable implements Runnable   { 
  public void run()   { 
  //do something here  
  hread.currentThread().setPriority(pro);
  }  
} 

public static void main(String[] args){
    //(2)创建类实例
    Runnable oneRunnable = new SomeRunnable();   
    //(3)以此作为Thread的target创建Thread对象
    Thread oneThread = new Thread(oneRunnable); 
    //(4)调用start()方法启动线程
    oneThread.start(); 
}

用Thread.currentThread()方法获得线程对象,线程之间可以共享线程类的实例变量。

3、实现 Callable接口
//(1)定义接口实现类,重写call()方法(有返回值,可以抛出异常)
class SomeCallable implements Callable   { 
  public ? call()   { 
  	//do something here  
   	return ?;
  }  
} 

public static void main(String[] args){   
	//(2)使用FutureTask类包装Callale对象
    Callable oneCallable = new SomeCallable();  
    FutureTask oneTask = new FutureTask(oneCallable); 
	//(3)以此作为Thread的target创建Thread对象
    // FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了 Future和Runnable接口
    Thread oneThread = new Thread(oneTask);   
    //(4)调用get()方法获得返回值
    oneThread.start(); 
}

三种方式比较

  1. Thread: 继承方式, 不建议使用, 因为Java是单继承的,不够灵活。

  2. Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制。

  3. Callable: Callable是重写的call()方法并且有返回值,可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行。

  4. 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般通过Thread类来启动线程。

    推荐后两种方法,三种实现方式本质上都是Runnable实现。

三、线程的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fxISTvyH-1595590719567)(C:\Users\ddjj6\AppData\Roaming\Typora\typora-user-images\1565601904563.png)]

  1. 新建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 为线程分配内存并初始化成员变量。

    如果希望子线程调用start()方法后立即执行,可以使用Thread.sleep()方式使主线程睡眠,转去执行子线程。

  2. 就绪(runnable)状态: 调用了start()方法, 为线程创建栈和程序计数器,等待CPU进行调度。

  3. 运行(running)状态: 获得处理器资源,执行run()方法。

  4. 阻塞(blocked)状态: 暂时停止执行线程,解除阻塞后进入就绪状态。

  5. 死亡(terminated)状态: 线程销毁,此时不可调用start()方法。

不能对已启动或死亡的线程再次调用start()方法,否则会出现Java.lang.IllegalThreadStateException异常。

四、控制线程

1、线程合并

即将几个并行线程的线程合并为一个单线程执行,应用场景为当前线程等待另一线程完成。

void join()      
     当前线程等该加入该线程后面,等待该线程终止。    
void join(long millis)  
     当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度  
void join(long millis,int nanos)   
     等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度  
a.join():当前线程等待a线程完成。
2、后台线程

定义:在后台运行的,为其他线程提供服务的线程。设置线程对象的方法setDaemon(true),必须在线程启动前设置

特征:若前台线程全部死亡,则后台线程自动死亡。

主线程默认为前台线程,子线程特性与父线程相同。

3、线程睡眠

**sleep()睡眠的始终是当前正在运行的线程。**sleep是静态方法,最好不要用Thread的实例对象调用。

 Thread.sleep(10);  

就绪状态进入到运行状态,是由系统控制的,所以如果调用Thread.sleep(1000),可能结果会大于1秒。

4、线程让步

可以让当前正在执行的线程)进入就绪状态。yield()是静态方法。

yield()与sleep()区别:

  1. yield()进入就绪状态而非阻塞状态。所以有可能刚进入就绪状态,又被调度到运行状态。
  2. yield()只会给优先级相同或更高的线程执行机会。
  3. sleep()抛出InterruptedException异常而yield()没有。
  4. sleep()具有更好的移植性,通常不要依靠yield方法来控制并发线程的执行。
5、改变线程优先级

在默认情况下,main线程具有普通优先级。优先级高的执行机会越多。子线程优先级与父线程相同。

//设置一个指定线程的优先级
setPriority(int newPriority)
//返回一个指定线程的优先级
getPriority()

由于不同操作系统有限级不同,故应尽量避免直接指定优先级,而应采取三个静态常量设置优先级。

MAX_PRIORITY   =10

MIN_PRIORITY   =1

NORM_PRIORITY   =5
6、线程结束

Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit是极端不安全的。

想要安全有效的结束一个线程,可以使用下面的方法:

​ • 正常执行完run方法,然后结束掉;

​ • 在线程执行体种控制循环条件和判断条件的标识符来结束掉线程。

public class MyThread extends Thread {  
    int i=0;  
    boolean next=true;  
    @Override  
    public void run() {  
        while (next) {  
            if(i==10)  
                next=false;  
            i++;  
            System.out.println(i);  
        }  
    }  
}

一个线程结束另一个线程

img

五、线程同步

当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

1、同步代码块

即有synchronized关键字修饰的语句块。同步监视器通常显式指定为共享资源。

synchronized (this) {  
    
}

2、同步方法

即有synchronized关键字修饰的方法。**同步监视器为this,故无需显式指定同步监视器。**在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

public synchronized void save(){} 

注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。

3、释放同步监视器

(书749)

4、特殊域变量

• volatile关键字为域变量的访问提供了一种免锁机制;

• 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新;

• 因此每次使用该域就要重新计算,而不是使用寄存器中的值;

• volatile能保证可见性,有序性;

• volatile不能保证原子操作,不能保证线程安全;

• 它也不能用来修饰final类型的变量。能不使用它就不适用它

[复制代码](javascript:void(0)😉

 public class SynchronizedThread {
 
        class Bank {
 
            private volatile int account = 100;
 
            public int getAccount() {
                return account;
            }
 
            /**
             * 用同步方法实现
             * 
             * @param money
             */
            public synchronized void save(int money) {
                account += money;
            }
 
            /**
             * 用同步代码块实现
             * 
             * @param money
             */
            public void save1(int money) {
                synchronized (this) {
                    account += money;
                }
            }
        }
 
        class NewThread implements Runnable {
            private Bank bank;
 
            public NewThread(Bank bank) {
                this.bank = bank;
            }
 
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    // bank.save1(10);
                    bank.save(10);
                    System.out.println(i + "账户余额为:" +bank.getAccount());
                }
            }
 
        }
 
        /**
         * 建立线程,调用内部类
         */
        public void useThread() {
            Bank bank = new Bank();
            NewThread new_thread = new NewThread(bank);
            System.out.println("线程1");
            Thread thread1 = new Thread(new_thread);
            thread1.start();
            System.out.println("线程2");
            Thread thread2 = new Thread(new_thread);
            thread2.start();
        }
 
        public static void main(String[] args) {
            SynchronizedThread st = new SynchronizedThread();
            st.useThread();
        }
 

[复制代码](javascript:void(0)😉

**注:**多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。用final域,有锁保护的域和volatile域可以避免非同步的问题。

4、同步锁

ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。ReenreantLock类的常用方法有:

 ReentrantLock() : 创建一个ReentrantLock实例         
 lock() : 获得锁        
 unlock() : 释放锁

class Bank {
  
            private int account = 100;
            //需要声明这个锁
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
    
            //这里不再需要synchronized 
            public void save(int money) {
                //获得锁    
                lock.lock();
                try{
                    account += money;
                }finally{
                    //释放锁
                    lock.unlock();
                }
                
            }
5、死锁

产生死锁的四个必要条件如下。即任意一个条件不满足即不会产生死锁。

(1)死锁的四个必要条件

  • 互斥条件:资源不能被共享,只能被同一个进程使用

  • 请求与保持条件:已经得到资源的进程可以申请新的资源

  • 非剥夺条件:已经分配的资源不能从相应的进程中被强制剥夺

  • 循环等待条件:系统中若干进程组成环路,该环路中每个进程都在等待相邻进程占用的资源

    (2)处理死锁的方法

  • 忽略该问题,也即鸵鸟算法。当发生了什么问题时,不管他,直接跳过,无视它;

  • 检测死锁并恢复;

  • 资源进行动态分配;

  • 破除上面的四种死锁条件之一。

六、线程通信

1、三种方法

wait(), notify(),notifyall()属于Object类,但必须由同步监视器对象调用

生产者消费者

单个 if notify

多个

    class Resource{  
        private String name;  
        private int count=1;  
        private boolean flag=false;  
        public synchronized void set(String name){  
            while(flag) /*原先是if,现在改成while,这样生产者线程从冻结状态醒来时,还会再判断flag.*/ 
                try{wait();}catch(Exception e){}  
            this.name=name+"---"+count++;  
            System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);  
            flag=true;  
            this.notifyAll();/*原先是notity(), 现在改成notifyAll(),这样生产者线程生产完一个商品后可以将等待中的消费者线程唤醒,否则只将上面改成while后,可能出现所有生产者和消费者都在wait()的情况。*/  
        }  
        public synchronized void out(){  
            while(!flag) /*原先是if,现在改成while,这样消费者线程从冻结状态醒来时,还会再判断flag.*/  
                try{wait();}catch(Exception e){}  
            System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);  
            flag=false;  
            this.notifyAll(); /*原先是notity(), 现在改成notifyAll(),这样消费者线程消费完一个商品后可以将等待中的生产者线程唤醒,否则只将上面改成while后,可能出现所有生产者和消费者都在wait()的情况。*/  
        }  
    }  
    public class ProducerConsumerDemo{  
        public static void main(String[] args){  
            Resource r=new Resource();  
            Producer pro=new Producer(r);  
            Consumer con=new Consumer(r);  
            Thread t1=new Thread(pro);  
            Thread t2=new Thread(con);  
            Thread t3=new Thread(pro);  
            Thread t4=new Thread(con);  
            t1.start();  
            t2.start();  
            t3.start();  
            t4.start();  
        }  
    } 

2、Condition

jdk1.5中,提供了多线程的升级解决方案为:

​ (1)将同步synchronized替换为显式的Lock操作;

​ (2)将Object类中的wait(), notify(),notifyAll()替换成了Condition对象,该对象可以通过Lock锁对象获取;

​ (3)一个Lock对象上可以绑定多个Condition对象,这样实现了本方线程只唤醒对方线程,而jdk1.5之前,一个同步只能有一个锁,不同的同步只能用锁来区分,且锁嵌套时容易死锁。

可以使用lock的condition对象的await(), signal(),signalall()方法进行通信。

    class Resource{  
        private String name;  
        private int count=1;  
        private boolean flag=false;  
        //Lock是一个接口,ReentrantLock是该接口的一个直接子类
        private Lock lock = new ReentrantLock();
        //创建代表生产者方面的Condition对象
        private Condition condition_pro=lock.newCondition();
        //使用同一个锁,创建代表消费者方面的Condition对象  
        private Condition condition_con=lock.newCondition(); 
          
        public void set(String name){  
            //锁住此语句与lock.unlock()之间的代码  
            lock.lock();
            try{  
                while(flag)  
                    //生产者线程在生产者conndition_pro对象上等待  
                    condition_pro.await(); 
                this.name=name+"---"+count++;  
                System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);  
                flag=true;  
                 condition_con.signalAll();  
            }  
            finally{  
                 //unlock()要放在finally块中。  
                lock.unlock();
            }  
        }  
        
        public void out(){  
            //锁住此语句与lock.unlock()之间的代码  
            lock.lock(); 
            try{  
                while(!flag)  
                    //消费者线程在消费者conndition_con对象上等待  
                    condition_con.await(); 
            System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name); 
            flag=false;  
            //唤醒所有在condition_pro对象下等待的线程,也就是唤醒所有生产者线程 
            condition_pro.signqlAll(); 
            }  
            finally{  
                lock.unlock();  
            }  
        }  
    }  

3、阻塞队列

**BlockingQueue具有一个特征:**当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则线程被阻塞;但消费者线程试图从BlockingQueue中取出元素时,如果队列已空,则该线程阻塞。阻塞队列里面的put、take方法是被加:synchronized 同步限制

ArrayBlockingQueue :基于数组实现的BlockingQueue队列。

LinkedBlockingQueue:基于链表实现的BlockingQueue队列。

PriorityBlockingQueue:它并不是保准的阻塞队列,该队列调用remove()、poll()、take()等方法提取出元素时,并不是取出队列中存在时间最长的元素,而是队列中最小的元素。
                       它判断元素的大小即可根据元素(实现Comparable接口)的本身大小来自然排序,也可使用Comparator进行定制排序。

SynchronousQueue:同步队列。对该队列的存、取操作必须交替进行。

DelayQueue:它是一个特殊的BlockingQueue,底层基于PriorityBlockingQueue实现,不过,DelayQueue要求集合元素都实现Delay接口(该接口里只有一个long getDelay()方法),
            DelayQueue根据集合元素的getDalay()方法的返回值进行排序。

BlockingQueue提供如下两个支持阻塞的方法,加了synchronized 同步限制

**(1)put(E e):**尝试把Eu元素放如BlockingQueue中,如果该队列的元素已满,则阻塞该线程。

**(2)take():**尝试从BlockingQueue的头部取出元素,如果该队列的元素已空,则阻塞该线程。

BlockingQueue继承了Queue接口,当然也可以使用Queue接口中的方法,这些方法归纳起来可以分为如下三组:

**(1)**在队列尾部插入元素,包括add(E e)、offer(E e)、put(E e)方法,当该队列已满时,这三个方法分别会抛出异常、返回false、阻塞队列。

**(2)**在队列头部删除并返回删除的元素。包括remove()、pull()、和take()方法,当该队列已空时,这三个方法分别会抛出异常、返回false、阻塞队列。

**(3)**在队列头部取出但不删除元素。包括element()和peek()方法,当队列已空时,这两个方法分别抛出异常、返回false。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueTest{
    public static void main(String[] args)throws Exception{
        //创建一个容量为1的BlockingQueue
        
        BlockingQueue<String> b=new ArrayBlockingQueue<>(1);
        //启动3个生产者线程
        new Producer(b).start();
        new Producer(b).start();
        new Producer(b).start();
        //启动一个消费者线程
        new Consumer(b).start();
        
    }
}
class Producer extends Thread{
    private BlockingQueue<String> b;
    
    public Producer(BlockingQueue<String> b){
        this.b=b;
        
    }
    public synchronized void run(){
        String [] str=new String[]{
            "java",
            "struts",
            "Spring"
        };
        for(int i=0;i<9999999;i++){
            System.out.println(getName()+"生产者准备生产集合元素!");
            try{
            
                b.put(str[i%3]);
                sleep(1000);
                System.out.println(getName()+"生产完成:"+str[i%3]);
                //尝试放入元素,如果队列已满,则线程被阻塞
                
            }catch(Exception e){System.out.println(e);}
        }
        
    }
}
class Consumer extends Thread{
    private BlockingQueue<String> b;
    public Consumer(BlockingQueue<String> b){
        this.b=b;
    }
    public  synchronized  void run(){
    
        while(true){
            System.out.println(getName()+"消费者准备消费集合元素!");
            try{
            	 sleep(1000);

                //尝试取出元素,如果队列已空,则线程被阻塞
                System.out.println(getName()+"消费完:"+b.take());
            }catch(Exception e){System.out.println(e);}
  
        }
    
    }
}

七、线程组和异常

一般情况下,子线程和父线程在同个线程组内,加入后运行过程中不得改变所属线程组,可在创建时指定线程组名,但之后不可修改

new thread(ThreadGroup group, String name) 

其余见书761后

八、线程池

合理利用线程池能够带来三个好处。

  1. 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
1、Executors工厂类

Executor线程池框架的**最大优点是把任务的提交和执行解耦。**客户端将要执行的任务封装成Task,然后提交即可。而Task如何执行客户端则是透明的。如提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果。

(1)使用Executors的静态工厂类创建线程池

1、newFixedThreadPool() :
作用:该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也不会销毁已经创建好的线程,自始自终都是那几个固定的线程在工作,所以该线程池可以控制线程的最大并发数。
栗子:假如有一个新任务提交时,线程池中如果有空闲的线程则立即使用空闲线程来处理任务,如果没有,则会把这个新任务存在一个任务队列中,一旦有线程空闲了,则按FIFO方式处理任务队列中的任务。
2、newCachedThreadPool() :
作用:该方法返回一个可以根据实际情况调整线程池中线程的数量的线程池。即该线程池中的线程数量不确定,是根据实际情况动态调整的。
栗子:假如该线程池中的所有线程都正在工作,而此时有新任务提交,那么将会创建新的线程去处理该任务,而此时假如之前有一些线程完成了任务,现在又有新任务提交,那么将不会创建新线程去处理,而是复用空闲的线程去处理新任务。那么此时有人有疑问了,那这样来说该线程池的线程岂不是会越集越多?其实并不会,因为线程池中的线程都有一个“保持活动时间”的参数,通过配置它,如果线程池中的空闲线程的空闲时间超过该“保存活动时间”则立刻停止该线程,而该线程池默认的“保持活动时间”为60s。
3、newSingleThreadExecutor() :
作用:该方法返回一个只有一个线程的线程池,即每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待这一个线程空闲,当这个线程空闲了再按FIFO方式顺序执行任务队列中的任务。
4、newScheduledThreadPool() :
作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。
5、newSingleThreadScheduledExecutor() :
作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。只不过和上面的区别是该线程池大小为1,而上面的可以指定线程池的大小。

**注:**Executors只是一个工厂类,它所有的方法返回的都是ThreadPoolExecutorScheduledThreadPoolExecutor这两个类的实例。

(2) ExecutorService执行方法

execute(Runnable)

​ 这个方法接收一个Runnable实例,并且异步的执行

ExecutorService executorService = Executors.newSingleThreadExecutor();

executorService.execute(new Runnable() {
public void run() {
    System.out.println("Asynchronous task");
}
});

executorService.shutdown();

submit(Runnable)

submit(Runnable)execute(Runnable)区别是前者可以返回一个Future对象,通过返回的Future对象,我们可以检查提交的任务是否执行完毕。

Future future = executorService.submit(new Runnable() {
public void run() {
    System.out.println("Asynchronous task");
}
});
 //returns null if the task has finished correctly.future.get()方法会产生阻塞。
future.get(); 

submit(Callable)

submit(Callable)submit(Runnable)类似,也会返回一个Future对象。

Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
    System.out.println("Asynchronous Callable");
    return "Callable Result";
}
});
//future.get()可以返回Callable接口中的call()的返回值。future.get()方法会产生阻塞。
System.out.println("future.get() = " + future.get());

invokeAny(…)

接收的是一个Callable的集合,返回所有Callable任务中其中一个任务的执行结果。

ExecutorService executorService = Executors.newSingleThreadExecutor();

Set<Callable<String>> callables = new HashSet<Callable<String>>();

callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 2";
}
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
    return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();

invokeAll(…)

接收一个Callable集合,返回一个Future的List,其中对应着每个Callable任务执行后的Future对象。

ExecutorService executorService = Executors.newSingleThreadExecutor();

Set<Callable<String>> callables = new HashSet<Callable<String>>();

callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 1";
}
});
callables.add(new Callable<String>() {
public String call() throws Exception {
    return "Task 2";
}
});
callables.add(new Callable<String>() {
    public String call() throws Exception {
    return "Task 3";
}
});
String result = executorService.invokeAny(callables);
System.out.println("result = " + result);
executorService.shutdown();

(3) ExecutorService关闭方法

​ 当我们使用完成ExecutorService之后应该关闭它,否则它里面的线程会一直处于运行状态。如果的应用程序是通过main()方法启动的,在这个main()退出之后,如果应用程序中的ExecutorService没有关闭,这个应用将一直运行。之所以会出现这种情况,是因为ExecutorService中运行的线程会阻止JVM关闭。

​ 要关闭ExecutorService中执行的线程,我们可以调用**ExecutorService.shutdown()**方法。在调用shutdown()方法之后,ExecutorService不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。

​ 如果想立即关闭ExecutorService,我们可以调用**ExecutorService.shutdownNow()**方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。

2、ForkJoinPool

在Java 8中,引入了自动并行化的概念。它能够让一部分Java代码自动地以并行的方式执行,前提是使用了ForkJoinPool。

ForkJoinPool使用了一个无限队列来保存需要执行的任务,而线程的数量则是通过构造函数传入,如果没有向构造函数中传入希望的线程数量,那么当前计算机可用的CPU数量会被设置为线程数量作为默认值。

ForkJoinPool主要用来使用分治法(Divide-and-Conquer Algorithm)来解决问题。

package work1201.basic;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.TimeUnit;

/**
 * Function: 使用ForkJoinPool完成一个任务的分段执行
 * 简单的打印0-300的数值。用多线程实现并行执行     
 */
public class ForkJoinPoolAction {
    
    public static void main(String[] args) throws Exception{
        PrintTask task = new PrintTask(0, 300);
        //创建实例,并执行分割任务
        ForkJoinPool pool = new ForkJoinPool();
        pool.submit(task);
         //线程阻塞,等待所有任务完成
        pool.awaitTermination(2, TimeUnit.SECONDS);
        pool.shutdown();
    }
}

/**
 * 要完成的任务及分解方式
 * Function: 继承RecursiveAction来实现“可分解”的任务。
 */
class PrintTask extends RecursiveAction{
    //最多只能打印50个数
    private static final int THRESHOLD = 50; 
    private int start;
    private int end;
    
    

    public PrintTask(int start, int end) {
        super();
        this.start = start;
        this.end = end;
    }



    @Override
    protected void compute() {
        
        if(end - start < THRESHOLD){
            for(int i=start;i<end;i++){
                System.out.println(Thread.currentThread().getName()+"的i值:"+i);
            }
        }else {
            int middle =(start+end)/2;
            PrintTask left = new PrintTask(start, middle);
            PrintTask right = new PrintTask(middle, end);
            //并行执行两个“小任务”
            left.fork();
            right.fork();
        }
        
    }
    
}

package work1201.basic;

import java.util.Random;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

/**
 * Function: 对一个长度为100的元素值进行累加
 */
public class ForJoinPollTask {

    public static void main(String[] args) throws Exception {
        int[] arr = new int[100];
        Random random = new Random();
        int total =0;
        //初始化100个数组元素
        for(int i=0,len = arr.length;i<len;i++){
            int temp = random.nextInt(20);
            //对数组元素赋值,并将数组元素的值添加到sum总和中
            total += (arr[i]=temp);
        }
        System.out.println("初始化数组总和:"+total);
        SumTask task = new SumTask(arr, 0, arr.length);
        //创建一个通用池,这个是jdk1.8提供的功能
        ForkJoinPool pool = ForkJoinPool.commonPool();
        //提交分解的SumTask任务
        Future<Integer> future = pool.submit(task); 
        System.out.println("多线程执行结果:"+future.get());
        //关闭线程池
        pool.shutdown();         

    }

}

/**
 * 要完成的任务及分解方式
 * Function: 继承抽象类RecursiveTask,通过返回的结果,来实现数组的多线程分段累累加
 * RecursiveTask 具有返回值
 */
class SumTask extends RecursiveTask<Integer>{
    //每个小任务 最多只累加20个数
    private static final int THRESHOLD = 20; 
    private int arry[];
    private int start;
    private int end;
    
    

    /**
     * Creates a new instance of SumTask.
     * 累加从start到end的arry数组
     * @param arry
     * @param start
     * @param end
     */
    public SumTask(int[] arry, int start, int end) {
        super();
        this.arry = arry;
        this.start = start;
        this.end = end;
    }



    @Override
    protected Integer compute() {
        int sum =0;
        //当end与start之间的差小于threshold时,开始进行实际的累加
        if(end - start <THRESHOLD){
            for(int i= start;i<end;i++){
                sum += arry[i];
            }
            return sum;
        }else {//当end与start之间的差大于threshold,即要累加的数超过20个时候,将大任务分解成小任务
            int middle = (start+ end)/2;
            SumTask left = new SumTask(arry, start, middle);
            SumTask right = new SumTask(arry, middle, end);
            //并行执行两个 小任务
            left.fork();
            right.fork();
            //把两个小任务累加的结果合并起来
            return left.join()+right.join();
        }
        
    }
    
}

**注:**使用ThreadPoolExecutor和ForkJoinPool的性能差异:

(1)首先,使用ForkJoinPool能够使用数量有限的线程来完成非常多的具有父子关系的任务,比如使用4个线程来完成超过200万个任务。但是,使用ThreadPoolExecutor时是不可能完成的,因为ThreadPoolExecutor中的Thread无法选择优先执行子任务。

(2)ForkJoinPool能够实现工作窃取(Work Stealing),在该线程池的每个线程中会维护一个队列来存放需要被执行的任务。当线程自身队列中的任务都执行完毕后,它会从别的线程中拿到未被执行的任务并帮助它执行。因此,提高了线程的利用率,从而提高了整体性能。

(3)对于ForkJoinPool,还有一个因素会影响它的性能,就是停止进行任务分割的那个阈值。比如在快速排序中,当剩下的元素数量小于10的时候,就会停止子任务的创建。

结论:

  1. 当需要处理递归分治算法时,考虑使用ForkJoinPool;
  2. 仔细设置不再进行任务划分的阈值,这个阈值对性能有影响;
  3. Java 8中的一些特性会使用到ForkJoinPool中的通用线程池。在某些场合下,需要调整该线程池的默认的线程数量。
  4. ForkJoinPool在执行过程中,会创建大量的子任务,导致GC进行垃圾回收。

九、线程相关类

十、线程与单例模式

详见https://www.cnblogs.com/yoga21/p/9224557.html

最好的方法:双重检查(Double-Check idiom)的单例类

public class DubbleSingleton {
    //使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
    private static volatile  DubbleSingleton ds;
    public static DubbleSingleton getDs(){
        //a.第一轮判断,一旦对象创建成功,以后获取实例时就不需要同步获取锁了,提高效率
        if (ds==null){
            //b.第二轮判断,其他线程已经在第一轮判断时就进入了方法,所以其他线程会继续走synchronized代码块,这时进行第二轮判断,若其他线程会在第二轮判断时发现对象已经创建,则不会再继续创建对象。
            synchronized (DubbleSingleton.class){
                if(ds==null){
                    System.out.println("第二轮判断,我的ds==null,我来创建对象啦!");
                    //初始化对象
                    ds = new DubbleSingleton();
                }else {
                    System.out.println("有人已经把对象创建了!");
                }
            }
        }
        
        return ds;
    }

使用

    public static void main(String[] args){
          Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":"+DubbleSingleton.getDs().hashCode());
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":"+DubbleSingleton.getDs().hashCode());
            }
        },"t2");
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+":"+DubbleSingleton.getDs().hashCode());
            }
        },"t3");
        t1.start();
        t2.start();
        t3.start();
    }

}

十一、Timer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值