• java多线程面试总结


    一:基本知识点

    1.1线程与进程区别:

    1.进程是资源分配的最小单位,线程是CPU调度的最小单位

    2.一个进程由一个或多个线程组成

    3.进程之间相互独立,每个进程都有独立的代码和数据空间,但同一进程下的各个线程之间共享进程的代码和内存空间,每个线程有独立的运行栈和程序计数器

    4.线程上下文切换比进程上下文切换要快得多

    1.2线程实现

    java中要想实现多线程,有两种手段,一种是继续Thread类(extends )

    另外一种是实现Runable接口(implements ,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象)。

    实现runnable接口的优势:

    适合于资源的共享

    可以避免java中的单继承的限制

    增加程序的健壮性,代码可以被多个线程共享

    1.3线程状态转换

    新建状态(New):新创建了一个线程对象。

    就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。变得可运行,等待获取CPU的使用权。

    运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

    阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。

    死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

    1.4多线程应用场景(是为了充分利用cpu)

    1 线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时); 
    2 提供非均质的服务(有优先级任务处理)事件响应有优先级; 
    3 单任务并行计算,提高响应速度,降低时延; 
    4 与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)

    1. WEB,主线程专门监听用户的HTTP请求,然后启动子线程去处理用户的HTTP请求。提高吞吐量

    2. 某种任务,虽然耗时,但是不耗CPU的操作时,开启多个线程,效率会有显著提高。
    比如读取文件,然后处理。 磁盘IO是个很耗费时间,但是不耗CPU计算的工作。 所以可以一个线程读取数据,一个线程处理数据。肯定比

    3. 数据库操作

    1.5死锁

    产生原因:

    互斥条件:一个资源每次只能被一个进程使用。

    不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

    请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

    循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

    如何避免死锁:

    加锁顺序(线程按照一定的顺序加锁只有获得了从顺序上排在前面的锁之后,才能获取后面的锁。与解锁顺序无关

    加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁然后等待一段随机的时间再重试)

    死锁检测

    原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。

    1.6常用函数

    1. sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠,进入阻塞状态,不会释放锁

    2. join():当前线程进入阻塞状态,等待加入线程终止后才能执行

    3. setPriority(): 更改线程的优先级。

    4. setName(): 为线程设置一个名称。  

    5. interrupt():中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭

    6. wait()Obj.wait()Obj.notify()

    必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

    waitsleep区别 

    sleep()睡眠时,保持对象锁,仍然占有该锁;thread的方法
      而wait()睡眠时,释放对象锁。object的方法

     

    二、线程同步五种方式

    线程安全:就是说多线程访问同一代码,不会产生不确定的结果。

    1.synchronized同步方法

    即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。 

    也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类 

    2.synchronized同步代码块 

    即有synchronized关键字修饰的语句块。 

    当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞,但仍然可以访问该object中的非synchronized(this)同步代码块。

    3.volatile实现线程同步

    volatile修饰的变量,线程在每次使用变量的时候,都会从主存中读取变量最新值。变量修改后会直接改变主存内容。保证可见性,不能保证原子性

    4.使用重入锁实现线程同步ReentrantLock

    ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候,比如可以放弃锁等待先做别的事情(trylock),而Synchronized不能

    synchronized是在JVM层面上实现的,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

    在资源竞争很激烈的情况下,ReetrantLock的性能要优于Synchronized

    5.使用ThreadLocal管理变量

    使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

     

    吞吐量:单位时间内成功地传送数据的数量

    三、线程间通信

    1.synchronied关键字wait()/notify()、notifyAll()机制:

    2.条件对象的等待/通知机制(await()、signal()、signalAll()):所谓的条件对象也就是配合前面我们分析的Lock锁对象,通过锁对象的条件对象来实现等待/通知机制Condition conditionObj=ticketLock.newCondition()

    3.管道通信

    通过管道,将一个线程中的二进制数据消息发送给另一个。

     

    进程间通信:

     

    1.管道

     

      匿名管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系(父子)的进程间使用。

     

      有名管道: 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

     

    2.信号量: 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。

     

    3.消息队列: 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流

     

    4.信号:用于通知接收进程某个事件已经发生。

     

    5 .共享内存:共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。共享内存就是一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问信号量+共享内存通常结合在一起使用,来达到进程间的同步与互斥。

     

    四、线程池

    4.1 什么是线程池?

          线程池是一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。

    4.2 好处

    第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

    第三:提高线程的可管理性。

    4.3 适用场合?

    当一个Web服务器接受到大量短小线程的请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。但如果线程要求的运行时间比较长,此时线程的运行时间比创建时间要长得多,单靠减少创建时间对系统效率的提高不明显,此时就不适合应用线程池技术,需要借助其它的技术来提高服务器的服务效率。 

    4.4 创建线程池四种方式

    我们可以通过Executors工具的静态方法来创建线程池。

    1.newFixedThreadPool(int nThreads)

    创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化

    2.newCachedThreadPool()

    创建一个可缓存的线程池适当情况下可回收添加线程

    3.newSingleThreadExecutor()

    这是一个单线程的Executor

    4.newScheduledThreadPool(int corePoolSize)

    创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

    Executors 类使用 ExecutorService  提供了一个 ThreadPoolExecutor 的简单实现,但 ThreadPoolExecutor 提供的功能远不止这些。 

    ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(

    corePoolSize,// 核心线程数,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。

    maximumPoolSize, // 最大线程数 线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。

    keepAliveTime,  // 线程活动保持时间闲置线程存活时间  当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize此时不会退出线程

    TimeUnit.MILLISECONDS,// 时间单位,此处为毫秒

    runnableTaskQueuenew ,// 任务队列线程队列用于保存执行任务的阻塞队列 

    Executors.defaultThreadFactory(),// 线程工厂  

    RejectedExecutionHandler// 饱和策略队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

    );

    4.5 线程池的处理流程

    1.首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。

    2.其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。

    3.最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

    4.6 线程池组成

    线程池管理器ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

    工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

    任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

    任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

    4.7 合理的配置线程池

    可以从以下几个角度来进行分析:

    1. 任务的性质:CPU密集型任务IO密集型任务 

    任务性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能少的线程数量IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程。

    2. 任务的优先级:高,中和低。

    优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。(任务队列里的一种)

    3. 任务的执行时间:长,中和短。

    可以使用优先级队列,让执行时间短的任务先执行。

    4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。

    依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU

    五、同步案例

    5.1 顺序打印ABCwait()notify()

    public class MyThreadPrinter2 implements Runnable {   

        private String name;   

        private Object prev;   

        private Object self;   

        private MyThreadPrinter2(String name, Object prev, Object self) {   

            this.name = name;   

            this.prev = prev;   

            this.self = self;   

        }   

        @Override  

        public void run() {   

            int count = 10;   

            while (count > 0) {   

                synchronized (prev) {   

                    synchronized (self) {   

                        System.out.print(name);   

                        count--;  

                        self.notify();   

                    }   

                    try {   

                        prev.wait();   

                    } catch (InterruptedException e) {   

                        e.printStackTrace();   

                    }   

                }   

            }   

        }   

        public static void main(String[] args) throws Exception {   

            Object a = new Object();   

            Object b = new Object();   

            Object c = new Object();   

            MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);   

            MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);   

            MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);

     

            new Thread(pa).start();

            Thread.sleep(100);  //确保按顺序ABC执行

            new Thread(pb).start();

            Thread.sleep(100);  

            new Thread(pc).start();   

            Thread.sleep(100);  

            }   

    }  

    主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,等待下次获取prev锁后运行,终止当前线程,等待循环结束后再次被唤醒。

     

    5.2 单例模式

    特征:

    单例类只能有一个实例。

    单例类必须自己创建自己的唯一实例。

    单例类必须给所有其他对象提供这一实例。

    懒汉式单例

    Public class Singleton {

    Private Singleton (){};

    Private static Singleton single = null;

    Public static Singleton getInstance(){

    If(Singleton == null)

    Single = new Singleton ();

    return single;

    }

    }

    但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式

    方法一:getInstance方法上加同步

    public static synchronized Singleton getInstance(){

    If(Single == null)

    Single = new Singleton ();

    return single;

    }

    方法二 : 双检索

    public static Singleton getInstance(){

    If(single == null){

    Synchronized(Singleton.class){

    if(single == null)

    Single = new Singleton ();

    }

    }

    return single;

    }

    方法三:静态内部类

    饿汉式单例

    Public class singleton{

    Private singleton(){}

    Private static final singleton single = new singleton();

    Public static singleton getInstance(){

    return single;

    }

    }

    5.3 生产者消费者模式

    单生产者单消费者模式:

    1. public class KaoYaResource {  

    2.       

    3.     private String name;  

    4.     private int count = 1;//烤鸭的初始数量  

    5.     private boolean flag = false;//判断是否有需要线程等待的标志  

    6.     public synchronized void product(String name){  

    7.         if(flag){  

    8.             //此时有烤鸭,等待  

    9.             try {  

    10.                 this.wait();  

    11.             } catch (InterruptedException e) {  

    12.                 e.printStackTrace();  

    13.             }  

    14.         }  

    15.         this.name=name+count;//设置烤鸭的名称  

    16.         count++;  

    17.         System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);  

    18.         flag=true;//有烤鸭后改变标志  

    19.         notifyAll();//通知消费线程可以消费了  

    20.     }  

    21.     public synchronized void consume(){  

    22.         if(!flag){//如果没有烤鸭就等待  

    23.             try{this.wait();}catch(InterruptedException e){}  

    24.         }  

    25.         System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1  

    26.         flag = false;  

    27.         notifyAll();//通知生产者生产烤鸭  

    28.     }  

    29. }  

    30. 

    1. public class Single_Producer_Consumer {  

    2.     public static void main(String[] args)   

    3.     {  

    4.         KaoYaResource r = new KaoYaResource();  

    5.         Producer pro = new Producer(r);  

    6.         Consumer con = new Consumer(r);  

    7.         //生产者线程  

    8.         Thread t0 = new Thread(pro);  

    9.         //消费者线程  

    10.         Thread t2 = new Thread(con);  

    11.         //启动线程  

    12.         t0.start();  

    13.         t2.start();  

    14.     }  

    15. }  

    16. class Producer implements Runnable  

    17. {  

    18.     private KaoYaResource r;  

    19.     Producer(KaoYaResource r)  

    20.     {  

    21.         this.r = r;  

    22.     }  

    23.     public void run()  

    24.     {  

    25.         while(true)  

    26.         {  

    27.             r.product("北京烤鸭");  

    28.         }  

    29.     }  

    30. }  

    31. class Consumer implements Runnable  

    32. {  

    33.     private KaoYaResource r;  

    34.     Consumer(KaoYaResource r)  

    35.     {  

    36.         this.r = r;  

    37.     }  

    38.     public void run()  

    39.     {  

    40.         while(true)  

    41.         {  

    42.             r.consume();  

    43.         }  

    44.     }  

    }  

     

     

    一:基本知识点

    1.1线程与进程区别:

    1.进程是资源分配的最小单位,线程是CPU调度的最小单位

    2.一个进程由一个或多个线程组成

    3.进程之间相互独立,每个进程都有独立的代码和数据空间,但同一进程下的各个线程之间共享进程的代码和内存空间,每个线程有独立的运行栈和程序计数器

    4.线程上下文切换比进程上下文切换要快得多

    1.2线程实现

    java中要想实现多线程,有两种手段,一种是继续Thread类(extends )

    另外一种是实现Runable接口(implements ,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象)。

    实现runnable接口的优势:

    适合于资源的共享

    可以避免java中的单继承的限制

    增加程序的健壮性,代码可以被多个线程共享

    1.3线程状态转换

    新建状态(New):新创建了一个线程对象。

    就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。变得可运行,等待获取CPU的使用权。

    运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

    阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。

    死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

    1.4多线程应用场景(是为了充分利用cpu)

    1 线程间有数据共享,并且数据是需要修改的(不同任务间需要大量共享数据或频繁通信时); 
    2 提供非均质的服务(有优先级任务处理)事件响应有优先级; 
    3 单任务并行计算,提高响应速度,降低时延; 
    4 与人有IO交互的应用,良好的用户体验(键盘鼠标的输入,立刻响应)

    1. WEB,主线程专门监听用户的HTTP请求,然后启动子线程去处理用户的HTTP请求。提高吞吐量

    2. 某种任务,虽然耗时,但是不耗CPU的操作时,开启多个线程,效率会有显著提高。
    比如读取文件,然后处理。 磁盘IO是个很耗费时间,但是不耗CPU计算的工作。 所以可以一个线程读取数据,一个线程处理数据。肯定比

    3. 数据库操作

    1.5死锁

    产生原因:

    互斥条件:一个资源每次只能被一个进程使用。

    不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

    请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

    循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

    如何避免死锁:

    加锁顺序(线程按照一定的顺序加锁只有获得了从顺序上排在前面的锁之后,才能获取后面的锁。与解锁顺序无关

    加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁然后等待一段随机的时间再重试)

    死锁检测

    原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。

    1.6常用函数

    1. sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠,进入阻塞状态,不会释放锁

    2. join():当前线程进入阻塞状态,等待加入线程终止后才能执行

    3. setPriority(): 更改线程的优先级。

    4. setName(): 为线程设置一个名称。  

    5. interrupt():中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭

    6. wait()Obj.wait()Obj.notify()

    必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

    waitsleep区别 

    sleep()睡眠时,保持对象锁,仍然占有该锁;thread的方法
      而wait()睡眠时,释放对象锁。object的方法

     

    二、线程同步五种方式

    线程安全:就是说多线程访问同一代码,不会产生不确定的结果。

    1.synchronized同步方法

    即有synchronized关键字修饰的方法。由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。 

    也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类 

    2.synchronized同步代码块 

    即有synchronized关键字修饰的语句块。 

    当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞,但仍然可以访问该object中的非synchronized(this)同步代码块。

    3.volatile实现线程同步

    volatile修饰的变量,线程在每次使用变量的时候,都会从主存中读取变量最新值。变量修改后会直接改变主存内容。保证可见性,不能保证原子性

    4.使用重入锁实现线程同步ReentrantLock

    ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候,比如可以放弃锁等待先做别的事情(trylock),而Synchronized不能

    synchronized是在JVM层面上实现的,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

    在资源竞争很激烈的情况下,ReetrantLock的性能要优于Synchronized

    5.使用ThreadLocal管理变量

    使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。

     

    吞吐量:单位时间内成功地传送数据的数量

    三、线程间通信

    1.synchronied关键字wait()/notify()、notifyAll()机制:

    2.条件对象的等待/通知机制:所谓的条件对象也就是配合前面我们分析的Lock锁对象,通过锁对象的条件对象来实现等待/通知机制Condition conditionObj=ticketLock.newCondition()

     

    3.管道通信

    通过管道,将一个线程中的二进制数据消息发送给另一个。

    四、线程池

    4.1 什么是线程池?

          线程池是一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。

    4.2 好处

    第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

    第三:提高线程的可管理性。

    4.3 适用场合?

    当一个Web服务器接受到大量短小线程的请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。但如果线程要求的运行时间比较长,此时线程的运行时间比创建时间要长得多,单靠减少创建时间对系统效率的提高不明显,此时就不适合应用线程池技术,需要借助其它的技术来提高服务器的服务效率。 

    4.4 创建线程池四种方式

    我们可以通过Executors工具的静态方法来创建线程池。

    1.newFixedThreadPool(int nThreads)

    创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化

    2.newCachedThreadPool()

    创建一个可缓存的线程池适当情况下可回收添加线程

    3.newSingleThreadExecutor()

    这是一个单线程的Executor

    4.newScheduledThreadPool(int corePoolSize)

    创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

    Executors 类使用 ExecutorService  提供了一个 ThreadPoolExecutor 的简单实现,但 ThreadPoolExecutor 提供的功能远不止这些。 

    ThreadPoolExecutor mExecutor = new ThreadPoolExecutor(

    corePoolSize,// 核心线程数,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。

    maximumPoolSize, // 最大线程数 线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。

    keepAliveTime,  // 线程活动保持时间闲置线程存活时间  当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize此时不会退出线程

    TimeUnit.MILLISECONDS,// 时间单位,此处为毫秒

    runnableTaskQueuenew ,// 任务队列线程队列用于保存执行任务的阻塞队列 

    Executors.defaultThreadFactory(),// 线程工厂  

    RejectedExecutionHandler// 饱和策略队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。

    );

    4.5 线程池的处理流程

    1.首先线程池判断基本线程池是否已满?没满,创建一个工作线程来执行任务。满了,则进入下个流程。

    2.其次线程池判断工作队列是否已满?没满,则将新提交的任务存储在工作队列里。满了,则进入下个流程。

    3.最后线程池判断整个线程池是否已满?没满,则创建一个新的工作线程来执行任务,满了,则交给饱和策略来处理这个任务。

    4.6 线程池组成

    线程池管理器ThreadPool):用于创建并管理线程池,包括 创建线程池,销毁线程池,添加新任务;

    工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;

    任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

    任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;

    4.7 合理的配置线程池

    可以从以下几个角度来进行分析:

    1. 任务的性质:CPU密集型任务IO密集型任务 

    任务性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能少的线程数量IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程。

    2. 任务的优先级:高,中和低。

    优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。(任务队列里的一种)

    3. 任务的执行时间:长,中和短。

    可以使用优先级队列,让执行时间短的任务先执行。

    4. 任务的依赖性:是否依赖其他系统资源,如数据库连接。

    依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU

    五、同步案例

    5.1 顺序打印ABCwait()notify()

    public class MyThreadPrinter2 implements Runnable {   

        private String name;   

        private Object prev;   

        private Object self;   

        private MyThreadPrinter2(String name, Object prev, Object self) {   

            this.name = name;   

            this.prev = prev;   

            this.self = self;   

        }   

        @Override  

        public void run() {   

            int count = 10;   

            while (count > 0) {   

                synchronized (prev) {   

                    synchronized (self) {   

                        System.out.print(name);   

                        count--;  

                        self.notify();   

                    }   

                    try {   

                        prev.wait();   

                    } catch (InterruptedException e) {   

                        e.printStackTrace();   

                    }   

                }   

            }   

        }   

        public static void main(String[] args) throws Exception {   

            Object a = new Object();   

            Object b = new Object();   

            Object c = new Object();   

            MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);   

            MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);   

            MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);

     

            new Thread(pa).start();

            Thread.sleep(100);  //确保按顺序ABC执行

            new Thread(pb).start();

            Thread.sleep(100);  

            new Thread(pc).start();   

            Thread.sleep(100);  

            }   

    }  

    主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,等待下次获取prev锁后运行,终止当前线程,等待循环结束后再次被唤醒。

     

    5.2 单例模式

    特征:

    单例类只能有一个实例。

    单例类必须自己创建自己的唯一实例。

    单例类必须给所有其他对象提供这一实例。

    懒汉式单例

    Public class Singleton {

    Private Singleton (){};

    Private static Singleton single = null;

    Public static Singleton getInstance(){

    If(Singleton == null)

    Single = new Singleton ();

    return single;

    }

    }

    但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式

    方法一:getInstance方法上加同步

    public static synchronized Singleton getInstance(){

    If(Single == null)

    Single = new Singleton ();

    return single;

    }

    方法二 : 双检索

    public static Singleton getInstance(){

    If(single == null){

    Synchronized(Singleton.class){

    if(single == null)

    Single = new Singleton ();

    }

    }

    return single;

    }

    方法三:静态内部类

    饿汉式单例

    Public class singleton{

    Private singleton(){}

    Private static final singleton single = new singleton();

    Public static singleton getInstance(){

    return single;

    }

    }

    5.3 生产者消费者模式

    单生产者单消费者模式:

    1. public class KaoYaResource {  

    2.       

    3.     private String name;  

    4.     private int count = 1;//烤鸭的初始数量  

    5.     private boolean flag = false;//判断是否有需要线程等待的标志  

    6.     public synchronized void product(String name){  

    7.         if(flag){  

    8.             //此时有烤鸭,等待  

    9.             try {  

    10.                 this.wait();  

    11.             } catch (InterruptedException e) {  

    12.                 e.printStackTrace();  

    13.             }  

    14.         }  

    15.         this.name=name+count;//设置烤鸭的名称  

    16.         count++;  

    17.         System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);  

    18.         flag=true;//有烤鸭后改变标志  

    19.         notifyAll();//通知消费线程可以消费了  

    20.     }  

    21.     public synchronized void consume(){  

    22.         if(!flag){//如果没有烤鸭就等待  

    23.             try{this.wait();}catch(InterruptedException e){}  

    24.         }  

    25.         System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1  

    26.         flag = false;  

    27.         notifyAll();//通知生产者生产烤鸭  

    28.     }  

    29. }  

    30. 

    1. public class Single_Producer_Consumer {  

    2.     public static void main(String[] args)   

    3.     {  

    4.         KaoYaResource r = new KaoYaResource();  

    5.         Producer pro = new Producer(r);  

    6.         Consumer con = new Consumer(r);  

    7.         //生产者线程  

    8.         Thread t0 = new Thread(pro);  

    9.         //消费者线程  

    10.         Thread t2 = new Thread(con);  

    11.         //启动线程  

    12.         t0.start();  

    13.         t2.start();  

    14.     }  

    15. }  

    16. class Producer implements Runnable  

    17. {  

    18.     private KaoYaResource r;  

    19.     Producer(KaoYaResource r)  

    20.     {  

    21.         this.r = r;  

    22.     }  

    23.     public void run()  

    24.     {  

    25.         while(true)  

    26.         {  

    27.             r.product("北京烤鸭");  

    28.         }  

    29.     }  

    30. }  

    31. class Consumer implements Runnable  

    32. {  

    33.     private KaoYaResource r;  

    34.     Consumer(KaoYaResource r)  

    35.     {  

    36.         this.r = r;  

    37.     }  

    38.     public void run()  

    39.     {  

    40.         while(true)  

    41.         {  

    42.             r.consume();  

    43.         }  

    44.     }  

    }  

     

     

  • 相关阅读:
    LaTeX 超链接
    剑指offer2 数组
    LaTeX 插入源代码
    RGB
    linux 程序在后台运行
    Linux Vim编辑与退出
    复杂度估计
    剑指offer 2 loading...
    剑指offer2 整数
    剑指offer2 字符串
  • 原文地址:https://www.cnblogs.com/wmbg/p/6872112.html
Copyright © 2020-2023  润新知