Java基础篇——线程、并发编程知识点全面介绍(面试、学习的必备索引)

原创不易,如需转载,请注明出处http://www.javashuo.com/article/p-xepdqbaa-hy.html,但愿你们多多支持!!! html

1、线程基础

一、线程与进程

  • 线程是指进程中的一个执行流程,一个进程中能够运行多个线程。
  • 进程是指一个内存中运行的应用程序,每一个进程都有本身独立的一块内存空间,即进程空间或(虚空间),好比一个qq.exe就是一个进程。

二、线程的特色

  • 线程共享分配给该进程的全部资源
  • 线程之间实际上轮换执行(也就是线程切换)
  • 一个程序至少有一个进程,一个进程至少有一个线程
  • 线程不可以独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制
  • 线程有本身的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含如下内容:
    • 一个指向当前被执行指令的指令指针
    • 一个栈
    • 一个寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值
    • 一个私有的数据区

三、线程的做用

  • 进程在执行过程当中拥有独立的内存单元,而多个线程共享内存,从而极大地提升了程序的运行效率(并发执行)

四、线程的建立

  • 继承Thread类
  • 实现Runnable接口
  • 经过ThreadPool获取

五、线程的状态(生命周期)

  • 建立:当用new操做符建立一个线程时。此时程序尚未开始运行线程中的代码
  • 就绪:当start()方法返回后,线程就处于就绪状态
  • 运行:当线程得到CPU时间后,它才进入运行状态,真正开始执行run()方法
  • 阻塞:所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其余处于就绪状态的线程就能够得到CPU时间,进入运行状态
  • 死亡:一、run方法正常退出而天然死亡;二、一个未捕获的异常终止了run方法而使线程猝死java

    线程生命周期.jpg

六、线程的优先级、线程让步yield、线程合并join、线程睡眠sleep

  • 优先级:线程老是存在优先级,优先级范围在1~10之间(数值越大优先级越高),线程默认优先级是5,优先级高的理论上先执行,但实际不必定。
  • 线程让步:yield方法调用后 ,是直接进入就绪状态,因此有可能刚进入就绪状态,又被调度到运行状态(使用yield()的目的是让相同优先级的线程之间能适当的轮转执行)。
  • 线程合并:保证当前线程中止执行,直到该线程所加入的线程完成为止。然而,若是它加入的线程没有存活,则当前线程不须要中止。
  • 线程睡眠:sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。

七、线程的分类(如下二者的惟一区别之处就在虚拟机的离开)

  • 守护线程: thread.setDaemon(true),必须在thread.start()以前设置,GC线程就是一个守护线程。
  • 普通线程

八、正确结束线程(给出一些方案)

  • 使用Thread.stop()方法,可是该方法已经被废弃了,使用它是极端不安全的,会形成数据不一致的问题。
  • 使用interrupt()方法中止一个线程,直接调用该方法不会终止一个正在运行的线程,须要加入一个判断语句才能够完成线程的中止。
  • 使用共享变量的方式,在这种方式中,之因此引入共享变量,是由于该变量能够被多个执行相同任务的线程用来做为是否中断的信号,通知中断线程的执行。

2、线程同步

一、线程同步的意义

  • 线程的同步是为了防止多个线程访问一个数据对象时,对数据形成的破坏

二、锁的原理

  • Java中每一个对象都有一个内置锁,内置锁是一个互斥锁,这就是意味着最多只有一个线程可以得到该锁。
  • 当程序运行到非静态的synchronized同步方法上时,自动得到与正在执行代码类的当前实例(this实例)有关的锁。得到一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
  • 当程序运行到synchronized同步方法或代码块时才该对象锁才起做用。
  • 一个对象只有一个锁。因此,若是一个线程得到该锁,就没有其余线程能够得到锁,直到第一个线程释放(或返回)锁。这也意味着任何其余线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
  • 释放锁是指持锁线程退出了synchronized同步方法或代码块。

三、锁和同步的理解

  • 只能同步方法,而不能同步变量和类。
  • 每一个对象只有一个锁,当提到同步时,应该清楚在什么上同步,也就是说,在哪一个对象上同步。
  • 没必要同步类中全部的方法,类能够同时拥有同步和非同步方法。
  • 若是两个线程要执行一个类中的synchronized方法,而且两个线程使用相同的实例来调用方法,那么一次只能有一个线程可以执行方法,另外一个须要等待,直到锁被释放。也就是说:若是一个线程在对象上得到一个锁,就没有任何其余线程能够进入(该对象的)类中的任何一个同步方法
  • 若是线程拥有同步和非同步方法,则非同步方法能够被多个线程自由访问而不受锁的限制。
  • 线程睡眠时,它所持的任何锁都不会释放。
  • 线程能够得到多个锁。好比,在一个对象的同步方法里面调用另一个对象的同步方法,则获取了两个对象的同步锁。
  • 同步损害并发性,应该尽量缩小同步范围。同步不但能够同步整个方法,还能够同步方法中一部分代码块。
  • 在使用同步代码块时候,应该指定在哪一个对象上同步,也就是说要获取哪一个对象的锁。

四、对象锁和类锁的区别

  • 对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。
  • 类的对象实例能够有不少个,可是每一个类只有一个class对象,因此不一样对象实例的对象锁是互不干扰的,可是每一个类只有一个类锁。

五、线程的死锁及规避

  • 死锁是线程间相互等待锁锁形成的,一旦程序发生死锁,程序将死掉。
  • 若是咱们可以避免在对象的同步方法中调用其它对象的同步方法,那么就能够避免死锁产生的可能性。

六、volatile关键字(推荐你们一片文章)

  • 正确使用 volatile变量
  • 与锁相比,Volatile 变量是一种很是简单但同时又很是脆弱的同步机制,它在某些状况下将提供优于锁的性能和伸缩性。
  • 若是严格遵循 volatile 的使用条件 —— 即变量真正独立于其余变量和本身之前的值 —— 在某些状况下可使用 volatile 代替 synchronized 来简化代码。

3、线程的交互

一、线程交互的基础知识

  • void notify()——唤醒在此对象监视器上等待的单个线程。
  • void notifyAll()——唤醒在此对象监视器上等待的全部线程。
  • void wait()——致使当前的线程等待,直到其余线程调用此对象的 notify()方法或 notifyAll()方法。
  • void wait(longtimeout)——致使当前的线程等待,直到其余线程调用此对象的 notify()方法或 notifyAll()方法,或者超过指定的时间量。
  • void wait(longtimeout, int nanos)——致使当前的线程等待,直到其余线程调用此对象的 notify()方法或 notifyAll()方法,或者其余某个线程中断当前线程,或者已超过某个实际时间量。

二、注意点

  • 必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
  • 当在对象上调用wait()方法时,执行该代码的线程当即放弃它在对象上的锁。然而调用notify()时,并不意味着这时线程会放弃其锁。若是线程仍然在完成同步代码,则线程在移出以前不会放弃锁。所以,调用notify()并不意味着这时该锁变得可用。

4、线程池

一、好处

  • 下降资源消耗。经过重复利用已建立的线程下降线程建立和销毁形成的消耗。
  • 提升线程的可管理性。线程是稀缺资源,若是无限制的建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一的分配,调优和监控。
  • 提升响应速度。当任务到达时,任务能够不须要等到线程建立就能当即执行。

二、线程池的建立使用

public class ThreadPoolExecutor extends AbstractExecutorService {
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
     
        public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    }
  • 参数介绍:
    • corePoolSize:核心池的大小
    • maximumPoolSize:线程池最大线程数
    • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止
    • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
      • TimeUnit.DAYS; //天
      • TimeUnit.HOURS; //小时
      • TimeUnit.MINUTES; //分钟
      • TimeUnit.SECONDS; //秒
      • TimeUnit.MILLISECONDS; //毫秒
      • TimeUnit.MICROSECONDS; //微妙
      • TimeUnit.NANOSECONDS; //纳秒
    • workQueue:一个阻塞队列,用来存储等待执行的任务
    • threadFactory:线程工厂,主要用来建立线程
    • handler:表示当拒绝处理任务时的策略,有如下四种取值:
      • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
      • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,可是不抛出异常。
      • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,而后从新尝试执行任务(重复此过程)
      • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

三、java默认实现的4中线程池(不建议使用)

  • newCachedThreadPool:建立一个可缓存线程池,若是线程池长度超过处理须要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
  • newFixedThreadPool:建立一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。
  • newScheduledThreadPool:建立一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor:建立一个单线程化的线程池,它只会用惟一的工做线程来执行任务,保证全部任务按照指定顺序(FIFO, LIFO, 优先级)执行。

5、并发编程相关内容

一、synchronized 的局限性 与 Lock 的优势

  • 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  • synchronized没法判断是否获取锁的状态,Lock能够判断是否获取到锁;
  • synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程当中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),不然容易形成线程死锁;
  • 用synchronized关键字的两个线程1和线程2,若是当前线程1得到锁,线程2线程等待。若是线程1阻塞,线程2则会一直等待下去,而Lock锁就不必定会等待下去,若是尝试获取不到锁,线程能够不用一直等待就结束了;
  • synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(二者皆可)
  • Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少许的同步问题。

二、Lock 和 ReadWriteLock

  • Lock接口,ReentrantLock(可重入锁)是惟一的实现类。git

    public interface Lock {
        void lock();
        //lockInterruptibly()方法比较特殊,当经过这个方法去获取锁时,若是线程正在等待获取锁,则这个线程可以响应中断,即中断线程的等待状态。
        //也就使说,当两个线程同时经过lock.lockInterruptibly()想获取某个锁时,倘若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法可以中断线程B的等待过程。
        void lockInterruptibly() throws InterruptedException;
        boolean tryLock();
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        void unlock();
        Condition newCondition();
    }
  • ReadWriteLock接口,ReentrantReadWriteLock实现了ReadWriteLock接口。github

    public interface ReadWriteLock {
        Lock readLock();
        Lock writeLock();
    }
    • 读读共享
    • 写写互斥
    • 读写互斥
    • 写读互斥

三、信号量(Semaphore)

  • 介绍:Java的信号量其实是一个功能完毕的计数器,对控制必定资源的消费与回收有着很重要的意义,信号量经常用于多线程的代码中,并能监控有多少数目的线程等待获取资源,而且经过信号量能够得知可用资源的数目等等。
  • 特色:
    • 以控制某个资源可被同时访问的个数,经过 acquire() 获取一个许可,若是没有就等待,而 release() 释放一个许可。
    • 单个信号量的Semaphore对象能够实现互斥锁的功能,而且能够是由一个线程得到了“锁”,再由另外一个线程释放“锁”,这可应用于死锁恢复的一些场合。
    • 信号量解决了锁一次只能让一个线程访问资源的问题,信号量能够指定多个线程,同时访问一个资源。
  • 分为公平模式和非公平模式(默认非公平)编程

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

    区别在于:公平模式会考虑是否已经有线程在等待,若是有则直接返回-1表示获取失败;而非公平模式不会关心有没有线程在等待,会去快速竞争资源的使用权。
    说到竞争就得提到AbstractQueuedSynchronizer同步框架,一个仅仅须要简单继承就能够实现复杂线程的同步方案,建议你们去研究一下。segmentfault

四、闭锁(CountDownLatch)

  • 介绍:闭锁是一种同步工具,能够延迟线程的进度直到终止状态。能够把它理解为一扇门,当闭锁到达结束状态以前,这扇门一直是关闭的,没有任何线程能够经过。当闭锁到达结束状态时,这扇门会打开并容许全部线程经过,而且闭锁打开后不可再改变状态。
    闭锁能够确保某些任务直到其余任务完成后才继续往下执行。缓存

  • 使用介绍
    • 构造器中的计数值(count)实际上就是闭锁须要等待的线程数量。这个值只能被设置一次,并且CountDownLatch没有提供任何机制去从新设置这个计数值。
    • 与CountDownLatch的第一次交互是主线程等待其余线程。主线程必须在启动其余线程后当即调用CountDownLatch.await()方法。这样主线程的操做就会在这个方法上阻塞,直到其余线程完成各自的任务。
    • 其余N 个线程必须引用闭锁对象,由于他们须要通知CountDownLatch对象,他们已经完成了各自的任务。这种通知机制是经过 CountDownLatch.countDown()方法来完成的;每调用一次这个方法,在构造函数中初始化的count值就减1。因此当N个线程都调 用了这个方法,count的值等于0,而后主线程就能经过await()方法,恢复执行本身的任务。

四、栅栏(CyclicBarrier)

  • 介绍:栅栏相似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,全部的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其余线程。CyclicBarrier可使必定数量的线程反复地在栅栏位置处聚集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到全部线程都到达栅栏位置。若是全部线程都到达栅栏位置,那么栅栏将打开,此时全部的线程都将被释放,而栅栏将被重置以便下次使用。安全

  • CyclicBarrier和CountDownLatch的区别:
    • CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可使用reset()方法重置,可使用屡次,因此CyclicBarrier可以处理更为复杂的场景;
    • CyclicBarrier还提供了一些其余有用的方法,好比getNumberWaiting()方法能够得到CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;
    • CountDownLatch容许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其余线程运行到栅栏位置。

五、原子量 (Atomic)

  • 介绍:Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位。计算机中的Atomic是指不能分割成若干部分的意思。若是一段代码被认为是Atomic,则表示这段代码在执行过程当中,是不能被中断的。一般来讲,原子指令由硬件提供,供软件来实现原子方法(某个线程进入该方法后,就不会被中断,直到其执行完成)多线程

  • 特性:在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具备排他性,即当某个线程进入方法,执行其中的指令时,不会被其余线程打断,而别的线程就像自旋锁同样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另外一个线程进入,这只是一种逻辑上的理解。其实是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。并发

  • 注意:原子量虽然能够保证单个变量在某一个操做过程的安全,但没法保证你整个代码块,或者整个程序的安全性。所以,一般还应该使用锁等同步机制来控制整个程序的安全性。

六、Condition

  • 介绍:在Java程序中,任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object类上),主要包括wait()、wait(long)、notify()、notifyAll()方法,这些方法与synchronized关键字配合,能够实现等待/通知模式。Condition接口也提供了相似Object的监视器方法,与Lock配合能够实现等待/通知模式,可是这二者在使用方式以及功能特性上仍是有区别的。
  • Condition与Object中的wati,notify,notifyAll区别:
    • Condition中的await()方法至关于Object的wait()方法,Condition中的signal()方法至关于Object的notify()方法,Condition中的signalAll()至关于Object的notifyAll()方法。
      不一样的是,Object中的这些方法是和同步锁捆绑使用的;而Condition是须要与互斥锁/共享锁捆绑使用的。
    • Condition它更强大的地方在于:可以更加精细的控制多线程的休眠与唤醒。对于同一个锁,咱们能够建立多个Condition,在不一样的状况下使用不一样的Condition。若是采用Object类中的wait(),notify(),notifyAll()实现该缓冲区,当向缓冲区写入数据以后须要唤醒"读线程"时,不可能经过notify()或notifyAll()明确的指定唤醒"读线程",而只能经过notifyAll唤醒全部线程(可是notifyAll没法区分唤醒的线程是读线程,仍是写线程)。 可是,经过Condition,就能明确的指定唤醒读线程。

七、并发编程概览

concurrent.png

6、总结

  • 到此线程的基本内容介绍就差很少了,这篇文章偏理论一些,每一个知识点的介绍并不全面。
  • 你们能够以此篇文章为索引,来展开对并发编程的深刻学习,细细咀嚼每一个知识点,相信你会有巨大的收获!!!

我的博客地址:

cnblogs:https://www.cnblogs.com/baixianlong
csdn:https://blog.csdn.net/tiantuo6513
segmentfault:https://segmentfault.com/u/baixianlong
github:https://github.com/xianlongbai

相关文章
相关标签/搜索