根底知识

为什么要运用并发编程(并发编程的长处)

  • 充分运用多核CPU的核算才干:经过并发编程的办法能够将多核CPU 的核算才干发挥到极致,功用得到进步
  • 便利进行事务拆分,进步体系并发才干和功用:在特别的事务场景下,先天的就适合于并发编程。现在的体系动不动就要求百万级乃至千万级的并发量,而多线程并发编程正是开发高并发体系的根底,运用好多线程机制能够大大进步体系整体的并发才干以及功用。面临杂乱事务模型,并行程序会比串行程序更习惯事务需求,而并发编程更能吻合这种事务拆分 。

并发编程有什么缺陷

并发编程的意图便是为了能进步程序的履行功率,进步程序运转速度,可是并发编程并不总是能进步程序运转速度的,而且并发编程或许会遇到许多问题,比方:内存走漏、上下文切换、线程安全、死锁等问题。

并发编程三要素是什么?在 Java 程序中怎样确保多线程的运转安全?

并发编程三要素(线程的安全性问题体现在):

原子性:原子,即一个不行再被分割的颗粒。原子性指的是一个或多个操作要么 悉数履行成功要么悉数履行失利。

可见性:一个线程对同享变量的修正,另一个线程能够马上看到。 (synchronized,volatile)

有序性:程序履行的次第依照代码的先后次第履行。(处理器或许会对指令进行 重排序)

呈现线程安全问题的原因:

  • 线程切换带来的原子性问题
  • 缓存导致的可见性问题
  • 编译优化带来的有序性问题

处理办法:

  • JDK Atomic最初的原子类、synchronized、LOCK,能够处理原子性问题
  • synchronized、volatile、LOCK,能够处理可见性问题
  • Happens-Before 规矩能够处理有序性问题

并行和并发有什么差异?

  • 并发:多个使命在同一个 CPU 核上,按细分的时刻片轮番(交替)履行,从逻辑 上来看那些使命是一同履行。
  • 并行:单位时刻内,多个处理器或多核处理器一同处理多个使命,是真实意义上 的“一同进行”。
  • 串行:有n个使命,由一个线程按次第履行。由于使命、办法都在一个线程履行 所以不存在线程不安全状况,也就不存在临界区的问题。

做一个形象的比方:

并发 = 两个行列和一台咖啡机

并行 = 两个行列和两台咖啡机

串行 = 一个行列和一台咖啡机

什么是多线程,多线程的好坏?

多线程:多线程是指程序中包括多个履行流,即在一个程序中能够一同运转多个 不同的线程来履行不同的使命。

多线程的长处: 能够进步 CPU 的运用率。在多线程程序中,一个线程有必要等候的时分,CPU能够运转其它的线程而不是等候,这样就大大进步了程序的功率。也便是说答应单个程序创立多个并行履行的线程来完结各自的使命。

多线程的劣势:

  • 线程也是程序,所以线程需求占用内存,线程越多占用内存也越多;
  • 多线程需求协调和办理,所以需求 CPU 时刻盯梢线程;
  • 线程之间对同享资源的拜访会相互影响,有必要处理竞用同享资源的问 题。

线程和进程差异 什么是线程和进程?

进程

一个在内存中运转的运用程序。每个进程都有自己独立的一块内存空间,一个进程能够有多个线程,比方在Windows体系中,一个运转的xx.exe便是一个进 程。

线程

进程中的一个履行使命(操控单元),担任当时进程中程序的履行。一个进程至 少有一个线程,一个进程能够运转多个线程,多个线程可同享数据。

进程与线程的差异

线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process), 它适当于只需一个线程的使命。在引进了线程的操作体系中,一般一个进程都有 若干个线程,至少包括一个线程。

根本差异: 进程是操作体系资源分配的根本单位,而线程是处理器使命调度和履行的根本单位

资源开支: 每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开支;线程能够看做轻量级的进程,同一类线程同享代码和数据空间,每个线程都有自己独立的运转栈和程序计数器(PC),线程之间切换的开支小。

包括联系: 假设一个进程内有多个线程,则履行进程不是一条线的,而是多条线 (线程)共同完结的;线程是进程的一部分,所以线程也被称为轻权进程或许轻 量级进程。

内存分配: 同一进程的线程同享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响联系: 一个进程溃散后,在保护形式下不会对其他进程发生影响,可是一个线程溃散整个进程都死掉。所以多进程要比多线程健壮。

履行进程: 每个独立的进程有程序运转的进口、次第履行序列和程序出口。可是 线程不能独立履行,有必要依存在运用程序中,由运用程序供给多个线程履行控 制,两者均可并发履行

什么是上下文切换?

多线程编程中一般线程的个数都大于 CPU 中心的个数,而一个 CPU 中心在任 意时刻只能被一个线程运用,为了让这些线程都能得到有用履行,CPU 采纳的 战略是为每个线程分配时刻片并轮转的办法。当一个线程的时刻片用完的时分就会重新处于安排妥当状况让给其他线程运用,这个进程就归于一次上下文切换。

归纳来说便是:当时使命在履行完 CPU 时刻片切换到另一个使命之前会先保存 自己的状况,以便下次再切换回这个使命时,能够再加载这个使命的状况。使命从保存到再加载的进程便是一次上下文切换

上下文切换一般是核算密集型的。也便是说,它需求适当可观的处理器时刻,在 每秒几十上百次的切换中,每次切换都需求纳秒量级的时刻。所以,上下文切换对体系来说意味着消耗许多的CPU时刻,事实上,或许是操作体系中时刻消耗最大的操作。

Linux 比较与其他操作体系(包括其他类 Unix 体系)有许多的长处,其间有一 项便是,其上下文切换和形式切换的时刻消耗十分少。

看护线程和用户线程有什么差异呢?

看护线程和用户线程

  • 用户 (User) 线程:运转在前台,履行具体的使命,如程序的主线程、衔接网络的子线程等都是用户线程
  • 看护 (Daemon) 线程:运转在后台,为其他前台线程服务。也能够说看护线程是 JVM 中非看护线程的 “仆人”。一旦一切用户线程都结束运转,看护线程 会随 JVM 一同结束作业

main 函数所在的线程便是一个用户线程啊,main 函数发动的一同在 JVM 内部 一同还发动了好多看护线程,比方废物收回线程。 比较显着的差异之一是用户线程结束,JVM 退出,不论这个时分有没有看护线程运转。而看护线程不会影响 JVM 的退出。

留意事项:

  1. setDaemon(true)有必要在start()办法前履行,不然会抛出 IllegalThreadStateException 反常
  2. 在看护线程中发生的新线程也是看护线程
  3. 不是一切的使命都能够分配给看护线程来履行,比方读写操作或许核算逻辑
  4. 看护 (Daemon) 线程中不能依靠 finally 块的内容来确保履行关闭或整理资源的逻辑。由于咱们上面也说过了一旦一切用户线程都结束运转,看护线程会随JVM一同结束作业,所以看护 (Daemon) 线程中的finally句子块或许无法被履行。

如安在 Windows 和 Linux 上查找哪个线程cpu运用率最高?

windows上面用使命办理器看,linux下能够用 top 这个东西看。

  1. 找出cpu耗用凶猛的进程pid, 终端履行top指令,然后按下shift+p 查找出cpu运用凶猛的pid号
  2. 依据上面第一步拿到的pid号,top -H -p pid 。然后按下shift+p,查找出cpu运用率凶猛的线程号,比方top -H -p 1328
  3. 将获取到的线程号转化成16进制,去百度转化一下就行
  4. 运用jstack东西将进程信息打印输出,jstack pid号 > /tmp/t.dat,比 如jstack 31365 > /tmp/t.dat
  5. 编辑/tmp/t.dat文件,查找线程号对应的信息

什么是线程死锁

百度百科:死锁是指两个或两个以上的进程(线程)在履行进程中,由于竞赛资源或许由于相互通讯而构成的一种堵塞的现象,若无外力效果,它们都将无法推进下去。此刻称体系处于死锁状况或体系发生了死锁,这些永久在相互等候的进 程(线程)称为死锁进程(线程)。

多个线程一同被堵塞,它们中的一个或许悉数都在等候某个资源被开释。由于线程被无限期地堵塞,因而程序不行能正常中止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们一同都想恳求对方 的资源,所以这两个线程就会相互等候而进入死锁状况。

八股文系列:Java的并发编程

线程死锁 下面经过一个比方来阐明线程死锁,代码模拟了上图的死锁的状况 :

 public class DeadLockDemo {
   private static Object resource1 = new Object();//资源 1 
   private static Object resource2 = new Object();//资源 2
   public static void main(String[] args) {
     new Thread(() ‐> { 
       synchronized (resource1) { 
           System.out.println(Thread.currentThread() + "get resource1"); 
           try { 
              Thread.sleep(1000); 
            } catch (InterruptedException e) { 
              e.printStackTrace(); 
             } 
          System.out.println(Thread.currentThread() + "waiting get resource2"); 
          synchronized (resource2) { 
             System.out.println(Thread.currentThread() + "get resource2"); 
           } 
       } 
   }, "线程 1").start();
   new Thread(() ‐> { 
       synchronized (resource2) { 
          System.out.println(Thread.currentThread() + "get resource2"); 
          try { 
             Thread.sleep(1000); 
           } catch (InterruptedException e) { 
             e.printStackTrace(); 
             } 
          System.out.println(Thread.currentThread() + "waiting get resource1"); 
            synchronized (resource1) { 
              System.out.println(Thread.currentThread() + "get resource1"); 
           } 
         } 
       }, "线程 2").start(); 
     } 
  } 

输出成果

Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2 
Thread[线程 1,5,main]waiting get resource2 
Thread[线程 2,5,main]waiting get resource1 

线程 A 经过 synchronized (resource1) 取得 resource1 的监视器锁,然后通 过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到CPU履行权,然 后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开端企图恳求获取对方的资源,然后这两个线程就会堕入相互等候的状况,这也就发生了死 锁。上面的比方契合发生死锁的四个必要条件。

构成死锁的四个必要条件是什么

  1. 互斥条件:线程(进程)关于所分配到的资源具有排它性,即一个资源只 能被一个线程(进程)占用,直到被该线程(进程)开释
  2. 恳求与坚持条件:一个线程(进程)因恳求被占用资源而发生堵塞时,对 已取得的资源坚持不放。
  3. 不掠夺条件:线程(进程)已取得的资源在末运用完之前不能被其他线程 强行掠夺,只需自己运用结束后才开释资源。
  4. 循环等候条件:当发生死锁时,所等候的线程(进程)必定会构成一个环 路(类似于死循环),构成永久堵塞

怎样防止线程死锁

咱们只需损坏发生死锁的四个条件中的其间一个就能够了。

损坏互斥条件

这个条件咱们没有办法损坏,由于咱们用锁本来便是想让他们互斥的(临界资源 需求互斥拜访)。

损坏恳求与坚持条件

一次性恳求一切的资源。

损坏不掠夺条件

占用部分资源的线程进一步恳求其他资源时,假设恳求不到,能够主动开释它占有的资源。

损坏循环等候条件

靠按序恳求资源来预防。按某一次第恳求资源,开释资源则反序开释。损坏循环 等候条件。

咱们对线程 2 的代码修正成下面这样就不会发生死锁了。

new Thread(() ‐> {
   synchronized (resource1) { 
      System.out.println(Thread.currentThread() + "get resource1"); 
      try { 
         Thread.sleep(1000); 
      } catch (InterruptedException e) { 
         e.printStackTrace();
      } 
      System.out.println(Thread.currentThread() + "waiting get resource2"); 
      synchronized (resource2) { 
         System.out.println(Thread.currentThread() + "get resource2"); 
       } 
     } 
   }, "线程 2").start(); 

输出成果

Thread[线程 1,5,main]get resource1
Thread[线程 1,5,main]waiting get resource2 
Thread[线程 1,5,main]get resource2 
Thread[线程 2,5,main]get resource1 
Thread[线程 2,5,main]waiting get resource2 
Thread[线程 2,5,main]get resource2 

咱们剖析一下上面的代码为什么防止了死锁的发生?

线程 1 首要取得到 resource1 的监视器锁,这时分线程 2 就获取不到了。然后线程 1再去获取 resource2 的监视器锁,能够获取到。然后线程 1 开释了对 resource1、resource2 的监视器锁的占用,线程 2 获取到就能够履行了。这样 就损坏了损坏循环等候条件,因而防止了死锁。

创立线程的四种办法

创立线程有四种办法:

  • 承继 Thread 类;
  • 完结 Runnable 接口;
  • 完结 Callable 接口;
  • 运用 Executors 东西类创立线程池承继 Thread 类

1.承继Thread类

进程

  1. 界说一个Thread类的子类,重写run办法,将相关逻辑完结,run()办法便是线程要履行的事务逻辑办法
  2. 创立自界说的线程子类目标
  3. 调用子类实例的star()办法来发动线程
 public class MyThread extends Thread {
   @Override
	public void run() {
	System.out.println(Thread.currentThread().getName() + " run()办法正在履行...");
	}
 }
 public class TheadTest {
	public static void main(String[] args) {
	MyThread myThread = new MyThread();
	myThread.start();
	System.out.println(Thread.currentThread().getName() + " main()办法履行结束");
	}
 }

运转成果

main main()办法履行结束
Thread‐0 run()办法正在履行... 

2.完结 Runnable 接口

进程

  1. 界说Runnable接口完结类MyRunnable,并重写run()办法
  2. 创立MyRunnable实例myRunnable,以myRunnable作为target创立Thead目标,该Thread目标才是真实的线程目标
  3. 调用线程目标的start()办法
 public class MyRunnable implements Runnable {
	@Override
	public void run() {
	   System.out.println(Thread.currentThread().getName() + " run()办法履行中...");
	}
 }
 public class RunnableTest {
	  public static void main(String[] args) {
	      MyRunnable myRunnable = new MyRunnable();
	      Thread thread = new Thread(myRunnable);
	      thread.start();
	      System.out.println(Thread.currentThread().getName() + " main()办法履行完结");
	  }
 }

履行成果

main main()办法履行完结
Thread‐0 run()办法履行中...

3.完结 Callable 接口

进程

  1. 创立完结Callable接口的类MyCallable
  2. 以MyCallable为参数创立FutureTask目标
  3. 将FutureTask作为参数创立Thread目标
  4. 调用线程目标的start()办法
 public class MyCallable implements Callable<Integer> {
	@Override
	public Integer call() {
	   System.out.println(Thread.currentThread().getName() + " call()办法履行中...");
	   return 1;
	}
 }
 public class CallableTest {
	public static void main(String[] args) {
	   FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyCallable());
	   Thread thread = new Thread(futureTask);
	   thread.start();
	   try {
	      Thread.sleep(1000);
	      System.out.println("回来成果 " + futureTask.get());
	   } catch (InterruptedException e) {
	      e.printStackTrace();
	   } catch (ExecutionException e) {
	       e.printStackTrace();
	   }
	     System.out.println(Thread.currentThread().getName() + " main()办法履行完结");
	 }
 }

履行成果

Thread‐0 call()办法履行中...
回来成果 1
main main()办法履行完结

4.运用 Executors 东西类创立线程池

Executors供给了一系列工厂办法用于创先线程池,回来的线程池都完结了ExecutorService接口。首要有newFixedThreadPool,newCachedThreadPool,newSingleThreadExecutor,newScheduledThreadPool,后续具体介绍这四种线程池

 public class MyRunnable implements Runnable {
	@Override
	public void run() {
	System.out.println(Thread.currentThread().getName() + " run()办法履行中...");
	}
 }
 publicclassSingleThreadExecutorTest{
 publicstaticvoidmain(String[]args){ 
    ExecutorServiceexecutorService=Executors.newSingleThreadExecutor(); 
    MyRunnablerunnableTest=newMyRunnable(); 
	   for (int i = 0; i < 5; i++) {
	      executorService.execute(runnableTest);
	   }
	   System.out.println("线程使命开端履行");
	   executorService.shutdown();
	}
 }

履行成果

线程使命开端履行
pool‐1‐thread‐1 is running...
pool‐1‐thread‐1 is running...
pool‐1‐thread‐1 is running...
pool‐1‐thread‐1 is running...
pool‐1‐thread‐1 is running...

线程的 run()和 start()有什么差异?

每个线程都是经过某个特定Thread目标所对应的办法run()来完结其操作的, run()办法称为线程体。经过调用Thread类的start()办法来发动一个线程。 start() 办法用于发动线程,run() 办法用于履行线程的运转时代码。run() 能够重复调用,而 start() 只能调用一次。 start()办法来发动一个线程,真实完结了多线程运转。调用start()办法无需等候 run办法体代码履行结束,能够直接持续履行其他的代码; 此刻线程是处于安排妥当状况,并没有运转。 然后经过此Thread类调用办法run()来完结其运转状况, run()办法运转结束, 此线程中止。然后CPU再调度其它线程。

run()办法是在本线程里的,仅仅线程里的一个函数,而不是多线程的。 假设直接调用run(),其实就适当所以调用了一个一般函数罢了,直接待用run()办法有必要等候run()办法履行结束才干履行下面的代码,所以履行途径仍是只需一条,根本就没有线程的特征,所以在多线程履行时要运用start()办法而不是run()办法

为什么咱们调用 start() 办法时会履行 run() 办法,为什么咱们不能直接调用 run() 办法?

这是另一个十分经典的 java 多线程面试问题,而且在面试中会常常被问到。很简略,可是许多人都会答不上来!

new 一个 Thread,线程进入了新建状况。调用 start() 办法,会发动一个线程并使线程进入了安排妥当状况,当分配到时刻片后就能够开端运转了。 start() 会履行线程的相应预备作业,然后主动履行 run() 办法的内容,这是真实的多线程作业。

而直接履行 run() 办法,会把 run 办法当成一个 main 线程下的一般办法去履行,并不会在某个线程中履行它,所以这并不是多线程作业。

总结: 调用 start 办法方可发动线程并使线程进入安排妥当状况,而 run 办法仅仅 thread 的一个一般办法调用,仍是在主线程里履行。

什么是 Callable 和 Future?

Callable 接口类似于 Runnable,从姓名就能够看出来了,可是 Runnable 不会回来成果,而且无法抛出回来成果的反常,而 Callable 功用更强壮一些,被线程履行后,能够回来值,这个回来值能够被 Future 拿到,也便是说,Future 能够拿到异步履行使命的回来值。

Future 接口标明异步使命,是一个或许还没有完结的异步使命的成果。所以说Callable用于发生成果,Future 用于获取成果。

什么是 FutureTask

FutureTask 标明一个异步运算的使命。FutureTask 里边能够传入一个 Callable 的具体完结类,能够对这个异步运算的使命的成果进行等候获取、判别是否现已完结、取消使命等操作。只需当运算完结的时分红果才干取回,假设运算没有完结 get 办法将会堵塞。一个 FutureTask 目标能够对调用了 Callable 和 Runnable 的目标进行包装,由于 FutureTask 也是Runnable 接口的完结类,所以 FutureTask 也能够放入线程池中。

线程的状况和根本操作说说线程的生命周期及五种根本状况?

八股文系列:Java的并发编程

  1. 新建(new):新创立了一个线程目标。
  2. 可运转(runnable):线程目标创立后,当调用线程目标的 start()办法,该线程处于安排妥当状况,等候被线程调度选中,获取cpu的运用权。
  3. 运转(running):可运转状况(runnable)的线程取得了cpu时刻片(timeslice),履行程序代码。注:安排妥当状况是进入到运转状况的仅有进口,也便是说,线程要想进入运转状况履行,首要有必要处于安排妥当状况中;
  4. 堵塞(block):处于运转状况中的线程由于某种原因,暂时抛弃对 CPU 的运用权,中止履行,此刻进入堵塞状况,直到其进入到安排妥当状况,才 有时机再次被 CPU 调用以进入到运转状况。

堵塞的状况分三种:

(一). 等候堵塞:运转状况中的线程履行 wait()办法,JVM会把该线程放入等候行列(waitting queue)中,使本线程进入到等候堵塞状况;

(二). 同步堵塞:线程在获取 synchronized 同步锁失利(由于锁被其它线程所占用),,则JVM会把该线程放入锁池(lock pool)中,线程会进入同步堵塞状况;

(三). 其他堵塞: 经过调用线程的 sleep()或 join()或发出了 I/O 恳求时,线程会进入到堵塞状况。当 sleep()状况超时、join()等候线程中止或许超时、或许 I/O 处理结束时,线程重新转入安排妥当状况。

  1. 逝世(dead):线程run()、main()办法履行结束,或许因反常退出了 run()办法,则该线程结束生命周期。逝世的线程不行再次复生。

Java 顶用到的线程调度算法是什么?

核算机一般只需一个 CPU,在恣意时刻只能履行一条机器指令,每个线程只需取得CPU 的运用权才干履行指令。所谓多线程的并发运转,其实是指从宏观上看,各个线程轮番取得 CPU 的运用权,分别履行各自的使命。在运转池中,会有多个处于安排妥当状况的线程在等候 CPU,JAVA 虚拟机的一项使命便是担任线程的调度,线程调度是指依照特定机制为多个线程分配 CPU 的运用权。

有两种调度模型:分时调度模型和抢占式调度模型。

分时调度模型是指让一切的线程轮番取得 cpu 的运用权,而且平均分配每个线程占用的 CPU 的时刻片这个也比较好了解。

java虚拟机选用抢占式调度模型,是指优先让可运转池中优先级高的线程占用CPU,假设可运转池中的线程优先级相同,那么就随机挑选一个线程,使其占用CPU。处于运转状况的线程会一向运转,直至它不得不抛弃 CPU。

线程的调度战略

线程调度器挑选优先级高的线程运转,可是假设发生以下状况,就会中止线程的运转:

(1) 线程体中调用了 yield 办法让出了对 cpu 的占用权利

(2) 线程体中调用了 sleep 办法使线程进入睡觉状况

(3) 线程由于 IO 操作遭到堵塞

(4) 别的一个更高优先级线程呈现

(5) 在支撑时刻片的体系中,该线程的时刻片用完

什么是线程调度器(Thread Scheduler)和时刻分片(Time Slicing )?

线程调度器是一个操作体系服务,它担任为 Runnable 状况的线程分配 CPU 时刻。一旦咱们创立一个线程并发动它,它的履行便依赖于线程调度器的完结。时刻分片是指将可用的 CPU 时刻分配给可用的 Runnable 线程的进程。分配

CPU 时刻能够依据线程优先级或许线程等候的时刻。

线程调度并不遭到 Java 虚拟机操控,所以由运用程序来操控它是更好的挑选

(也便是说不要让你的程序依赖于线程的优先级)。

请说出与线程同步以及线程调度相关的办法。

(1) wait():使一个线程处于等候(堵塞)状况,而且开释所持有的目标的锁;

(2) sleep():使一个正在运转的线程处于睡觉状况,是一个静态办法,调用此办法要处理 InterruptedException 反常;

(3) notify():唤醒一个处于等候状况的线程,当然在调用此办法的时分,并不能切当的唤醒某一个等候状况的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;

(4) notityAll():唤醒一切处于等候状况的线程,该办法并不是将目标的锁给一切线程,而是让它们竞赛,只需取得锁的线程才干进入安排妥当状况;

sleep() 和 wait() 有什么差异?

两者都能够暂停线程的履行

  • 类的不同:sleep() 是 Thread线程类的静态办法,wait() 是 Object类的办法。
  • 是否开释锁:sleep() 不开释锁;wait() 开释锁。
  • 用途不同:Wait 一般被用于线程间交互/通讯,sleep 一般被用于暂停履行。
  • 用法不同:wait() 办法被调用后,线程不会主动复苏,需求别的线程调用同一个目标上的 notify() 或许 notifyAll() 办法。sleep() 办法履行完结后,线程会主动复苏。或许能够运用wait(long timeout)超时后线程会主动复苏。

你是怎样调用 wait() 办法的?运用 if 块仍是循环?为什么?

处于等候状况的线程或许会收到过错警报和伪唤醒,假设不在循环中检查等候条件,程序就会在没有满意结束条件的状况下退出。

wait() 办法应该在循环调用,由于当线程获取到 CPU 开端履行的时分,其他条件或许还没有满意,所以在处理前,循环检测条件是否满意会更好。下面是一段标准的运用 wait 和 notify 办法的代码:

synchronized (monitor) {
	// 判别条件谓词是否得到满意
	while(!locked) {
	// 等候唤醒
	monitor.wait();
	}
// 处理其他的事务逻辑
}

为什么线程通讯的办法 wait(), notify()和 notifyAll()被界说在 Object 类里?

Java中,任何目标都能够作为锁,而且 wait(),notify()等办法用于等候目标的锁或许唤醒线程,在 Java 的线程中并没有可供任何目标运用的锁,所以恣意目标调用办法必定界说在Object类中。

wait(), notify()和 notifyAll()这些办法在同步代码块中调用

有的人会说,已然是线程抛弃目标锁,那也能够把wait()界说在Thread类里边啊,新界说的线程承继于Thread类,也不需求重新界说wait()办法的完结。然而,这样做有一个十分大的问题,一个线程彻底能够持有许多锁,你一个线程抛弃锁的时分,到底要抛弃哪个锁?当然了,这种规划并不是不能完结,仅仅办理起来愈加杂乱。

综上所述,wait()、notify()和notifyAll()办法要界说在Object类中。

为什么 wait(), notify()和 notifyAll()有必要在同步办法或许同步块中被调用?

当一个线程需求调用目标的 wait()办法的时分,这个线程有必要具有该目标的锁,

接着它就会开释这个目标锁并进入等候状况直到其他线程调用这个目标上的notify()办法。同样的,当一个线程需求调用目标的 notify()办法时,它会开释这个目标的锁,以便其他在等候的线程就能够得到这个目标锁。由于一切的这些办法都需求线程持有目标的锁,这样就只能经过同步来完结,所以他们只能在同步办法或许同步块中被调用。

Thread 类中的 yield 办法有什么效果?

使当时线程从履行状况(运转状况)变为可履行态(安排妥当状况)。

当时线程到了安排妥当状况,那么接下来哪个线程会从安排妥当状况变成履行状况呢?或许是当时线程,也或许是其他线程,看体系的分配了。

为什么 Thread 类的 sleep()和 yield ()办法是静态的?

Thread 类的 sleep()和 yield()办法将在当时正在履行的线程上运转。所以在其他处于等候状况的线程上调用这些办法是没有意义的。这便是为什么这些办法是静态的。它们能够在当时正在履行的线程中作业,并防止程序员过错的以为能够在其他非运转线程调用这些办法。

线程的 sleep()办法和 yield()办法有什么差异?

(1) sleep()办法给其他线程运转时机时不考虑线程的优先级,因而会给低优先级的线程以运转的时机;yield()办法只会给相同优先级或更高优先级的线程以运转的时机;

(2) 线程履行 sleep()办法后转入堵塞(blocked)状况,而履行 yield()办法后转入安排妥当(ready)状况;

(3) sleep()办法声明抛出 InterruptedException,而 yield()办法没有声明任何反常;

(4) sleep()办法比 yield()办法(跟操作体系 CPU 调度相关)具有更好的可移植性,一般不主张运用yield()办法来操控并发线程的履行。

怎样中止一个正在运转的线程?

在java中有以下3种办法能够中止正在运转的线程:

  1. 运用退出标志,使线程正常退出,也便是当run办法完结后线程中止。
  2. 运用stop办法强行中止,可是不引荐这个办法,由于stop和suspend及 resume相同都是过期作废的办法。
  3. 运用interrupt办法中止线程。

Java 中 interrupted 和 isInterrupted 办法的差异?

interrupt:用于中止线程。调用该办法的线程的状况为将被置为”中止”状况。留意:线程中止仅仅是置线程的中止状况位,不会中止线程。需求用户自己去监视线程的状况为并做处理。支撑线程中止的办法(也便是线程中止后会抛出interruptedException 的办法)便是在监视线程的中止状况,一旦线程的中止状况被置为“中止状况”,就会抛出中止反常。

interrupted:是静态办法,检查当时中止信号是true仍是false而且清除中止信号。假设一个线程被中止了,第一次调用 interrupted 则回来 true,第二次和后边的就回来 false 了。

isInterrupted:检查当时中止信号是true仍是false

什么是堵塞式办法?

堵塞式办法是指程序会一向等候该办法完结期间不做其他事情,ServerSocket 的accept()办法便是一向等候客户端衔接。这儿的堵塞是指调用成果回来之前,当时线程会被挂起,直到得到成果之后才会回来。此外,还有异步和非堵塞式办法在使命完结前就回来.

Java 中你怎样唤醒一个堵塞的线程?

首要 ,wait()、notify() 办法是针对目标的,调用恣意目标的 wait()办法都将导致线程堵塞,堵塞的一同也将开释该目标的锁,相应地,调用恣意目标的notify()办法则将随机免除该目标堵塞的线程,但它需求重新获取该目标的锁,直到获取成功才干往下履行;

其次,wait、notify 办法有必要在 synchronized 块或办法中被调用,而且要确保同步块或办法的锁目标与调用 wait、notify 办法的目标是同一个,如此一来在调用 wait 之前当时线程就现已成功获取某目标的锁,履行 wait 堵塞后当时线程就将之前获取的目标锁开释。

notify() 和 notifyAll() 有什么差异?

假设线程调用了目标的 wait()办法,那么线程便会处于该目标的等候池中,等候池中的线程不会去竞赛该目标的锁。

notifyAll() 会唤醒一切的线程,notify() 只会唤醒一个线程。

notifyAll() 调用后,会将悉数线程由等候池移到锁池,然后参加锁的竞赛,竞赛成功则持续履行,假设不成功则留在锁池等候锁被开释后再次参加竞赛。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机操控。

如安在两个线程间同享数据?

在两个线程间同享变量即可完结同享。

一般来说,同享变量要求变量本身是线程安全的,然后在线程内运用的时分,假设有对同享变量的复合操作,那么也得确保复合操作的线程安全性。

Java 怎样完结多线程之间的通讯和协作?

能够经过中止 和 同享变量的办法完结线程间的通讯和协作

比方说 经典的生产者-顾客模型:当行列满时,生产者需求等候行列有空间才干持续往里边放入产品,而在等候的期间内,生产者有必要开释对临界资源(即行列)的占用权。由于生产者假设不开释对临界资源的占用权,那么顾客就无法消费行列中的产品,就不会让行列有空间,那么生产者就会一向无限等候下去。因而,一般状况下,当行列满时,会让生产者交出对临界资源的占用权,并进入挂起状况。然后等候顾客消费了产品,然后顾客告诉生产者行列有空间了。同样地,当行列空时,顾客也有必要等候,等候生产者告诉它行列中有产品了。这种相互通讯的进程便是线程间的协作。

Java中线程通讯协作的 常见的两种办法:

一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll() 线程间直接的数据交流:

三.经过管道进行线程间通讯:1)字节省;2)字符流

同步办法和同步块,哪个是更好的挑选?

同步块是更好的挑选,由于它不会锁住整个目标(当然你也能够让它锁住整个目标)。同步办法会锁住整个目标,哪怕这个类中有多个不相相关的同步块,这一般会导致他们中止履行并需求等候取得这个目标上的锁。

同步块更要契合敞开调用的准则,只在需求锁住的代码块锁住相应的目标,这样从旁边面来说也能够防止死锁。

请知道一条准则:同步的范围越小越好。

什么是线程同步和线程互斥,有哪几种完结办法?

当一个线程对同享的数据进行操作时,应使之成为一个”原子操作“,即在没有完结相关操作之前,不答应其他线程打断它,不然,就会损坏数据的完整性,必定会得到过错的处理成果,这便是线程的同步。

在多线程运用中,考虑不同线程之间的数据同步和防止死锁。当两个或多个线程之间一同等候对方开释资源的时分就会构成线程之间的死锁。为了防止死锁的发生,需求经过同步来完结线程安全。

线程互斥是指关于同享的进程体系资源,在各单个线程拜访时的排它性。当有若干个线程都要运用某一同享资源时,任何时刻最多只答应一个线程去运用,其它要运用该资源的线程有必要等候,直到占用资源者开释该资源。线程互斥能够看成是一种特别的线程同步。

线程间的同步办法大体可分为两类:用户形式和内核形式。望文生义,内核形式便是指运用体系内核目标的单一性来进行同步,运用时需求切换内核态与用户态,而用户形式便是不需求切换到内核态,只在用户态完结操作。

用户形式下的办法有:原子操作(例如一个单一的全局变量),临界区。内核形式下的办法有:事情,信号量,互斥量。

完结线程同步的办法

  • 同步代码办法:sychronized 关键字润饰的办法同步代码块:
  • sychronized 关键字润饰的代码块
  • 运用特别变量域volatile完结线程同步:volatile关键字为域变量的拜访供给了一种免锁机制
  • 运用重入锁完结线程同步:reentrantlock类是可重入、互斥、完结了 lock接口的锁他与sychronized办法具有相同的根本行为和语义

在监视器(Monitor)内部,是怎样做线程同步的?程序应该做哪种等级的同步?

在 java 虚拟机中,每个目标( Object 和 class )经过某种逻辑相关监视器,每个监视器和一个目标引证相相关,为了完结监视器的互斥功用,每个目标都相关着一把锁。

一旦办法或许代码块被 synchronized 润饰,那么这个部分就放入了监视器的监视区域,确保一次只能有一个线程履行该部分的代码,线程在获取锁之前不答应履行该部分的代码

别的 java 还供给了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案

假设你提交使命时,线程池行列已满,这时会发生什么

这儿差异一下:

(1) 假设运用的是无界行列 LinkedBlockingQueue,也便是无界行列的话,没联系,持续添加使命到堵塞行列中等候履行,由于 LinkedBlockingQueue 能够近乎以为是一个无穷大的行列,能够无限寄存使命

(2) 假设运用的是有界行列比方 ArrayBlockingQueue,使命首要会被添加到ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会依据maximumPoolSize 的值添加线程数量,假设添加了线程数量仍是处理不过来,ArrayBlockingQueue 持续满,那么则会运用回绝战略RejectedExecutionHandler 处理满了的使命,默许是 AbortPolicy

什么叫线程安全?servlet 是线程安全吗?

线程安满是编程中的术语,指某个办法在多线程环境中被调用时,能够正确地处理多个线程之间的同享变量,使程序功用正确完结。

Servlet 不是线程安全的,servlet 是单实例多线程的,当多个线程一同拜访同一个办法,是不能确保同享变量的线程安全性的。

Struts2 的 action 是多实例多线程的,是线程安全的,每个恳求过来都会 new 一个新的 action 分配给这个恳求,恳求完结后毁掉。

SpringMVC 的 Controller 是线程安全的吗?不是的,和 Servlet 类似的处理流程。

Struts2 长处是不必考虑线程安全问题;Servlet 和 SpringMVC 需求考虑线程

安全问题,可是功用能够进步不必处理太多的 gc,能够运用 ThreadLocal 来处理多线程的问题。

在 Java 程序中怎样确保多线程的运转安全?

办法一:运用安全类,比方 java.util.concurrent 下的类,运用原子类AtomicInteger

办法二:运用主动锁 synchronized。

办法三:运用手动锁 Lock。

手动锁 Java 示例代码如下:

 Locklock=newReentrantLock();
 lock.lock(); 
 try{
	   System. out. println("取得锁");
	} catch (Exception e) {
	   // TODO: handle exception
	} finally {
	   System. out. println("开释锁");
	   lock. unlock();
	}

你对线程优先级的了解是什么?

每一个线程都是有优先级的,一般来说,高优先级的线程在运转时会具有优先权,但这依赖于线程调度的完结,这个完结是和操作体系相关的(OS dependent)。咱们能够界说线程的优先级,可是这并不能确保高优先级的线程 会在低优先级的线程前履行。线程优先级是一个 int 变量(从 1-10),1 代表低 优先级,10 代表高优先级。 Java 的线程优先级调度会托付给操作体系去处理,所以与具体的操作体系优先 级有关,如非特别需求,一般无需设置线程优先级。

线程类的结构办法、静态块是被哪个线程调用的

这是一个十分刁钻和狡猾的问题。

请记住:线程类的结构办法、静态块是被 new这个线程类所在的线程所调用的,而 run 办法里边的代码才是被线程本身所调用的。

假设说上面的说法让你感到困惑,那么我举个比方,假设 Thread2 中 new 了Thread1,main 函数中 new 了 Thread2,那么:

(1) Thread2 的结构办法、静态块是 main 线程调用的,Thread2 的 run()办法是Thread2 自己调用的

(2) Thread1 的结构办法、静态块是 Thread2 调用的,Thread1 的 run()办法是Thread1 自己调用的

Java 中怎样获取一份线程 dump 文件?你如安在 Java 中获取线程堆栈?

Dump文件是进程的内存镜像。能够把程序的履行状况经过调试器保存到dump 文件中。

在 Linux 下,你能够经过指令 kill -3 PID (Java 进程的进程 ID)来获取 Java 运用的 dump 文件。

在 Windows 下,你能够按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或过错文件中,它或许打印在操控台或许日志文件中,具体方位依赖运用的装备。

一个线程运转时发生反常会怎样?假设反常没有被捕获该线程将会中止履行。

Thread.UncaughtExceptionHandler是用于处理未捕获反常构成线程突然中止状况的一个内嵌接口。当一个未捕获反常将构成线程中止的时分,JVM 会运用 Thread.getUncaughtExceptionHandler()来查询线程的

UncaughtExceptionHandler 并将线程和反常作为参数传递给 handler 的 uncaughtException()办法进行处理。

Java 线程数过多会构成什么反常?

  • 线程的生命周期开支十分高消耗过多的
  • CPU资源假设可运转的线程数量多于可用处理器的数量,那么有线程将会被搁置。许多闲暇的线程会占用许多内存,给废物收回器带来压力,而且许多的线程在竞赛CPU资源时还将发生其他功用的开支。
  • 下降稳定性JVM 在可创立线程的数量上存在一个约束,这个约束值将跟着渠道的不同而不同,而且承受着多个因素约束,包括 JVM 的发动参数、Thread 结构函数中恳求栈的巨细,以及底层操作体系对线程的约束等。假设损坏了这些约束,那么或许抛出OutOfMemoryError 反常。

并发理论

Java内存模型

Java中废物收回有什么意图?什么时分进行废物收回?

废物收回是在内存中存在没有引证的目标或超越效果域的目标时进行的。废物收回的意图是辨认而且丢掉运用不再运用的目标来开释和重用资源。

假设目标的引证被置为null,废物收集器是否会当即开释目标占用的内存?

不会,鄙人一个废物回调周期中,这个目标将是被可收回的。

也便是说并不会当即被废物收集器马上收回,而是鄙人一次废物收回时才会开释其占用的内存。

finalize()办法什么时分被调用?析构函数(finalization)的意图是什么?

1) 废物收回器(garbage colector)决议收回某目标时,就会运转该目标的finalize()办法;

finalize是Object类的一个办法,该办法在Object类中的声明protected void finalize() throws Throwable { }

在废物收回器履行时会调用被收回目标的finalize()办法,能够掩盖此办法来完结对其资源的收回。留意:一旦废物收回器预备开释目标占用的内存,将首要调用该目标的finalize()办法,而且下一次废物收回动作发生时,才真实收回目标占用的内存空间

2) GC本来便是内存收回了,运用还需求在finalization做什么呢? 答案是大部分时分,什么都不必做(也便是不需求重载)。只需在某些很特别的状况下,比方你调用了一些native的办法(一般是C写的),能够要在finaliztion里去调用C的开释函数。

重排序数据依赖性为什么代码会重排序?

在履行程序时,为了进步功用,处理器和编译器常常会对指令进行重排序,可是不能随意重排序,不是你想怎样排序就怎样排序,它需求满意以下两个条件:在单线程环境下不能改动程序运转的成果存在数据依赖联系的不答应重排序

需求留意的是:重排序不会影响单线程环境的履行成果,可是会损坏多线程的履行语义。

as-if-serial规矩和happens-before规矩的差异

as-if-serial语义确保单线程内程序的履行成果不被改动,happens-before联系确保正确同步的多线程程序的履行成果不被改动。 as-if-serial语义给编写单线程程序的程序员发明了一个幻景:单线程程序是按程序的次第来履行的。happens-before联系给编写正确同步的多线程程序的程序员发明了一个幻景:正确同步的多线程程序是按 happens-before指定的次第来履行的。

as-if-serial语义和happens-before这么做的意图,都是为了在不改动程序履行成果的前提下,尽或许地进步程序履行的并行度。

并发关键字

synchronized

synchronized 的效果?

在 Java 中,synchronized 关键字是用来操控线程同步的,便是在多线程的环境下,操控 synchronized 代码段不被多个线程一同履行。synchronized 能够润饰类、办法、变量。

别的,在 Java 前期版别中,synchronized归于重量级锁,功率低下,由于监视器锁(monitor)是依赖于底层的操作体系的 Mutex Lock 来完结的,Java 的线程是映射到操作体系的原生线程之上的。假设要挂起或许唤醒一个线程,都需求操作体系帮忙完结,而操作体系完结线程之间的切换时需求从用户态转化到内核态,这个状况之间的转化需求相对比较长的时刻,时刻本钱相对较高,这也是为什么前期的 synchronized 功率低的原因。幸亏的是在 Java 6 之后 Java 官方对从 JVM 层面临synchronized 较大优化,所以现在的 synchronized 锁功率也优化得很不错了。JDK1.6对锁的完结引进了许多的优化,如自旋锁、习惯性自旋锁、锁消除、锁粗化、倾向锁、轻量级锁等技能来削减锁操作的开支。

说说自己是怎样运用 synchronized 关键字,在项目顶用到了吗

synchronized关键字最首要的三种运用办法:

润饰实例办法: 效果于当时目标实例加锁,进入同步代码前要取得当时目标实例的锁

润饰静态办法: 也便是给当时类加锁,会效果于类的一切目标实例,由于静态成员不归于任何一个实例目标,是类成员( static 标明这是该类的一个静态资源,不论new了多少个目标,只需一份)。所以假设一个线程A调用一个实例目标的非静态 synchronized 办法,而线程B需求调用这个实例目标所属类的静态 synchronized 办法,是答应的,不会发生互斥现象,由于拜访静态 synchronized 办法占用的锁是当时类的锁,而拜访非静态 synchronized 办法占用的锁是当时实例目标锁。

润饰代码块: 指定加锁目标,对给定目标加锁,进入同步代码库前要取得给定目标的锁。

总结: synchronized 关键字加到 static 静态办法和 synchronized(class)代码块上都是是给 Class 类上锁。synchronized 关键字加到实例办法上是给目标实例上锁。尽量不要运用 synchronized(String a) 由于JVM中,字符串常量池具有缓存功用!

下面我以一个常见的面试题为例解说一下 synchronized 关键字的具体运用。面试中面试官常常会说:“单例形式了解吗?来给我手写一下!给我解释一下两层查验锁办法完结单例形式的原理呗!” 两层校验锁完结目标单例(线程安全)

public class Singleton {
 private volatile static Singleton uniqueInstance;
	private Singleton() {
	}
	public static Singleton getUniqueInstance() {
	//先判别目标是否现已实例过,没有实例化过才进入加锁代码
	if (uniqueInstance == null) {
	//类目标加锁
	  synchronized (Singleton.class) {
	     if (uniqueInstance == null) {
	         uniqueInstance = new Singleton();
	      }
	  }
	}
	return uniqueInstance;
	}
}

别的,需求留意 uniqueInstance 选用 volatile 关键字润饰也是很有必要。 uniqueInstance = new Singleton(); 这段代码其实是分为三步履行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

可是由于 JVM 具有指令重排的特性,履行次第有或许变成 1->3->2。指令重排在单线程环境下不会呈现问题,可是在多线程环境下会导致一个线程取得还没有初始化的实例。例如,线程 T1 履行了 1 和 3,此刻 T2 调用getUniqueInstance() 后发现 uniqueInstance 不为空,因而回来 uniqueInstance,但此刻 uniqueInstance 还未被初始化。运用 volatile 能够禁止 JVM 的指令重排,确保在多线程环境下也能正常运转。

说一下 synchronized 底层完结原理?

synchronized是Java中的一个关键字,在运用的进程中并没有看到显现的加锁和解锁进程。因而有必要经过javap指令,检查相应的字节码文件。 synchronized 同步句子块的状况

public class SynchronizedDemo {
   public void method() {
      synchronized (this) {
         System.out.println("synchronized 代码块");
	    }
	 }
}

先运用指令 javac SynchronizedDemo.java 编译成class文件

经过JDK 反汇编指令 javap -c -v SynchronizedDemo.class

八股文系列:Java的并发编程

能够看出在履行同步代码块之前之后都有一个monitor字样,其间前面的是monitorenter,后边的是离开monitorexit,不难想象一个线程也履行同步代码块,首要要获取锁,而获取锁的进程便是monitorenter ,在履行完代码块之后,要开释锁,开释锁便是履行monitorexit指令。

为什么会有两个monitorexit呢?

这个首要是防止在同步代码块中线程因反常退出,而锁没有得到开释,这必定会构成死锁(等候的线程永久获取不到锁)。因而 后一个monitorexit是确保在反常状况下,锁也能够得到开释,防止死锁。

仅有ACC_SYNCHRONIZED这么一个标志,该符号标明线程进入该办法时,需求monitorenter,退出该办法时需求monitorexit。

synchronized可重入的原理

重入锁是指一个线程获取到该锁之后,该线程能够持续取得该锁。底层原理保护一个计数器,当线程获取该锁时,计数器加一,再次取得该锁时持续加一,开释锁时,计数器减一,当计数器值为0时,标明该锁未被任何线程所持有,其它线程能够竞赛获取锁。

什么是自旋

许多 synchronized 里边的代码仅仅一些很简略的代码,履行时刻十分快,此刻等候的线程都加锁或许是一种不太值得的操作,由于线程堵塞触及到用户态和内核态切换的问题。已然 synchronized 里边的代码履行得十分快,不妨让等候锁的线程不要被堵塞,而是在 synchronized 的鸿沟做循环,这便是自旋。假设做了屡次循环发现还没有取得锁,再堵塞,这样或许是一种更好的战略。

多线程中 synchronized 锁晋级的原理是什么?

synchronized 锁晋级原理:在锁目标的目标头里边有一个 threadid 字段,在第一次拜访的时分 threadid 为空,jvm 让其持有倾向锁,并将 threadid 设置为其线程 id,再次进入的时分会先判别 threadid 是否与其线程 id 共同,假设共同则能够直接运用此目标,假设不共同,则晋级倾向锁为轻量级锁,经过自旋循环必定次数来获取锁,履行必定次数之后,假设还没有正常获取到要运用的目标,此刻就会把锁从轻量级晋级为重量级锁,此进程就构成了 synchronized 锁的晋级。

锁的晋级的意图:锁晋级是为了减低了锁带来的功用消耗。在 Java 6 之后优化 synchronized 的完结办法,运用了倾向锁晋级为轻量级锁再晋级到重量级锁的办法,然后减低了锁带来的功用消耗。

线程 B 怎样知道线程 A 修正了变量

(1) volatile 润饰变量

(2) synchronized 润饰修正动量的办法

(3) wait/notify

(4)while 轮询

当一个线程进一个目标的 synchronized 办法 A 之后,其它线程是否可进入此目标的 synchronized 办法 B?

不能。其它线程只能拜访该目标的非同步办法,同步办法则不能进入。由于非静态办法上的 synchronized 润饰符要求履行办法时要取得目标的锁,假设现已进入A 办法阐明目标锁现已被取走,那么企图进入 B 办法的线程就只能在等锁池(留意不是等候池哦)中等候目标的锁。

synchronized、volatile、CAS 比较

(1)synchronized 是失望锁,归于抢占式,会引起其他线程堵塞。

(2)volatile 供给多线程同享变量可见性和禁止指令重排序优化。

(3)CAS 是依据抵触检测的达观锁(非堵塞)

synchronized 和 Lock 有什么差异?

  • 首要synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
  • synchronized 能够给类、办法、代码块加锁;而 lock 只能给代码块加锁。
  • synchronized 不需求手动获取锁和开释锁,运用简略,发生反常会主动开释锁,不会构成死锁;而 lock 需求自己加锁和开释锁,假设运用不当没有 unLock()去开释锁就会构成死锁。
  • 经过 Lock 能够知道有没有成功获取锁,而 synchronized 却无法办到。

synchronized 和 ReentrantLock 差异是什么?

synchronized 是和 if、else、for、while 相同的关键字,ReentrantLock 是类,这是二者的本质差异。已然 ReentrantLock 是类,那么它就供给了比synchronized 更多更灵敏的特性,能够被承继、能够有办法、能够有各种各样的类变量

synchronized 前期的完结比较低效,对比 ReentrantLock,大多数场景功用都相差较大,可是在 Java 6 中对 synchronized 进行了十分多的改善。

相同点:两者都是可重入锁。“可重入锁”概念是:自己能够再次获取自己的内部锁。比方一个线程取得了某个目标的锁,此刻这个目标锁还没有开释,当其再次想要获取这个目标的锁的时分仍是能够获取的,假设不行锁重入的话,就会构成死锁。

同一个线程每次获取锁,锁的计数器都自增1,所以要比及锁的计数器下降为0 时才干开释锁。首要差异如下:

  • ReentrantLock 运用起来比较灵敏,可是有必要有开释锁的配合动作;
  • ReentrantLock 有必要手动获取与开释锁,而 synchronized 不需求手动开释和开启锁;
  • ReentrantLock 只适用于代码块锁,而 synchronized 能够润饰类、办法、变量等。
  • 二者的锁机制其实也是不相同的。ReentrantLock 底层调用的是 Unsafe 的 park 办法加锁,synchronized 操作的应该是目标头中 mark word
  • Java中每一个目标都能够作为锁,这是synchronized完结同步的根底: 一般同步办法,锁是当时实例目标
  • 静态同步办法,锁是当时类的class目标
  • 同步办法块,锁是括号里边的目标

volatile

volatile 关键字的效果

关于可见性,Java 供给了 volatile 关键字来确保可见性禁止指令重排

volatile 供给 happens-before 的确保,确保一个线程的修正能对其他线程是可见的。当一个同享变量被 volatile 润饰时,它会确保修正的值会当即被更新到主存,当有其他线程需求读取时,它会去内存中读取新值。

从实践角度而言,volatile 的一个重要效果便是和 CAS 结合,确保了原子性,具体的能够拜见java.util.concurrent.atomic 包下的类,比方 AtomicInteger。

volatile 常用于多线程环境下的单次操作(单次读或许单次写)。

Java 中能创立 volatile 数组吗?

能,Java 中能够创立 volatile 类型数组,不过仅仅一个指向数组的引证,而不是整个数组。意思是,假设改动引证指向的数组,将会遭到 volatile 的保护,可是假设多个线程一同改动数组的元素,volatile 标示符就不能起到之前的保护效果了。

volatle 变量和 atomic 变量有什么不同?

volatile 变量能够确保先行联系,即写操作会发生在后续的读操作之前, 但它并不能确保原子性。例如用 volatile 润饰 count 变量,那么 count++ 操作就不是原子性的。

而 AtomicInteger 类供给的 atomic 办法能够让这种操作具有原子性,如getAndIncrement()办法会原子性的进行增量操作把当时值加一,其它数据类型和引证变量也能够进行类似操作。

volatile 能使得一个非原子操作变成原子操作吗?

关键字volatile的首要效果是使变量在多个线程间可见,但无法确保原子性,关于多个线程拜访同一个实例变量需求加锁进行同步。

虽然volatile只能确保可见性不能确保原子性,但用volatile润饰long和double 能够确保其操作原子性。

所以从Oracle Java Spec里边能够看到:

  • 关于64位的long和double,假设没有被volatile润饰,那么对其操作能够不是原子的。在操作的时分,能够分红两步,每次对32位操作。
  • 假设运用volatile润饰long和double,那么其读写都是原子操作关于64位的引证地址的读写,都是原子操作
  • 在完结JVM时,能够自由挑选是否把读写long和double作为原子操作
  • 引荐JVM完结为原子操作

volatile 润饰符的有过什么实践?

单例形式

是否 Lazy 初始化:是

是否多线程安全:是

完结难度:较杂乱描述:关于Double-Check这种或许呈现的问题(当然这种概率现已十分小了,但毕竟仍是有的嘛~),处理方案是:只需求给instance的声明加上volatile关键字即可volatile关键字的一个效果是禁止指令重排,把instance声明为volatile 之后,对它的写操作就会有一个内存屏障 ,这样,在它的赋值完结之前,就不必会调用读操作。留意:volatile阻止的不是singleton = newSingleton()这句话内部[1-2-3]的指令重排,而是确保了在一个写操作

public class Singleton {
    private static volatile Singleton instance = null;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

synchronized 和 volatile 的差异是什么?

synchronized 标明只需一个线程能够获取效果目标的锁,履行代码,堵塞其他线程。

volatile 标明变量在 CPU 的寄存器中是不确定的,有必要从主存中读取。确保多线程环境下变量的可见性;禁止指令重排序。

差异

  • volatile 是变量润饰符;synchronized 能够润饰类、办法、变量。
  • volatile 仅能完结变量的修正可见性,不能确保原子性;而 synchronized 则能够确保变量的修正可见性和原子性。
  • volatile 不会构成线程的堵塞;synchronized 或许会构成线程的堵塞。
  • volatile符号的变量不会被编译器优化;synchronized符号的变量能够被编译器优化。
  • volatile关键字是线程同步的轻量级完结,所以volatile功用肯定比 synchronized关键字要好。可是volatile关键字只能用于变量而synchronized关键字能够润饰办法以及代码块。synchronized关键字在JavaSE1.6之后进行了首要包括为了削减取得锁和开释锁带来的功用消耗而引进的倾向锁和轻量级锁以及其它各种优化之后履行功率有了显著进步,实践开发中运用 synchronized 关键字的场景仍是更多一些。

final

什么是不行变目标,它对写并发运用有什么帮助?

不行变目标(Immutable Objects)即目标一旦被创立它的状况(目标的数据,也即目标属性值)就不能改动,反之即为可变目标(Mutable Objects)。

不行变目标的类即为不行变类(Immutable Class)。Java 渠道类库中包括许多不行变类,如 String、根本类型的包装类、BigInteger 和 BigDecimal 等。

只需满意如下状况,一个目标才是不行变的; 它的状况不能在创立后再被修正; 一切域都是 final 类型;而且,它被正确创立(创立期间没有发生 this 引证的逸出)。

不行变目标确保了目标的内存可见性,对不行变目标的读取不需求进行额外的同步手法,进步了代码履行功率。

Lock体系

Lock简介与初识AQS

Java Concurrency API 中的 Lock 接口(Lock interface)是什么?对比同步它有什么优势?

Lock 接口比同步办法和同步块供给了更具扩展性的锁操作。他们答应更灵敏的结构,能够具有彻底不同的性质,而且能够支撑多个相关类的条件目标。

它的优势有:

(1) 能够使锁更公正

(2) 能够使线程在等候锁的时分响应中止

(3) 能够让线程测验获取锁,并在无法获取锁的时分当即回来或许等候一段时刻

(4) 能够在不同的范围,以不同的次第获取和开释锁

整体上来说 Lock 是 synchronized 的扩展版,Lock 供给了无条件的、可轮询的(tryLock 办法)、守时的(tryLock 带参办法)、可中止的(lockInterruptibly)、可多条件行列的(newCondition 办法)锁操作。别的 Lock 的完结类根本都支撑非公正锁(默许)和公正锁,synchronized 只支撑非公正锁,当然,在大部分状况下,非公正锁是高效的挑选。

达观锁和失望锁的了解及怎样完结,有哪些完结办法?

失望锁:总是假设 坏的状况,每次去拿数据的时分都以为他人会修正,所以每次在拿数据的时分都会上锁,这样他人想拿这个数据就会堵塞直到它拿到锁。传统的联系型数据库里边就用到了许多这种锁机制,比方行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比方 Java 里边的同步原语 synchronized 关键字的完结也是失望锁。

达观锁:望文生义,便是很达观,每次去拿数据的时分都以为他人不会修正,所以不会上锁,可是在更新的时分会判别一下在此期间他人有没有去更新这个数据,能够运用版别号等机制。达观锁适用于多读的运用类型,这样能够进步吞吐量,像数据库供给的类似于 write_condition 机制,其实都是供给的达观锁。在 Java中 java.util.concurrent.atomic 包下面的原子变量类便是运用了达观锁的一种完结办法 CAS 完结的。

达观锁的完结办法:

1、运用版别标识来确定读到的数据与提交时的数据是否共同。提交后修正版别标识,不共一起能够采纳丢掉和再次测验的战略。

2、 java 中的 Compare and Swap 即 CAS ,当多个线程测验运用 CAS 一同更新同一个变量时,只需其间一个线程能更新变量的值,而其它线程都失利,失利的线程并不会被挂起,而是被奉告这次竞赛中失利,并能够再次测验。 CAS 操作中包括三个操作数 —— 需求读写的内存方位(V)、进行比较的预期原值(A)和拟写入的新值(B)。假设内存方位 V 的值与预期原值 A 相匹配,那么处理器会主动将该方位值更新为新值 B。不然处理器不做任何操作。

什么是 CAS

CAS 是 compare and swap 的缩写,即咱们所说的比较交流。

CAS 是一种依据锁的操作,而且是达观锁。在 java 中锁分为达观锁和失望锁。

失望锁是将资源锁住,等一个之前取得锁的线程开释锁之后,下一个线程才干够拜访。而达观锁采纳了一种宽泛的态度,经过某种办法不加锁来处理资源,比方经过给记载加 version 来获取数据,功用较失望锁有很大的进步。

CAS 操作包括三个操作数 —— 内存方位(V)、预期原值(A)和新值(B)。假设内存地址里边的值和 A 的值是相同的,那么就将内存里边的值更新成 B。

CAS是经过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里边的值被b 线程修正了,那么 a 线程需求自旋,到下次循环才有或许时机履行。

java.util.concurrent.atomic 包下的类大多是运用 CAS 操作来完结的

(AtomicInteger,AtomicBoolean,AtomicLong)。

CAS 的会发生什么问题?

1、ABA 问题:比方说一个线程 one 从内存方位 V 中取出 A,这时分另一个线程 two 也从内存中取出 A,而且 two 进行了一些操作变成了 B,然后 two 又将 V 方位的数据变成 A,这时分线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。虽然线程 one 的 CAS 操作成功,但或许存在潜藏的问题。从 Java1.5 开端 JDK 的 atomic包里供给了一个类 AtomicStampedReference 来处理 ABA 问题。

2、循环时刻长开支大:

关于资源竞赛严峻(线程抵触严峻)的状况,CAS 自旋的概率会比较大,然后浪费更多的 CPU 资源,功率低于 synchronized。

3、只能确保一个同享变量的原子操作:当对一个同享变量履行操作时,咱们能够运用循环 CAS 的办法来确保原子操作,可是对多个同享变量操作时,循环 CAS 就无法确保操作的原子性,这个时分就能够用锁。

什么是死锁?

当线程 A 持有独占锁a,并测验去获取独占锁 b 的一同,线程 B 持有独占锁 b,并测验获取独占锁 a 的状况下,就会发生 AB 两个线程由于相互持有对方需求的锁,而发生的堵塞现象,咱们称为死锁。

发生死锁的条件是什么?怎样防止死锁?

发生死锁的必要条件:

1、 互斥条件:所谓互斥便是进程在某一时刻内独占资源。

2、 恳求与坚持条件:一个进程因恳求资源而堵塞时,对已取得的资源坚持不放。

3、 不掠夺条件:进程已取得资源,在末运用完之前,不能强行掠夺。

4、 循环等候条件:若干进程之间构成一种头尾相接的循环等候资源联系。

这四个条件是死锁的必要条件,只需体系发生死锁,这些条件必定成立,而只需上述条件之 一不满意,就不会发生死锁。

了解了死锁的原因,尤其是发生死锁的四个必要条件,就能够最大或许地防止、预防和免除死锁。

防止死锁能够选用以下的办法:

  • 尽量运用 tryLock(long timeout, TimeUnit unit)的办法(ReentrantLock、ReentrantReadWriteLock),设置超时时刻,超时能够退出防止死锁。
  • 尽量运用 Java.util.concurrent 并发类代替自己手写锁。尽量下降锁的运用粒度,尽量不要几个功用用同一把锁。
  • 尽量削减同步的代码块。

死锁与活锁的差异,死锁与饥饿的差异?

死锁:是指两个或两个以上的进程(或线程)在履行进程中,因争夺资源而构成的一种相互等候的现象,若无外力效果,它们都将无法推进下去。

活锁:使命或许履行者没有被堵塞,由于某些条件没有满意,导致一向重复测验,失利,测验,失利。

活锁和死锁的差异在于,处于活锁的实体是在不断的改动状况,这便是所谓的“活”, 而处于死锁的实体表现为等候;活锁有或许自行解开,死锁则不能。

饥饿:一个或许多个线程由于种种原因无法取得所需求的资源,导致一向无法履行的状况。

Java 中导致饥饿的原因:

1、高优先级线程吞噬一切的低优先级线程的 CPU 时刻。

2、线程被永久堵塞在一个等候进入同步块的状况,由于其他线程总是能在它之前持续地对该同步块进行拜访。

3、线程在等候一个本身也处于永久等候完结的目标(比方调用这个目标的 wait 办法),由于其他线程总是被持续地取得唤醒。

多线程锁的晋级原理是什么?

在Java中,锁共有4种状况,等级从低到高依次为:无状况锁,倾向锁,轻量级锁和重量级锁状况,这几个状况会跟着竞赛状况逐步晋级。锁能够晋级但不能降

AQS(AbstractQueuedSynchronizer)详解与源码剖析

AQS 介绍

AQS的全称为(AbstractQueuedSynchronizer),这个类在 java.util.concurrent.locks包下面。

八股文系列:Java的并发编程

AQS是一个用来构建锁和同步器的结构,运用AQS能简略且高效地结构出运用广泛的许多的同步器,比方咱们说到的ReentrantLock,Semaphore,其他的比如ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是依据AQS的。当然,咱们自己也能运用AQS十分轻松容易地结构出契合咱们自己需求的同步器。

AQS 原理剖析

下面大部分内容其实在AQS类注释上现已给出了,不过是英语看着比较费劲一点,感兴趣的话能够看看源码。

AQS 原理概览

AQS中心思维是,假设被恳求的同享资源闲暇,则将当时恳求资源的线程设置为有用的作业线程,而且将同享资源设置为锁定状况。假设被恳求的同享资源被占用,那么就需求一套线程堵塞等候以及被唤醒时锁分配的机制,这个机制AQS是用CLH行列锁完结的,行将暂时获取不到锁的线程加入到行列中。

CLH(Craig,Landin,and Hagersten)行列是一个虚拟的双向行列(虚拟的双向行列即不存在行列实例,仅存在结点之间的相相联系)。AQS是将每条恳求同享资源的线程封装成一个CLH锁行列的一个结点(Node)来完结锁的分配。

看个AQS(AbstractQueuedSynchronizer)原理图:

八股文系列:Java的并发编程

AQS运用一个int成员变量来标明同步状况,经过内置的FIFO行列来完结获取资源线程的排队作业。AQS运用CAS对该同步状况进行原子操作完结对其值的修正。

private volatile int state;//同享变量,运用volatile润饰确保线程可见性

状况信息经过protected类型的getState,setState,compareAndSetState进行操作

//回来同步状况的当时值
protected final int getState() {
	return state;
}
// 设置同步状况的值
protected final void setState(int newState) {
	state = newState;
}
//原子地(CAS操作)将同步状况值设置为给定值update假设当时同步状况的值等于expect (期望值)
protected final boolean compareAndSetState(int expect, int update) { 
      return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

AQS 对资源的同享办法

AQS界说两种资源同享办法

  • xclusive(独占):只需一个线程能履行,如ReentrantLock。又可分为公正锁和非公正锁:

    公正锁:依照线程在行列中的排队次第,先到者先拿到锁

    非公正锁:当线程要获取锁时,无视行列次第直接去抢锁,谁抢到便是谁的

  • Share(同享):多个线程可一同履行,如Semaphore/CountDownLatch。Semaphore、CountDownLatch、 CyclicBarrier、ReadWriteLock 咱们都会在后边讲到。ReentrantReadWriteLock 能够看成是组合式,由于ReentrantReadWriteLock也便是读写锁答应多个线程一同对某一资源进行读。不同的自界说同步器争用同享资源的办法也不同。自界说同步器在完结时只需求完结同享资源 state 的获取与开释办法即可,至于具体线程等候行列的保护(如获取资源失利入队/唤醒出队等),AQS现已在顶层完结好了。

AQS底层运用了模板办法形式,同步器的规划是依据模板办法形式的,假设需求自界说同步器一般的办法是这样(模板办法形式很经典的一个运用):

  1. 运用者承继AbstractQueuedSynchronizer并重写指定的办法。(这些重写办法很简略,无非是关于同享资源state的获取和开释)
  2. 将AQS组合在自界说同步组件的完结中,并调用其模板办法,而这些模板办法会调用运用者重写的办法。

这和咱们以往经过完结接口的办法有很大差异,这是模板办法形式很经典的一个运用。

AQS运用了模板办法形式,自界说同步器时需求重写下面几个AQS供给的模板办法:

isHeldExclusively()//该线程是否正在独占资源。只需用到condition才需求去完结它。
tryAcquire(int)//独占办法。测验获取资源,成功则回来true,失利则回来false。
tryRelease(int)//独占办法。测验开释资源,成功则回来true,失利则回来false。
tryAcquireShared(int)//同享办法。测验获取资源。负数标明失利;0标明成功,但没有剩下可用资源;正数标明成功,且有剩下资源。
tryReleaseShared(int)//同享办法。测验开释资源,成功则回来true,失利则回来false。

默许状况下,每个办法都抛出 UnsupportedOperationException。 这些办法的完结有必要是内部线程安全的,而且一般应该简略而不是堵塞。AQS类中的其他办法都是final ,所以无法被其他类运用,只需这几个办法能够被其他类运用。

以ReentrantLock为例: state初始化为0,标明未锁定状况。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。尔后,其他线程再tryAcquire()时就会失利,直到A线程unlock()到state=0(即开释锁)停止,其它线程才有时机获取该锁。当然,开释锁之前,A线程自己是能够重复获取此锁的(state会累加),这便是可重入的概念。但要留意,获取多少次就要开释多么次,这样才干确保state是能回到零态的。

以CountDownLatch以例: 使命分为N个子线程去履行,state也初始化为N(留意N要与线程个数共同)。这N个子线程是并行履行的,每个子线程履行完后countDown()一次,state会CAS(Compare and Swap)减1。比及一切子线程都履行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从 await()函数回来,持续后余动作。

一般来说,自界说同步器要么是独占办法,要么是同享办法,他们也只需完结 tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS 也支撑自界说同步器一同完结独占和同享两种办法,如ReentrantReadWriteLock。

ReentrantLock(重入锁)完结原理与公正锁非公正锁差异什么?

ReentrantLock重入锁,是完结Lock接口的一个类,也是在实践编程中运用频率很高的一个锁,支撑重入性,标明能够对同享资源能够重复加锁,即当时线程获取该锁再次获取不会被堵塞。

在java关键字synchronized隐式支撑重入性,synchronized经过获取自增,开释自减的办法完结重入。与此一同,ReentrantLock还支撑公正锁和非公正锁两种办法。那么,要想完彻底全的弄懂ReentrantLock的话,首要也便是ReentrantLock同步语义的学习:1. 重入性的完结原理;2. 公正锁和非公正锁。

重入性的完结原理要想支撑重入性,就要处理两个问题:1. 在线程获取锁的时分,假设现已获取锁的线程是当时线程的话则直接再次获取成功;2. 由于锁会被获取n次,那么只需锁在被开释同样的n次之后,该锁才算是彻底开释成功。

ReentrantLock支撑两种锁:公正锁和非公正锁。何谓公正性,是针对获取锁而言的,假设一个锁是公正的,那么锁的获取次第就应该契合恳求上的肯定时刻次第,满意FIFO。

读写锁ReentrantReadWriteLock源码剖析

ReadWriteLock 是什么

首要清晰一下,不是说 ReentrantLock 不好,仅仅 ReentrantLock 某些时分有约束。假设运用 ReentrantLock,或许本身是为了防止线程 A 在写数据、线程 B 在读数据构成的数据不共同,但这样,假设线程 C 在读数据、线程 D 也在读数据,读数据是不会改动数据的,没有必要加锁,可是仍是加锁了,下降了程序的功用。由于这个,才诞生了读写锁 ReadWriteLock。

ReadWriteLock 是一个读写锁接口,读写锁是用来进步并发程序功用的锁别离技能,ReentrantReadWriteLock 是 ReadWriteLock 接口的一个具体完结,完结了读写的别离,读锁是同享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,进步了读写的功用。

而读写锁有以下三个重要的特性:

(1)公正挑选性:支撑非公正(默许)和公正的锁获取办法,吞吐量仍对错公正优于公正。

(2)重进入:读锁和写锁都支撑线程重进入。

(3)锁降级:遵循获取写锁、获取读锁再开释写锁的次第,写锁能够降级成为读锁。

Condition源码剖析与等候告诉机制 LockSupport详解

并发容器之ConcurrentHashMap详解(JDK1.8版别)与源码剖析

什么是ConcurrentHashMap?

ConcurrentHashMap是Java中的一个线程安全且高效的HashMap完结。平常触及高并发假设要用map结构,那第一时刻想到的便是它。相关于hashmap来说,ConcurrentHashMap便是线程安全的map,其间运用了锁分段的思维进步了并发度。

那么它到底是怎样完结线程安全的?

JDK 1.6版别关键要素:

  • segment承继了ReentrantLock充当锁的角色,为每一个segment供给了线程安全的确保;
  • segment保护了哈希散列表的若干个桶,每个桶由HashEntry构成的链表。

JDK1.8后,ConcurrentHashMap抛弃了原有的Segment 分段锁,而选用了 CAS + synchronized 来确保并发安全性。

Java 中 ConcurrentHashMap 的并发度是什么?

ConcurrentHashMap 把实践 map 划分红若干部分来完结它的可扩展性和线程安全。这种划分是运用并发度取得的,它是 ConcurrentHashMap 类结构函数的一个可选参数,默许值为 16,这样在多线程状况下就能防止争用。

在 JDK8 后,它摒弃了 Segment(锁段)的概念,而是启用了一种全新的办法完结,运用 CAS 算法。一同加入了更多的辅助变量来进步并发度,具体内容仍是检查源码吧。

什么是并发容器的完结?

何为同步容器:能够简略地了解为经过 synchronized 来完结同步的容器,假设有多个线程调用同步容器的办法,它们将会串行履行。比方 Vector,Hashtable,以及 Collections.synchronizedSet,synchronizedList 等办法回来的容器。能够经过检查 Vector,Hashtable 等这些同步容器的完结代码,能够看到这些容器完结线程安全的办法便是将它们的状况封装起来,并在需求同步的办法上加上关键字 synchronized。

并发容器运用了与同步容器彻底不同的加锁战略来供给更高的并发性和弹性性,例如在 ConcurrentHashMap 中选用了一种粒度更细的加锁机制,能够称为分段锁,在这种锁机制下,答应恣意数量的读线程并发地拜访 map,而且履行读操作的线程和写操作的线程也能够并发的拜访 map,一同答应必定数量的写操作线程并发地修正 map,所以它能够在并发环境下完结更高的吞吐量。

Java 中的同步调集与并发调集有什么差异?

同步调集与并发调集都为多线程和并发供给了适宜的线程安全的调集,不过并发调集的可扩展性更高。在 Java1.5 之前程序员们只需同步调集来用且在多线程并发的时分会导致争用,阻止了体系的扩展性。Java5 介绍了并发调集像

ConcurrentHashMap,不仅供给线程安全还用锁别离和内部分区等现代技能进步了可扩展性。

SynchronizedMap 和 ConcurrentHashMap 有什么差异?

SynchronizedMap 一次锁住整张表来确保线程安全,所以每次只能有一个线程来访为 map。

ConcurrentHashMap 运用分段锁来确保在多线程下的功用。

ConcurrentHashMap 中则是一次锁住一个桶。ConcurrentHashMap 默许将 hash 表分为 16 个桶,比如 get,put,remove 等常用操作只锁当时需求用到的桶。

这样,本来只能一个线程进入,现在却能一同有 16 个写线程履行,并发功用的进步是显而易见的。

别的 ConcurrentHashMap 运用了一种不同的迭代办法。在这种迭代办法中,当iterator 被创立后调集再发生改动就不再是抛出ConcurrentModificationException,取而代之的是在改动时 new 新的数据然后不影响原有的数据,iterator 完结后再将头指针替换为新的数据 ,这样 iterator线程能够运用本来老的数据,而写线程也能够并发的完结改动。

并发容器之CopyOnWriteArrayList详解

CopyOnWriteArrayList 是什么,能够用于什么运用场景?有哪些优缺陷?

CopyOnWriteArrayList 是一个并发容器。有许多人称它是线程安全的,我以为这句话不严谨,缺少一个前提条件,那便对错复合场景下操作它是线程安全的。

CopyOnWriteArrayList(免锁容器)的长处之一是当多个迭代器一同遍历和修正这个列表时,不会抛出 ConcurrentModificationException。在CopyOnWriteArrayList 中,写入将导致创立整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修正时,读取操作能够安全地履行。

CopyOnWriteArrayList 的运用场景经过源码剖析,咱们看出它的优缺陷比较显着,所以运用场景也就比较显着。便是适宜读多写少的场景。

CopyOnWriteArrayList 的缺陷

  1. 由于写操作的时分,需求复制数组,会消耗内存,假设原数组的内容比较多的状况下,或许导致 young gc 或许 full gc。
  2. 不能用于实时读的场景,像复制数组、新增元素都需求时刻,所以调用一个 set 操作后,读取到数据或许仍是旧的,虽然CopyOnWriteArrayList 能做到终究共同性,可是仍是无法满意实时性要求。
  3. 由于实践运用中或许无法确保 CopyOnWriteArrayList 到底要放置多少数据,假设数据略微有点多,每次 add/set 都要重新复制数组,这个代价实在太昂扬了。在高功用的互联网运用中,这种操作分分钟引起故障。

CopyOnWriteArrayList 的规划思维

  1. 读写别离,读和写分隔
  2. 终究共同性
  3. 运用别的拓荒空间的思路,来处理并发抵触并发容器之ThreadLocal详解

ThreadLocal 是什么?有哪些运用场景?

ThreadLocal 是一个本地线程副本变量东西类,在每个线程中都创立了一个ThreadLocalMap 目标,简略说 ThreadLocal 便是一种以空间换时刻的做法,每个线程能够拜访自己内部 ThreadLocalMap 目标内的 value。经过这种办法,防止资源在多线程间同享。

原理:线程局部变量是约束于线程内部的变量,归于线程本身一切,不在多个线程间同享。Java供给ThreadLocal类来支撑线程局部变量,是一种完结线程安全的办法。可是在办理环境下(如 web 服务器)运用线程局部变量的时分要特别当心,在这种状况下,作业线程的生命周期比任何运用变量的生命周期都要长。

任何线程局部变量一旦在作业完结后没有开释,Java 运用就存在内存走漏的危险。

经典的运用场景是为每个线程分配一个 JDBC 衔接 Connection。这样就能够确保每个线程的都在各自的 Connection 上进行数据库的操作,不会呈现 A 线程关了 B线程正在运用的 Connection; 还有 Session 办理 等问题。 ThreadLocal 运用比方:

public class TestThreadLocal {
//线程本地存储变量
   private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new ThreadLocal<Integer>(){
   @Override
   protected Integer initialValue() {
      return 0;
   }
};
  public static void main(String[] args) {
    //发动三个线程
     for (int i = 0; i <3; i++) {
     Thread t = new Thread() {
        @Override
        public void run() {
           add10ByThreadLocal();
        }
    };
       t.start();
   }
}
/**
* 线程本地存储变量加 
*/ 
  	private static void add10ByThreadLocal() {
    	for (int i = 0; i <5; i++) {
    	Integer n = THREAD_LOCAL_NUM.get();
    	n += 1;
    	THREAD_LOCAL_NUM.set(n);
    	System.out.println(Thread.currentThread().getName() + " : ThreadLocal n um=" + n);
    	}
    }
}

打印成果:发动了 3 个线程,每个线程 后都打印到 “ThreadLocal num=5”,而不是 num 一向在累加直到值等于 15

Thread‐0 : ThreadLocal num=1
Thread‐1 : ThreadLocal num=1
Thread‐0 : ThreadLocal num=2
Thread‐0 : ThreadLocal num=3
Thread‐1 : ThreadLocal num=2
Thread‐2 : ThreadLocal num=1
Thread‐0 : ThreadLocal num=4
Thread‐2 : ThreadLocal num=2
Thread‐1 : ThreadLocal num=3
Thread‐1 : ThreadLocal num=4
Thread‐2 : ThreadLocal num=3
Thread‐0 : ThreadLocal num=5
Thread‐2 : ThreadLocal num=4
Thread‐2 : ThreadLocal num=5
Thread‐1 : ThreadLocal num=5

什么是线程局部变量?

线程局部变量是约束于线程内部的变量,归于线程本身一切,不在多个线程间同享。Java 供给 ThreadLocal 类来支撑线程局部变量,是一种完结线程安全的办法。可是在办理环境下(如 web 服务器)运用线程局部变量的时分要特别当心,在这种状况下,作业线程的生命周期比任何运用变量的生命周期都要长。任何线程局部变量一旦在作业完结后没有开释,Java 运用就存在内存走漏的危险。

ThreadLocal内存走漏剖析与处理方案

ThreadLocal构成内存走漏的原因?

ThreadLocalMap 中运用的 key 为 ThreadLocal 的弱引证,而 value 是强引证。所以,假设 ThreadLocal 没有被外部强引证的状况下,在废物收回的时分,key 会被整理掉,而 value 不会被整理掉。这样一来,ThreadLocalMap 中就会呈现key 为null的Entry。假设咱们不做任何措施的话,value 永久无法被GC 收回,这个时分就或许会发生内存走漏。ThreadLocalMap完结中现已考虑了这种状况,在调用 set()、get()、remove() 办法的时分,会整理掉 key 为 null 的记载。运用完 ThreadLocal办法后 好手动调用remove()办法

ThreadLocal内存走漏处理方案?

  • 每次运用完ThreadLocal,都调用它的remove()办法,清除数据。
  • 在运用线程池的状况下,没有及时整理ThreadLocal,不仅是内存走漏的问题,更严峻的是或许导致事务逻辑呈现问题。所以,运用ThreadLocal就跟加锁完要解锁相同,用完就整理。

并发容器之BlockingQueue详解

什么是堵塞行列?堵塞行列的完结原理是什么?怎样运用堵塞行列来完结生产者-顾客模型?

堵塞行列(BlockingQueue)是一个支撑两个附加操作的行列。

这两个附加的操作是:在行列为空时,获取元素的线程会等候行列变为非空。当行列满时,存储元素的线程会等候行列可用。

堵塞行列常用于生产者和顾客的场景,生产者是往行列里添加元素的线程,顾客是从行列里拿元素的线程。堵塞行列便是生产者寄存元素的容器,而顾客也只从容器里拿元素。

JDK7 供给了 7 个堵塞行列。分别是:

ArrayBlockingQueue :一个由数组结构组成的有界堵塞行列。

LinkedBlockingQueue :一个由链表结构组成的有界堵塞行列。

PriorityBlockingQueue :一个支撑优先级排序的无界堵塞行列。

DelayQueue:一个运用优先级行列完结的无界堵塞行列。

SynchronousQueue:一个不存储元素的堵塞行列。

LinkedTransferQueue:一个由链表结构组成的无界堵塞行列。

LinkedBlockingDeque:一个由链表结构组成的双向堵塞行列。

Java 5 之前完结同步存取时,能够运用一般的一个调集,然后在运用线程的协作和线程同步能够完结生产者,顾客形式,首要的技能便是用好wait,notify,notifyAll,sychronized 这些关键字。而在 java 5 之后,能够运用堵塞行列来完结,此办法大大简少了代码量,使得多线程编程愈加容易,安全方面也有确保。

BlockingQueue 接口是 Queue 的子接口,它的首要用途并不是作为容器,而是作为线程同步的的东西,因而他具有一个很显着的特性,当生产者线程企图向 BlockingQueue 放入元素时,假设行列已满,则线程被堵塞,当顾客线程企图从中取出一个元素时,假设行列为空,则该线程会被堵塞,正是由于它所具有这个特性,所以在程序中多个线程交替向 BlockingQueue 中放入元素,取出元素,它能够很好的操控线程之间的通讯。

堵塞行列运用最经典的场景便是socket 客户端数据的读取和解析,读取数据的线程不断将数据放入行列,然后解析线程不断从行列取数据解析。

什么是线程池?有哪几种创立办法?

池化技能比较咱们现已层出不穷了,线程池、数据库衔接池、Http 衔接池等等都是对这个思维的运用。池化技能的思维首要是为了削减每次获取资源的消耗,进步对资源的运用率。

在面向目标编程中,创立和毁掉目标是很费时刻的,由于创立一个目标要获取内存资源或许其它更多资源。在 Java 中更是如此,虚拟机将企图盯梢每一个目标,以便能够在目标毁掉后进行废物收回。所以进步服务程序功率的一个手法便是尽或许削减创立和毁掉目标的次数,特别是一些很耗资源的目标创立和毁掉,这便是”池化资源”技能发生的原因。

线程池望文生义便是事前创立若干个可履行的线程放入一个池(容器)中,需求的时分从池中获取线程不必自行创立,运用结束不需求毁掉线程而是放回池中,然后削减创立和毁掉线程目标的开支。Java 5+中的 Executor 接口界说一个履行线程的东西。它的子类型即线程池接口是 ExecutorService。要装备一个线程池是比较杂乱的,尤其是关于线程池的原理不是很清楚的状况下,因而在东西类Executors上面供给了一些静态工厂办法,生成一些常用的线程池,如下所示:

(1) newSingleThreadExecutor:创立一个单线程的线程池。这个线程池只需一个线程在作业,也便是适当于单线程串行履行一切使命。假设这个仅有的线程由于反常结束,那么会有一个新的线程来代替它。此线程池确保一切使命的履行次第依照使命的提交次第履行。

(2) newFixedThreadPool:创立固定巨细的线程池。每次提交一个使命就创立一个线程,直到线程抵达线程池的最大巨细。线程池的巨细一旦抵达最大值就会坚持不变,假设某个线程由于履行反常而结束,那么线程池会弥补一个新线程。假设期望在服务器上运用线程池,主张运用 newFixedThreadPool办法来创立线程池,这样能取得更好的功用。

(3) newCachedThreadPool:创立一个可缓存的线程池。假设线程池的巨细超越了处理使命所需求的线程,那么就会收回部分闲暇(60 秒不履行使命)的线程,当使命数添加时,此线程池又能够智能的添加新线程来处理使命。此线程池不会对线程池巨细做约束,线程池巨细彻底依赖于操作体系(或许说 JVM)能够创立的最大线程巨细。

(4) newScheduledThreadPool:创立一个巨细无限的线程池。此线程池支撑守时以及周期性履行使命的需求。

线程池有什么长处?

  • 下降资源消耗:重用存在的线程,削减目标创立毁掉的开支。
  • 进步响应速度。可有用的操控最大并发线程数,进步体系资源的运用率,一同防止过多资源竞赛,防止堵塞。当使命抵达时,使命能够不需求的比及线程创立就能当即履行。
  • 进步线程的可办理性。线程是稀缺资源,假设无约束的创立,不仅会消耗体系资源,还会下降体系的稳定性,运用线程池能够进行一致的分配,调优和监控。
  • 附加功用:供给守时履行、定时履行、单线程、并发数操控等功用。综上所述运用线程池结构 Executor 能更好的办理线程、供给体系资源运用率。

线程池都有哪些状况?

  • RUNNING:这是最正常的状况,接受新的使命,处理等候行列中的使命。
  • SHUTDOWN:不接受新的使命提交,可是会持续处理等候行列中的使命。
  • STOP:不接受新的使命提交,不再处理等候行列中的使命,中止正在履行使命的线程。
  • TIDYING:一切的使命都毁掉了,workCount 为 0,线程池的状况在转化为 TIDYING 状况时,会履行钩子办法 terminated()。
  • TERMINATED:terminated()办法结束后,线程池的状况就会变成这个。

什么是 Executor 结构?为什么运用 Executor 结构?

Executor 结构是一个依据一组履行战略调用,调度,履行和操控的异步使命的结构。

每次履行使命创立线程 new Thread()比较消耗功用,创立一个线程是比较耗时、耗资源的,而且无约束的创立线程会引起运用程序内存溢出。

所以创立一个线程池是个更好的的处理方案,由于能够约束线程的数量而且能够收回再运用这些线程。运用Executors 结构能够十分便利的创立一个线程池。

在 Java 中 Executor 和 Executors 的差异?

  • Executors 东西类的不同办法依照咱们的需求创立了不同的线程池,来满意事务的需求。
  • Executor 接口目标能履行咱们的线程使命。
  • ExecutorService 接口承继了 Executor 接口并进行了扩展,供给了更多的办法咱们能取得使命履行的状况而且能够获取使命的回来值。
  • 运用 ThreadPoolExecutor 能够创立自界说线程池。
  • Future 标明异步核算的成果,他供给了检查核算是否完结的办法,以等候核算的完结,并能够运用 get()办法获取核算的成果。

线程池中 submit() 和 execute() 办法有什么差异?

接纳参数:execute()只能履行 Runnable 类型的使命。submit()能够履行 Runnable 和 Callable 类型的使命。

回来值:submit()办法能够回来持有核算成果的 Future 目标,而execute()没有反常处理:submit()便利Exception处理

什么是线程组,为什么在 Java 中不引荐运用?

ThreadGroup 类,能够把线程归属到某一个线程组中,线程组中能够有线程目标,也能够有线程组,组中还能够有线程,这样的安排结构有点类似于树的办法。

线程组和线程池是两个不同的概念,他们的效果彻底不同,前者是为了便利线程的办理,后者是为了办理线程的生命周期,复用线程,削减创立毁掉线程的开支。为什么不引荐运用线程组?由于运用有许多的安全隐患吧,没有具体追究,假设需求运用,引荐运用线程池。

线程池之ThreadPoolExecutor详解

Executors和ThreaPoolExecutor创立线程池的差异

《阿里巴巴Java开发手册》中强制线程池不答应运用 Executors 去创立,而是经过 ThreadPoolExecutor 的办法,这样的处理办法让写的同学愈加清晰线程池的运转规矩,规避资源耗尽的危险

Executors 各个办法的坏处:

  • newFixedThreadPool 和 newSingleThreadExecutor: 首要问题是堆积的恳求处理行列或许会消耗十分大的内存,乃至 OOM。
  • newCachedThreadPool 和 newScheduledThreadPool: 首要问题是线程数最大数是 Integer.MAX_VALUE,或许会创立数量十分多的线程,乃至 OOM。

ThreaPoolExecutor创立线程池办法只需一种,便是走它的结构函数,参数自己指定

你知道怎样创立线程池吗?

创立线程池的办法有多种,这儿你只需求答 ThreadPoolExecutor 即可。

ThreadPoolExecutor() 是最原始的线程池创立,也是阿里巴巴 Java 开发手册中清晰标准的创立线程池的办法。

ThreadPoolExecutor结构函数重要参数剖析

八股文系列:Java的并发编程

ThreadPoolExecutor3 个最重要的参数:

corePoolSize :中心线程数,线程数界说了最小能够一同运转的线程数量。

maximumPoolSize :线程池中答应存在的作业线程的最大数量

workQueue:当新使命来的时分会先判别当时运转的线程数量是否抵达中心线程数,假设抵达的话,使命就会被寄存在行列中。

ThreadPoolExecutor其他常见参数:

  1. keepAliveTime:线程池中的线程数量大于 corePoolSize 的时分,假设这时没有新的使命提交,中心线程外的线程不会当即毁掉,而是会等候,直到等候的时刻超越了 keepAliveTime才会被收回毁掉;
  2. unit :keepAliveTime 参数的时刻单位。
  3. threadFactory:为线程池供给创立新线程的线程工厂
  4. handler :线程池使命行列超越 maxinumPoolSize 之后的回绝战略 ThreadPoolExecutor饱和战略

ThreadPoolExecutor饱和战略界说:

假设当时一同运转的线程数量抵达最大线程数量而且行列也现已被放满了任时,ThreadPoolTaskExecutor 界说一些战略:

  • ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来回绝新使命的处理。
  • ThreadPoolExecutor.CallerRunsPolicy:调用履行自己的线程运转使命。可是这种战略会下降关于新使命提交速度,影响程序的整体功用。别的,这个战略喜爱添加行列容量。假设您的运用程序能够承受此推迟而且你不能使命丢掉任何一个使命恳求的话,你能够挑选这个战略。
  • ThreadPoolExecutor.DiscardPolicy:不处理新使命,直接丢掉掉。
  • ThreadPoolExecutor.DiscardOldestPolicy: 此战略将丢掉最早的未处理的使命恳求。

举个比方:

Spring 经过 ThreadPoolTaskExecutor 或许咱们直接经过 ThreadPoolExecutor 的结构函数创立线程池的时分,当咱们不指定 RejectedExecutionHandler 饱和战略的话来装备线程池的时分默许运用的是 ThreadPoolExecutor.AbortPolicy。在默许状况下,ThreadPoolExecutor 将抛出 RejectedExecutionException 来回绝新来的使命 ,这代表你将丢掉对这个使命的处理。 关于可弹性的运用程序,主张运用 ThreadPoolExecutor.CallerRunsPolicy。当最大池被填满时,此战略为咱们供给可弹性行列。(这个直接检查 ThreadPoolExecutor 的结构函数源码就能够看出,比较简略的原因,这儿就不贴代码了)

一个简略的线程池Demo:Runnable+ThreadPoolExecutor 线程池完结原理

为了让咱们更清楚上面的面试题中的一些概念,我写了一个简略的线程池 Demo。

首要创立一个 Runnable 接口的完结类(当然也能够是 Callable 接口,咱们上面也说了两者的差异。)

 importjava.util.Date;
/** 
 *这是一个简略的Runnable类,需求大约5秒钟来履行其使命。
 */
 publicclassMyRunnableimplementsRunnable{
 privateStringcommand;
 publicMyRunnable(Strings){ 
    this.command=s;
 }
 @Override 
 publicvoidrun(){ 
    System.out.println(Thread.currentThread().getName()+"Start.Time=" +newDate()); 
    processCommand();
    System.out.println(Thread.currentThread().getName()+"End.Time="+ newDate()); 
 }
 privatevoidprocessCommand(){
    try{
       Thread.sleep(5000);
       }catch(InterruptedExceptione){
          e.printStackTrace(); 
       } 
    }
 @Override
 publicStringtoString(){ 
    returnthis.command;
 }
} 

编写测验程序,咱们这儿以阿里巴巴引荐的运用 ThreadPoolExecutor 结构函数自界说参数的办法来创立线程池。

 importjava.util.concurrent.ArrayBlockingQueue;
 importjava.util.concurrent.ThreadPoolExecutor;
 importjava.util.concurrent.TimeUnit;
 publicclassThreadPoolExecutorDemo{
 privatestaticfinalintCORE_POOL_SIZE=5;
 privatestaticfinalintMAX_POOL_SIZE=10; 
 privatestaticfinalintQUEUE_CAPACITY=100;
 privatestaticfinalLongKEEP_ALIVE_TIME=1L; 
 publicstaticvoidmain(String[]args){
 //运用阿里巴巴引荐的创立线程池的办法
 //经过ThreadPoolExecutor结构函数自界说参数创立 
 ThreadPoolExecutorexecutor=newThreadPoolExecutor( 
    CORE_POOL_SIZE, 
    MAX_POOL_SIZE, 
    KEEP_ALIVE_TIME, 
    TimeUnit.SECONDS,
    newArrayBlockingQueue<>(QUEUE_CAPACITY), 
    newThreadPoolExecutor.CallerRunsPolicy());
 for(inti=0;i<10;i++){ 
 //创立WorkerThread目标(WorkerThread类完结了Runnable接口)
   Runnableworker=newMyRunnable(""+i); 
 //履行Runnable 
   executor.execute(worker);
 } 
 //中止线程池 
 executor.shutdown(); 
 while(!executor.isTerminated()){ 
   } 
 System.out.println("Finishedallthreads");
 }
 } 

能够看到咱们上面的代码指定了:

  1. corePoolSize: 中心线程数为 5。
  2. maximumPoolSize :最大线程数 10
  3. keepAliveTime : 等候时刻为 1L。
  4. unit: 等候时刻的单位为 TimeUnit.SECONDS。
  5. workQueue:使命行列为 ArrayBlockingQueue,而且容量为 100;

handler:饱和战略为 CallerRunsPolicy。 Output:

pool‐1‐thread‐2 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool‐1‐thread‐5 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool‐1‐thread‐4 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool‐1‐thread‐1 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool‐1‐thread‐3 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool‐1‐thread‐5 End. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐3 End. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐2 End. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐4 End. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐1 End. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐2 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐1 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐4 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐3 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐5 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool‐1‐thread‐2 End. Time = Tue Nov 12 20:59:54 CST 2019
pool‐1‐thread‐3 End. Time = Tue Nov 12 20:59:54 CST 2019
pool‐1‐thread‐4 End. Time = Tue Nov 12 20:59:54 CST 2019
pool‐1‐thread‐5 End. Time = Tue Nov 12 20:59:54 CST 2019
pool‐1‐thread‐1 End. Time = Tue Nov 12 20:59:54 CST 2019

什么是原子操作?在 Java Concurrency API 中有哪些原子类 (atomic classes)?

原子操作(atomic operation)意为”不行被中止的一个或一系列操作” 。

处理器运用依据对缓存加锁或总线加锁的办法来完结多处理器之间的原子操作。

在 Java 中能够经过锁和循环 CAS 的办法来完结原子操作。 CAS 操作——Compare & Set,或是 Compare & Swap,现在几乎一切的 CPU 指令都支撑CAS 的原子操作。

原子操作是指一个不受其他操作影响的操作使命单元。原子操作是在多线程环境下防止数据不共同有必要的手法。

int++并不是一个原子操作,所以当一个线程读取它的值并加 1 时,别的一个线程有或许会读到之前的值,这就会引发过错。

为了处理这个问题,有必要确保添加操作是原子的,在 JDK1.5 之前咱们能够运用同步技能来做到这一点。到 JDK1.5,java.util.concurrent.atomic 包供给了 int 和long 类型的原子包装类,它们能够主动的确保关于他们的操作是原子的而且不需求运用同步。

java.util.concurrent 这个包里边供给了一组原子类。其根本的特性便是在多线程环境下,当有多个线程一同履行这些类的实例包括的办法时,具有排他性,即当某个线程进入办法,履行其间的指令时,不会被其他线程打断,而别的线程就像自旋锁相同,一向比及该办法履行完结,才由 JVM 从等候行列中挑选另一个线程进入,这仅仅一种逻辑上的了解。

原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

原子属性更新器:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater

处理 ABA 问题的原子类:AtomicMarkableReference(经过引进一个boolean来反映中间有没有变过),AtomicStampedReference(经过引进一个 int 来累加来反映中间有没有变过)

说一下 atomic 的原理?

Atomic包中的类根本的特性便是在多线程环境下,当有多个线程一同对单个(包括根本类型及引证类型)变量进行操作时,具有排他性,即当多个线程一同对该变量的值进行更新时,仅有一个线程能成功,而未成功的线程能够向自旋锁相同,持续测验,一向比及履行成功。

AtomicInteger 类的部分源码:

	// setup to use Unsafe.compareAndSwapInt for updates(更新操作时供给“比较并替换”的效果)
	private static final Unsafe unsafe = Unsafe.getUnsafe(); 
  private static final long valueOffset;
	static {
	   try {
	      valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
	  } catch (Exception ex)
        { 
          throw new Error(ex);
        }
	  }
 private volatile int value;

AtomicInteger 类首要运用 CAS (compare and swap) + volatile 和 native 办法来确保原子操作,然后防止 synchronized 的高开支,履行功率大为进步。 CAS的原理是拿期望的值和本来的一个值作比较,假设相同则更新成新的值。

UnSafe 类的 objectFieldOffset() 办法是一个本地办法,这个办法是用来拿到“本来的值”的内存地址,回来值是 valueOffset。别的 value 是一个volatile变量,在内存中可见,因而 JVM 能够确保任何时刻任何线程总能拿到该变量的最新值。

并发东西

并发东西之CountDownLatch与CyclicBarrier

在 Java 中 CycliBarriar 和 CountdownLatch 有什么差异?

CountDownLatch与CyclicBarrier都是用于操控并发的东西类,都能够了解成保护的便是一个计数器,可是这两者仍是各有不同侧重点的:

  • CountDownLatch一般用于某个线程A等候若干个其他线程履行完使命之后,它才履行;而CyclicBarrier一般用于一组线程相互等候至某个状况,然后这一组线程再一同履行;CountDownLatch着重一个线程等多个线程完结某件事情。CyclicBarrier是多个线程互等,等咱们都完结,再携手共进。
  • 调用CountDownLatch的countDown办法后,当时线程并不会堵塞,会持续往下履行;而调用CyclicBarrier的await办法,会堵塞当时线程,直到CyclicBarrier指定的线程悉数都抵达了指定点的时分,才干持续往下履行;
  • CountDownLatch办法比较少,操作比较简略,而CyclicBarrier供给的办法更多,比方能够经过getNumberWaiting(),isBroken()这些办法获取当时多个线程的状况,而且CyclicBarrier的结构办法能够传入 barrierAction,指定当一切线程都抵达时履行的事务功用;
  • CountDownLatch是不能复用的,而CyclicBarrier是能够复用的。

并发东西之Semaphore与Exchanger

Semaphore 有什么效果

Semaphore 便是一个信号量,它的效果是约束某段代码块的并发数。

Semaphore有一个结构函数,能够传入一个 int 型整数 n,标明某段代码最多只需 n 个线程能够拜访,假设超出了 n,那么请等候,比及某个线程履行结束这段代码块,下一个线程再进入。由此能够看出假设 Semaphore 结构函数中传入的 int 型整数 n=1,适当于变成了一个 synchronized 了。

Semaphore(信号量)-答应多个线程一同拜访: synchronized 和 ReentrantLock 都是一次只答应一个线程拜访某个资源,Semaphore(信号量) 能够指定多个线程一同拜访某个资源。

什么是线程间交流数据的东西Exchanger

Exchanger是一个用于线程间协作的东西类,用于两个线程间交流数据。它供给了一个交流的同步点,在这个同步点两个线程能够交流数据。交流数据是经过 exchange办法来完结的,假设一个线程先履行exchange办法,那么它会同步等候另一个线程也履行exchange办法,这个时分两个线程就都抵达了同步点,两个线程就能够交流数据。

常用的并发东西类有哪些?

  • Semaphore(信号量)-答应多个线程一同拜访: synchronized 和ReentrantLock 都是一次只答应一个线程拜访某个资源,Semaphore(信号量)能够指定多个线程一同拜访某个资源。
  • CountDownLatch(倒计时器): CountDownLatch是一个同步东西类,用来协调多个线程之间的同步。这个东西一般用来操控线程等候,它能够让某一个线程等候直到倒计时结束,再开端履行。
  • CyclicBarrier(循环栅栏): CyclicBarrier 和 CountDownLatch 十分类似,它也能够完结线程间的技能等候,可是它的功用比 CountDownLatch 愈加杂乱和强壮。首要运用场景和 CountDownLatch 类似。CyclicBarrier 的字面意思是可循环运用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程抵达一个屏障(也能够叫同步点)时被堵塞,直到最后一个线程抵达屏障时,屏障才会开门,一切被屏障阻拦的线程才会持续干活。CyclicBarrier默许的结构办法是 CyclicBarrier(int parties),其参数标明屏障阻拦的线程数量,每个线程调用await()办法告诉 CyclicBarrier 我现已抵达了屏障,然后当时线程被堵塞。

我是纪先生,用输出倒逼输入而持续学习,持续分享技能系列文章,以及全网值得保藏好文,欢迎重视大众号,做一个持续生长的技能人。

八股文系列历史文章

(也能够在专栏中看其他相关文章)

1. 八股文系列:Java根底知识

2. 八股文系列:Java调集容器

3. 八股文系列:Java中的反常和过错