Java线程相关知识,线程池底层源码分享和相关面试题(持续更新)

线程

 

线程有哪些状态

         */
        NEW, //当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread()

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE, //当调用线程对象的start()方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了start()此线程立即就会执行

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED, //处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态,
            
/** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called <tt>Object.wait()</tt> * on an object is waiting for another thread to call * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on * that object. A thread that has called <tt>Thread.join()</tt> * is waiting for a specified thread to terminate. */ WAITING, //这个状态下是不能分配CPU执行的,可使用Object.wait(),Thread,join(),LockSupport.park进入这个状态 /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ TIMED_WAITING,//其实这个状态和Waiting就是有没有超时时间的差别,这个状态下也是不能分配CPU执行的 /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED; //在我们的线程正常run结束之后或者run一半异常了就是终止状态

  

终止线程的方式

1.Tread.stop(). 2.Tread.interrupt()

推荐使用interrupt,因为使用stop之后,这个线程带有的锁也会消失,因此不推荐使用这个方法,interrupt()会使得线程Waiting和Timed_Waiting状态的线程抛出 interruptedException异常,调用interrupt()方法后,使得Running状态的线程再调用wait()、sleep()、jion()方法时抛出interruptedException异常,需要在catch中处理线程异常后的问题。

 

进程和线程的区别

来源于一个知乎老哥 //www.zhihu.com/question/25532384 比较容易理解

 

做个简单的比喻:进程=火车,线程=车厢

线程在进程下行进(单纯的车厢无法运行)

一个进程可以包含多个线程(一辆火车可以有多个车厢)

不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)

同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)

进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)

进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)

进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-”互斥锁”

进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

 

sleep方法和wait方法区别

都可以使当前线程放弃CPU一定的时间从而暂停,但是如果有锁的话,sleep方法不会释放这个锁,wait方法会, sleep方法必须要设定时间,wait方法可设定可不设定。 sleep属于Thread类,wait方法属于Object类

 

wait,notify方法存在的问题(Condition 下的await和signal也是一样)

notify和wait必须在synchrnizied同步块中执行,并且成对出现,只有先wait再notify才能被唤醒,否则无法唤醒。

比如notify放在wait前执行,t1先notify,3秒后t2在执行wait方法,程序就一直无法结束。

升级版本:

使用LockSupport的park和unpark

 

实现多线程有几种方式

1.继承Thread类,重写run方法

2.实现Runnable接口,重写run方法

3.实现Callable接口,重写call方法

4.使用线程池

 

ThreadLocal原理

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

  

 

 

进程之间的通信方式有哪些

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

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

消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

共享存储SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。

信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

套接字Socket:套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

 

 

 

 

 

 

线程池

线程池各个参数讲解

public ThreadPoolExecutor(int corePoolSize, //线程池核心工作线程数量,比如newFixedThreadPool中可以自定义的线程数量就是这个参数
                              int maximumPoolSize, //线程池所有工作线程的数量,比如newFixedThreadPool中的最大工作线程就是核心线程数,newCachedThreadPool中的最大工作线程数是Integer.MAX_VALUE
                              long keepAliveTime, //非核心线程存活的时间,当核心线程不够用的时候,创建出来的辅助工作线程在完成任务空闲多久后会被回收
                              TimeUnit unit, //上面一个参数的单位,分。秒等
                              BlockingQueue<Runnable> workQueue,//底层使用的阻塞队列数据结构,比如newFixedThreadPool底层使用LinkedBlockingQueue。工作队列,保存已提交但是未执行的任务
                              ThreadFactory threadFactory, //创建工作线程的工厂,保持默认即可
                              RejectedExecutionHandler handler) { //拒绝策略,即在所有工作线程数达到上限,底层阻塞队列也满了的时候采取的策略
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

常用线程池

1.newSingleThreadExecutor 单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务

2.newFixedThreadExecutor(n) 固定数量的线程池,没提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行

3.newCacheThreadExecutor(推荐使用) 可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。

4.newScheduleThreadExecutor 大小无限制的线程池,支持定时和周期性的执行线程

线程池底层执行原理

 

日常使用中使用哪种线程池

上面四种都不使用,我们需要通过ThreadPoolExcutor自定义线程池,阿里巴巴java开发手册明确指出,不允许使用Excutors工具类进行线程池的创建,因为某些开发人员不了解底层运行规则,避免资源耗尽的危险

1:FixedThreadPool 和 SingleThreadPool: 允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

2:CachedThreadPool 和 ScheduledThreadPool 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM

线程池4种拒绝策略

new ThreadPoolExecutor.AbortPolicy(); //遇到线程池达到最大线程数,且队列已经满了,直接抛异常 —-默认

new ThreadPoolExecutor.DiscardOldestPolicy() //遇到线程池达到最大线程数且队列已经满了,则丢弃队列中最前面的任务,

new ThreadPoolExecutor.DiscardPolicy() //遇到线程池达到最大线程数且队列已经满了,丢弃当前任务,不给它加入队列中

new ThreadPoolExecutor.CallerRunsPolicy();//会将任务返回给提交者进行执行,比如让main线程直接执行run方法

线程池优点

1.避免大量线程的创建销毁带来的性能开销

2.避免大量线程之间因为相互抢占系统资源导致的阻塞状态

3.能够对线程进行简单的管理并提供定时执行,间隔执行等功能

在IO密集型和CPU密集型下线程池最大线程数选择

在CPU密集型下,选用cpu核心数+1作为最大线程池工作线程数量,减少cpu切换任务带来的开销

在IO密集型下,尽可能多的加大工作线程数量,一般为cpu核心数*2

线程池提交任务时excute和submit有什么区别

1.两者接收参数不一致,excute接收Runnable接口,submit接收Callable接口

2.submit有返回值,execute没有返回值

3.submit支持返回Future

线程池状态

private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

RUNNING:线程池的初始状态,可以添加待执行的任务

SHUTDOWN:线程池处于待关闭状态,不接受新任务,但会处理完已接收的任务

STOP:线程池立即关闭,不接收新的任务,放弃缓存队列中的任务并且中断正在处理的任务

TIDYING:线程池自主整理状态,调用 terminated() 方法进行线程池整理

TERMINATED:线程池终止状态

 

Tags: