多线程/并发

目录

什么是线程?

什么是线程安全和线程不安全?

什么是⾃旋锁? 

什么是CAS? 

什么是乐观锁和悲观锁?

什么是AQS?

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

2.线程和进程的区别是什么?

Java 实现线程有哪几种方式?

启动线程方法 start()和 run()有什么区别?

怎么终止一个线程?如何优雅地终止线程?

一个线程的生命周期有哪几种状态?它们之间如何流转的?

线程中的 wait()和 sleep()方法有什么区别?

多线程同步有哪几种方法?

什么是死锁?如何避免死锁?

多线程之间如何进行通信?

线程怎样拿到返回结果?

violatile 关键字的作用?

新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

怎么控制同一时间只有 3 个线程运行?

为什么要使用线程池?

常用的几种线程池并讲讲其中的工作原理。

什么是线程池?

线程池的好处

线程池核心类

submit 和 execute 分别有什么区别呢?

如何关闭线程池

线程池启动线程 submit()和 execute()方法有什么不同?

CyclicBarrier 和 CountDownLatch 的区别?

什么是活锁、饥饿、无锁、死锁?

什么是原子性、可见性、有序性?

什么是守护线程?有什么用?

一个线程运行时发生异常会怎样?

线程 yield()方法有什么用?

什么是重入锁?

Synchronized 有哪几种用法?

Fork/Join 框架是干什么的?

线程数过多会造成什么异常?

说说线程安全的和不安全的集合。

什么是 CAS 算法?在多线程中有哪些应用。

怎么检测一个线程是否拥有锁?

Jdk 中排查多线程问题用什么命令?

线程同步需要注意什么?

线程 wait()方法使用有什么前提?

Fork/Join 框架使用有哪些要注意的地方?

线程之间如何传递数据?

保证"可见性"有哪几种方式?

说几个常用的 Lock 接口实现锁。

ThreadLocal 是什么?有什么应用场景?

ReadWriteLock 有什么用?

FutureTask 是什么?

怎么唤醒一个阻塞的线程?

不可变对象对多线程有什么帮助?

多线程上下文切换是什么意思?

Java 中用到了什么线程调度算法?

Thread.sleep(0)的作用是什么?

Java 内存模型是什么,哪些区域是线程共享的,哪些是不共享的?

什么是乐观锁和悲观锁?

Hashtable 的 size()方法为什么要做同步?

同步方法和同步块,哪种更好?

什么是自旋锁?

Runnable 和 Thread 用哪个好?

Java 中 notify 和 notifyAll 有什么区别?

为什么 wait/notify/notifyAll 这些方法不在 thread 类里面?

为什么 wait 和 notify 方法要在同步块中调用?

为什么你应该在循环中检查等待条件?

Java 中堆和栈有什么不同?

你如何在 Java 中获取线程堆栈?

如何创建线程安全的单例模式?

什么是阻塞式方法?

提交任务时线程池队列已满会时发会生什么?

Synchronized 用 过 吗 , 其 原 理 是 什 么 ?


什么是线程?

线程是操作系统能够进⾏运算调度的最⼩单位,它被包含在进程之中,是进程中的实际运作单位,可以使⽤多线程对运算提速

什么是线程安全和线程不安全?

线程安全
线程安全 : 就是多线程访问时,采⽤了加锁机制,当⼀个线程访问该类的某个数据时,进⾏保护,其他线程不能进⾏ 访问,直到该线程读取完,其他线程才可使⽤。不会出现数据不⼀致或者数据污染。Vector 是⽤同步⽅法来实现线程安全的, ⽽和它相似的 ArrayList 不是线程安全的。
线程不安全
线程不安全:就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执⾏写操作,⼀般都需要考虑线程同步,否则的话就可能影响线程安全。

什么是⾃旋锁? 

⾃旋锁是 SMP 架构中的⼀种 low-level 的同步机制。
  1. 当线程A想要获取⼀把⾃旋锁⽽该锁⼜被其它线程锁持有时,线程A会在⼀个循环中⾃旋以检测锁是不是已经可⽤了。
  2. ⽬前的JVM实现⾃旋会消耗CPU,如果⻓时间不调⽤doNotify⽅法,doWait⽅法会⼀直⾃旋,CPU会消耗太⼤
  3. ⾃旋锁⽐较适⽤于锁使⽤者保持锁时间⽐较短的情况,这种情况⾃旋锁的效率⽐较⾼。
  4. 自旋锁的优点是,等待锁的线程不会被挂起,因此可以减少线程切换的开销。
  5. ⾃旋锁需要注意:
    由于⾃旋时不释放CPU,因⽽持有⾃旋锁的线程应该尽快释放⾃旋锁,否则等待该⾃旋锁的线程会⼀直在那⾥⾃旋,这就会浪费CPU时间 ,。

    持有⾃旋锁的线程在sleep之前应该释放⾃旋锁以便其它线程可以获得⾃旋锁。
     

什么是CAS? 

compare and swap (比较与交换) ,是一种有名的 无锁算法 。无锁编程,
即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的
情况下实现变量的同步,所以也叫非阻塞同步( Non-blocking
Synchronization )。 CAS 算法 涉及到三个操作数
  • 需要读写的内存值 V
  • 进行比较的值 A
  • 拟写入的新值 B
当且仅当 V 的值等于 A 时, CAS 通过原子方式用新值 B 来更新 V 的值,否则
不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个 自旋操
,即 不断的重试
1、使⽤CAS在线程冲突严重时,会⼤幅降低程序性能;CAS只适合于线程冲突较少的情况使⽤。
2、synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是⾃旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了⾼吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;⽽线 程冲突严重的情况下,性能远⾼于CAS。

什么是乐观锁和悲观锁?

1 、悲观锁
Java JDK1.5 之前都是靠 synchronized 关键字保证同步的,这种通过使⽤⼀致的锁定协议来协调对共享状态的访问,可以确保⽆论哪个线程持有共享变量的锁,都采⽤独占的⽅式来访问这些变量。独占锁其实就是⼀种悲观锁,所以可以说synchronized是悲观锁。
2 、乐观锁
乐观锁( Optimistic Locking )其实是⼀种思想。相对悲观锁⽽⾔,乐观锁假设认为数据⼀般情况下不会造成冲突,所以在数据进⾏提交更新的时候,才会正式对数据的冲突与否进⾏检测,如果发现冲突了,则让返回⽤户错误的信息,让⽤户决定如何去做。

什么是AQS?

1 AbstractQueuedSynchronizer 简称 AQS ,是⼀个⽤于构建锁和同步容器的框架。事实上 concurrent 包内许多类都是基 于AQS 构建,例如 ReentrantLock
Semaphore CountDownLatch ReentrantReadWriteLock FutureTask 等。 AQS 解 决了在实现同步容器时设计的⼤量细节问题。
2 AQS 使⽤⼀个 FIFO 的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护⼀个等待状态waitStatus

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

1 、原⼦操作是指⼀个不受其他操作影响的操作任务单元。原⼦操作是在多线程环境下避免数据不⼀致必须的⼿段。
2 int++ 并不是⼀个原⼦操作,所以当⼀个线程读取它的值并加 1 时,另外⼀个线程有可能会读到之前的值,这就会引发 错误。
3 、为了解决这个问题,必须保证增加操作是原⼦的,在 JDK1.5 之前我们可以使⽤同步技术来做到这⼀点。到JDK1.5 java.util.concurrent.atomic 包提供了 int long 类型的装类,它们可以⾃动的保证对于他们的操作是原⼦的并且不需要使⽤同步。

2.线程和进程的区别是什么?

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地
址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一
个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的
地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程
序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进
行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

Java 实现线程有哪几种方式?

1 )继承 Thread 类实现多线程
2 )实现 Runnable 接口方式实现多线程
3 )使用 ExecutorService Callable Future 实现有返回结果的多线程

启动线程方法 start()和 run()有什么区别?

只有调用了 start() 方法,才会表现出多线程的特性,不同线程的 run() 方法里面的代
码交替执行。如果只是调用 run() 方法,那么代码还是同步执行的,必须等待一个
线程的 run() 方法里面的代码全部执行完毕之后,另外一个线程才可以执行其 run()
方法里面的代码。

怎么终止一个线程?如何优雅地终止线程?

stop 终止,不推荐。

 优雅的:interrupt

 interrupt仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止

一个线程的生命周期有哪几种状态?它们之间如何流转的?

5种 

RUNNABLE: (可运行状态) 表示线程已经触发 start() 方式调用,线程正式启动,线程处于运行中
状态。
BLOCKED (阻塞状态) 表示线程阻塞,等待获取锁,如碰到 synchronized lock 等关键字等
占用临界区的情况,一旦获取到锁就进行 RUNNABLE 状态继续运行。
WAITING (等待状态) 表示线程处于无限制等待状态,等待一个特殊的事件来重新唤醒,如
通过 wait() 方法进行等待的线程等待一个 notify() 或者 notifyAll() 方法,通过 join()
法进行等待的线程等待目标线程运行结束而唤醒,一旦通过相关事件唤醒线程,线
程就进入了 RUNNABLE 状态继续运行。
TIMED_WAITING (定时等待状态) 表示线程进入了一个有时限的等待,如 sleep(3000) ,等待 3 秒后线程重新进行 RUNNABLE 状态继续运行。
TERMINATED (终止状态) 表示线程执行完毕后,进行终止状态。需要注意的是,一旦线程
通过 start 方法启动后就再也不能回到初始 NEW 状态,线程终止后也不能再回到
RUNNABLE 状态

线程中的 wait()和 sleep()方法有什么区别?

  • sleep() 方法是 Thread 类中的方法,而 wait() 方法是 Object 类中的方法。
  • sleep() 方法不会释放 lock,但是 wait() 方法会释放,而且会加入到等待队列中。
  • sleep() 方法不依赖于同步器 synchronized(),但是 wait() 方法 需要依赖 synchronized 关键字。
  • 线程调用 sleep() 之后不需要被唤醒(休眠时开始阻塞,线程的监控状态依然保持着,当指定的休眠时间到了就会自动恢复运行状态),但是 wait() 方法需要被notify()或notifyAll()重新唤醒(不指定时间需要被别人中断)。

多线程同步有哪几种方法?

Synchronized 关键字, Lock 锁实现,分布式锁等。

什么是死锁?如何避免死锁?

死锁就是两个线程相互等待对方释放对象锁。

产生死锁的四大必要条件

  • 资源互斥:资源只有两种状态,只有可用和不可用两状态,不能同时使用,同一时刻只能被一个进程或线程使用。
  • 占有且请求:已经得到资源的进程或线程,继续请求新的资源,并持续占有旧的资源。
  • 资源不可剥夺:资源已经分配进程或线程后,不能被其它进程或线程强制性获取,除非资源的占有者主动释放。
  • 环路等待:死锁发生时,系统中必定有两个或两个以上的进程或线程组成一条等待环路。

注意:死锁一旦产生基本无解,现在的操作系统无法解决死锁,因此只能防止死锁产生。

防止死锁产生的方法

破坏占用且请求条件:采用预先静态分配的方法,进程或线程在运行前一次申请所有资源,在资源没有满足前不投入运行。
缺点:系统资源会被严重浪费,因为有些资源可能开始时使用,而有些资源结束时才使用。

破坏不可剥夺条件:当一个进程或线程已经占有一个不可剥夺的资源时,请求新资源时无法满足,则释放已经占有的资源,一段时间后再重新申请。
缺点:该策略实现起来比较复杂,释放已经获取资源可能会导致前一阶段的工作失效,反复的申请释放资源会增加系统开销,占用CPU和寄存器、内存等资源。

破坏循环等待条件:给每个资源进行编号,进程或线程按照顺序请求资源,只有拿到前一外资源,才能继续请求下一个资源。
缺点:资源的编号必须相对稳定,资源添加或销毁时会受到影响。

 

多线程之间如何进行通信?

线程通信主要可以分为三种方式,分别为共享内存、消息传递和管道流。每种方式有不同的方法来实现。

  • 共享内存:线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。volatile共享内存
  • 消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。
    1. wait/notify等待通知方式
    2. join方式
  • 管道流
    管道输入/输出流的形式

线程怎样拿到返回结果?

实现 Callable 接口。

violatile 关键字的作用?

一、volatile关键字的作用

  • 保证变量写操作的可见性;
  • 保证变量前后代码的执行顺序;

二、volatile的底层原理

  • 被volatile修饰的变量被修改时,会将修改后的变量直接写入主存中,并且将其他线程中该变量的缓存置为无效,从而让其它线程对该变量的引用直接从主存中获取数据,这样就保证了变量的可见性。
  • volatile修饰的变量inc某个时刻的值为10,线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。 (两个线程都进行自增,操作分为读写两个操作)

三、volatile的适用场景

  • 1、变量的修改不依赖于变量本身
    像是i++、i+=这类的操作在多线程下都是不能保证变量的原子性的。
  • 2、该变量没有包含在具有其他变量的不变式中
     

 volatile是一种“轻量级的锁”,它能保证锁的可见性,但不能保证锁的原子性。

新建 T1、T2、T3 三个线程,如何保证它们按顺序执行?

join 方法。

怎么控制同一时间只有 3 个线程运行?

Semaphore

new Semaphore(3) 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。

为什么要使用线程池?

  • 我们知道不用线程池的话,每个线程都要通过 new Thread(xxRunnable).start()的方 式来创建并运行一个线程,线程少的话这不会是问题,而真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数达到一定数量就会耗尽系统的 CPU 内存资源,也会造成 GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消 耗系统资源的,如果为每个任务都创建线程这无疑是一个很大的性能瓶颈。所以, 线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也 会自动销毁,而不会长驻内存。
  • 线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

常用的几种线程池并讲讲其中的工作原理。

什么是线程池?

很简单,简单看名字就知道是装有线程的池子,我们可以把要执行的多线程交给线
程池来处理,和连接池的概念一样,通过维护一定数量的线程池来达到多个线程的
复用。

线程池的好处

线程池优势:

  • 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
  • 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
  • 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
  • 提供更强大的功能,延时定时线程池。

线程池核心类

java.util.concurrent 包中我们能找到线程池的定义,其中 ThreadPoolExecutor
我们线程池核心类,首先看看线程池类的主要参数有哪些。
如何提交线程
如 可 以 先 随 便 定 义 一 个 固 定 大 小 的 线 程 池 ExecutorService es =
Executors.newFixedThreadPool(3); 提交一个线程 es.submit(xxRunnble);
es.execute(xxRunnble);

submit 和 execute 分别有什么区别呢?

execute 没有返回值,如果不需要知道线程的结果就使用 execute 方法,性能会好很
多。
submit 返回一个 Future 对象,如果想知道线程结果就使用 submit 提交,而且它能
在主线程中通过 Future get 方法捕获线程中的异常。

如何关闭线程池

es.shutdown();
        不再接受新的任务,之前提交的任务等执行结束再关闭线程池。
es.shutdownNow();
        不再接受新的任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程
list 列表。

线程池启动线程 submit()和 execute()方法有什么不同?

execute 没有返回值,如果不需要知道线程的结果就使用 execute 方法,性能会好很
多。
submit 返回一个 Future 对象,如果想知道线程结果就使用 submit 提交,而且它能
在主线程中通过 Future get 方法捕获线程中的异常。

CyclicBarrier 和 CountDownLatch 的区别?

两个看上去有点像的类,都在 java.util.concurrent 下,都可以用来表示代码运行到
某个点上,二者的区别在于:
1.CyclicBarrier 的某个线程运行到某个点上之后,该线程即停止运行,直到所有的
线程都到达了这个点,所有线程才重新运行; CountDownLatch 则不是,某线程运
行到某个点上之后,只是给某个数值 -1 而已,该线程继续运行
2.CyclicBarrier 只能唤起一个任务, CountDownLatch 可以唤起多个任务
3.CyclicBarrier 可 重 用 , CountDownLatch 不 可 重 用 , 计 数 值 为 0
CountDownLatch 就不可再用了

什么是活锁、饥饿、无锁、死锁?

死锁、活锁、饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现
了这三种情况,即线程不再活跃,不能再正常地执行下去了。
死锁
死锁是多线程中最差的一种情况,多个线程相互占用对方的资源的锁,而又相互等
对方释放锁,此时若无外力干预,这些线程则一直处理阻塞的假死状态,形成死锁。
举个例子, A 同学抢了 B 同学的钢笔, B 同学抢了 A 同学的书,两个人都相互占
用对方的东西,都在让对方先还给自己自己再还,这样一直争执下去等待对方还而
又得不到解决,老师知道此事后就让他们相互还给对方,这样在外力的干预下他们
才解决,当然这只是个例子没有老师他们也能很好解决,计算机不像人如果发现这
种情况没有外力干预还是会一直阻塞下去的。
活锁
活锁这个概念大家应该很少有人听说或理解它的概念,而在多线程中这确实存在。
活锁恰恰与死锁相反,死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿
到资源却又相互释放不执行。当多线程中出现了相互谦让,都主动将资源释放给别
的线程使用,这样这个资源在多个线程之间跳动而又得不到执行,这就是活锁。
饥饿
我们知道多线程执行中有线程优先级这个东西,优先级高的线程能够插队并优先执
行,这样如果优先级高的线程一直抢占优先级低线程的资源,导致低优先级线程无
法得到执行,这就是饥饿。当然还有一种饥饿的情况,一个线程一直占着一个资源
不放而导致其他线程得不到执行,与死锁不同的是饥饿在以后一段时间内还是能够
得到执行的,如那个占用资源的线程结束了并释放了资源。
无锁
无锁,即没有对资源进行锁定,即所有的线程都能访问并修改同一个资源,但同时
只有一个线程能修改成功。无锁典型的特点就是一个修改操作在一个循环内进行,
线程会不断的尝试修改共享资源,如果没有冲突就修改成功并退出否则就会继续下
一次循环尝试。所以,如果有多个线程修改同一个值必定会有一个线程能修改成功,
而其他修改失败的线程会不断重试直到修改成功。之前的文章我介绍过 JDK
CAS 原理及应用即是无锁的实现。
可以看出,无锁是一种非常良好的设计,它不会出现线程出现的跳跃性问题,锁使
用不当肯定会出现系统性能问题,虽然无锁无法全面代替有锁,但无锁在某些场合
下是非常高效的。

什么是原子性、可见性、有序性?

原子性、可见性、有序性是多线程编程中最重要的几个知识点,由于多线程情况复
杂,如何让每个线程能看到正确的结果,这是非常重要的。
原子性
原子性是指一个线程的操作是不能被其他线程打断,同一时间只有一个线程对一个
变量进行操作。在多线程情况下,每个线程的执行结果不受其他线程的干扰,比如
说多个线程同时对同一个共享成员变量 n++100 次,如果 n 初始值为 0 n 最后的
值应该是 100 ,所以说它们是互不干扰的,这就是传说的中的原子性。但 n++ 并不
是原子性的操作,要使用 AtomicInteger 保证原子性。
可见性
可见性是指某个线程修改了某一个共享变量的值,而其他线程是否可以看见该共享
变量修改后的值。在单线程中肯定不会有这种问题,单线程读到的肯定都是最新的 1
值,而在多线程编程中就不一定了。每个线程都有自己的工作内存,线程先把共享
变量的值从主内存读到工作内存,形成一个副本,当计算完后再把副本的值刷回主
内存,从读取到最后刷回主内存这是一个过程,当还没刷回主内存的时候这时候对
其他线程是不可见的,所以其他线程从主内存读到的值是修改之前的旧值。像
CPU 的缓存优化、硬件优化、指令重排及对 JVM 编译器的优化,都会出现可见性
的问题。
有序性
我们都知道程序是按代码顺序执行的,对于单线程来说确实是如此,但在多线程情
况下就不是如此了。为了优化程序执行和提高 CPU 的处理性能, JVM 和操作系统
都会对指令进行重排,也就说前面的代码并不一定都会在后面的代码前面执行,即
后面的代码可能会插到前面的代码之前执行,只要不影响当前线程的执行结果。所
以,指令重排只会保证当前线程执行结果一致,但指令重排后势必会影响多线程的
执行结果。虽然重排序优化了性能,但也是会遵守一些规则的,并不能随便乱排序,
只是重排序会影响多线程执行的结果。

什么是守护线程?有什么用?

什么是守护线程?与守护线程相对应的就是用户线程,守护线程就是守护用户线
程,当用户线程全部执行完结束之后,守护线程才会跟着结束。也就是守护线程必
须伴随着用户线程,如果一个应用内只存在一个守护线程,没有用户线程,守护线
程自然会退出。

一个线程运行时发生异常会怎样?

如果异常没有被捕获该线程将会停止执行。 Thread.UncaughtExceptionHandler 是用
于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个未捕获异常将造
成线程中断的时 候 JVM 会 使 用 Thread.getUncaughtExceptionHandler() 来 查 询 线
程 的 UncaughtExceptionHandler 并 将 线 程 和 异 常 作 为 参 数 传 递 给 handler
uncaughtException() 方法进行处理。

线程 yield()方法有什么用?

Yield 方法可以暂停当前正在执行的线程对象,让其它有相同优先级的线程执行。
它是一个静态方法而且只保证当前线程放弃 CPU 占用而不能保证使其它线程一定
能占用 CPU ,执行 yield() 的线程有可能在进入到暂停状态后马上又被执行。

什么是重入锁?

所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再
次获取本对象上的锁,而其他的线程是不可以的。

Synchronized 有哪几种用法?

锁类、锁方法、锁代码块。

Fork/Join 框架是干什么的?

大任务自动分散小任务,并发执行,合并小任务结果。

线程数过多会造成什么异常?

线程过多会造成栈溢出,也有可能会造成堆异常。

说说线程安全的和不安全的集合。

Java 中平时用的最多的 Map 集合就是 HashMap 了,它是线程不安全的。
看下面两个场景:
1 、当用在方法内的局部变量时,局部变量属于当前线程级别的变量,其他线程访
问不了,所以这时也不存在线程安全不安全的问题了。 1
2 、当用在单例对象成员变量的时候呢?这时候多个线程过来访问的就是同一个
HashMap 了,对同个 HashMap 操作这时候就存在线程安全的问题了。

什么是 CAS 算法?在多线程中有哪些应用。

CAS ,全称为 Compare and Swap ,即比较 - 替换。假设有三个操作数:内存值 V
旧的预期值 A 、要修改的值 B ,当且仅当预期值 A 和内存值 V 相同时,才会将内
存值修改为 B 并返回 true ,否则什么都不做并返回 false 。当然 CAS 一定要 volatile
变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期
A 对某条线程来说,永远是一个不会变的值 A ,只要某次 CAS 操作失败,永远
都不可能成功。
java.util.concurrent.atomic 包下面的 Atom**** 类都有 CAS 算法的应用。

怎么检测一个线程是否拥有锁?

java.lang.Thread#holdsLock 方法

Jdk 中排查多线程问题用什么命令?

jstack

线程同步需要注意什么?

1 、尽量缩小同步的范围,增加系统吞吐量。
2 、分布式同步锁无意义,要使用分布式锁。
3 、防止死锁,注意加锁顺序。

线程 wait()方法使用有什么前提?

要在同步块中使用。

Fork/Join 框架使用有哪些要注意的地方?

如果任务拆解的很深,系统内的线程数量堆积,导致系统性能性能严重下降;
如果函数的调用栈很深,会导致栈内存溢出;

线程之间如何传递数据?

通 过 在 线 程 之 间 共 享 对 象 就 可 以 了 , 然 后 通 过 wait/notify/notifyAll
await/signal/signalAll 进行唤起和等待,比方说阻塞队列 BlockingQueue 就是为线程
之间共享数据而设计的

保证"可见性"有哪几种方式?

synchronized viotatile

说几个常用的 Lock 接口实现锁。

ReentrantLock ReadWriteLock

ThreadLocal 是什么?有什么应用场景?

ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作
用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。用来
解决数据库连接、 Session 管理等。

ReadWriteLock 有什么用?

ReadWriteLock 是一个读写锁接口, ReentrantReadWriteLock ReadWriteLock
口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之
间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

FutureTask 是什么?

FutureTask 表示一个异步运算的任务, FutureTask 里面可以传入一个 Callable 的具
体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、
取消任务等操作。

怎么唤醒一个阻塞的线程?

如果线程是因为调用了 wait() sleep() 或者 join() 方法而导致的阻塞,可以中断线程,
并且通过抛出 InterruptedException 来唤醒它;如果线程遇到了 IO 阻塞,无能为力,
因为 IO 是操作系统实现的, Java 代码并没有办法直接接触到操作系统。

不可变对象对多线程有什么帮助?

不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步
手段,提升了代码执行效率。

多线程上下文切换是什么意思?

多线程的上下文切换是指 CPU 控制权由一个已经正在运行的线程切换到另外一个
就绪并等待获取 CPU 执行权的线程的过程。

Java 中用到了什么线程调度算法?

抢占式。一个线程用完 CPU 之后,操作系统会根据线程优先级、线程饥饿情况等
数据算出一个总的优先级并分配下一个时间片给某个线程执行。

Thread.sleep(0)的作用是什么?

由于 Java 采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到 CPU
控制权的情况,为了让某些优先级比较低的线程也能获取到 CPU 控制权,可以使
Thread.sleep(0) 手动触发一次操作系统分配时间片的操作,这也是平衡 CPU 控制
权的一种操作。

Java 内存模型是什么,哪些区域是线程共享的,哪些是不共享的?

内存模型定义了 共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了 CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。
线程独占
程序计数器(Program Counter Rerister)
程序计数器区域一块内存较小的区域,它用于存储线程的每个执行指令,每个线程
都有自己的程序计数器,此区域不会有内存溢出的情况。
虚拟机栈(VM Stack
虚拟机栈描述的是 Java 方法执行的内存模型,每个方法被执行的时候都会同时创
建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口
等信息。每一个方法被调用直至执行完成的过程就对应着一个栈帧在虚拟机栈中从
入栈到出栈的过程。
本地方法栈(Native Method Stack
本地方法栈用于支持本地方法(
native 标识的方法,即非 Java 语言实现的方法)。
虚拟机栈和本地方法栈,当线程请求分配的栈容量超过 JVM 允许的最大容量时抛
StackOverflowError 异常。
线程共享区域
线程共享区域包含:堆和方法区。
堆(Heap
堆是最常处理的区域,它存储在 JVM 启动时创建的数组和对象, JVM 垃圾收集也
主要是在堆上面工作。
如 果 实 际 所 需 的 堆 超 过 了 自 动 内 存 管 理 系 统 能 提 供 的 最 大 容 量 时 抛 出 1
OutOfMemoryError 异常。
方法区(Method Area
方法区是可供各条线程共享的运行时内存区域。存储了每一个类的结构信息,例如
运行时常量池( Runtime Constant Pool )、字段和方法数据、构造函数和普通方法
的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法。当创建类
和接口时,如果构造运行时常量池所需的内存空间超过了方法区所能提供的最大内
存空间后就会抛出 OutOfMemoryError
运行时常量池( Runtime Constant Pool
运行时常量池是方法区的一部分,每一个运行时常量池都分配在 JVM 的方法区中,
在类和接口被加载到 JVM 后,对应的运行时常量池就被创建。运行时常量池是每
一个类或接口的常量池( Constant_Pool )的运行时表现形式,它包括了若干种常量:
编译器可知的数值字面量到必须运行期解析后才能获得的方法或字段的引用。如果
方法区的内存空间不能满足内存分配请求,那 Java 虚 拟 机 将 抛 出 一 个
OutOfMemoryError 异常。栈包含 Frames ,当调用方法时, Frame 被推送到堆栈。
一个 Frame 包含局部变量数组、操作数栈、常量池引用。

什么是乐观锁和悲观锁?

乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐
观锁认为竞争不总是会发生,因此它不需要持有锁,将比较 - 替换这两个动作作为
一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有
相应的重试逻辑。
悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,
悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的
锁,就像 synchronized ,不管三七二十一,直接上了锁就操作资源了。

Hashtable 的 size()方法为什么要做同步?

同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以
多条线程同时访问。 所以,这样就有问题了,可能线程 A 在执行 Hashtable put
方法添加数据,线程 B 则可以正常调用 size() 方法读取 Hashtable 中当前元素的个
数,那读取到的值可能不是最新的,可能线程 A 添加了完了数据,但是没有对
size++ ,线程 B 就已经读取 size 了,那么对于线程 B 来说读取到的 size 一定是不准
确的。
而给 size() 方法加了同步之后,意味着线程 B 调用 size() 方法只有在线程 A
调用 put 方法完毕之后才可以调用,这样就保证了线程安全性 CPU 执行代码,执行
的不是 Java 代码,这点很关键,一定得记住。 Java 代码最终是被翻译成机器码执
行的,机器码才是真正可以和硬件电路交互的代码。即使你看到 Java 代码只有一
行,甚至你看到 Java 代码编译之后生成的字节码也只有一行,也不意味着对于底
层来说这句语句的操作只有一个。一句 "return count" 假设被翻译成了三句汇编语句
执行,一句汇编语句和其机器码做对应,完全可能执行完第一句,线程就切换了。

同步方法和同步块,哪种更好?

同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码
的效率。
请知道一条原则:同步的范围越小越好。

什么是自旋锁?

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线
程改变时才能进入临界区。

Runnable 和 Thread 用哪个好?

Java 不支持类的多重继承,但允许你实现多个接口。所以如果你要继承其他类,也 1
为了减少类之间的耦合性, Runnable 会更好。

Java 中 notify 和 notifyAll 有什么区别?

notify() 方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用
武之地。
notifyAll() 唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行。

为什么 wait/notify/notifyAll 这些方法不在 thread 类里面?

这是个设计相关的问题,它考察的是面试者对现有系统和一些普遍存在但看起来不
合理的事物的看法。回答这些问题的时候,你要说明为什么把这些方法放在 Object
类里是有意义的,还有不把它放在 Thread 类里的原因。
一个很明显的原因是
JAVA 提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如
果线程需要等待某些锁那么调用对象中的 wait() 方法就有意义了。如果 wait() 方法定
义在 Thread 类中,线程正在等待的是哪个锁就不明显了。简单的说,由于 wait
notify notifyAll 都是锁级别的操作,所以把他们定义在 Object 类中因为锁属于对
象。

为什么 wait 和 notify 方法要在同步块中调用?

  • 调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁。 
  •  notify(),notifyAll()是将锁交给含有wait()方法的线程,让其继续执行下去,如果自身没有锁,怎么叫把锁交给其他线程呢;(本质是让处于入口队列的线程竞争锁)

为什么你应该在循环中检查等待条件?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,
程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来时,不能
认为它原来的等待状态仍然是有效的,在 notify() 方法调用之后和等待线程醒来之
前这段时间它可能会改变。这就是在循环中使用 wait() 方法效果更好的原因,你可
以在 Eclipse 中创建模板调用 wait notify 试一试。

Java 中堆和栈有什么不同?

每个线程都有自己的栈内存,用于存储本地变量,方法参数和栈调用,一个线程中
存储的变量对其它线程是不可见的。而堆是所有线程共享的一片公用内存区域。对
象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线
程使用该变量就可能引发问题,这时 volatile 变量就可以发挥作用了,它要求线程
从主存中读取变量的值。

你如何在 Java 中获取线程堆栈?

对于不同的操作系统,有多种方法来获得 Java 进程的线程堆栈。当你获取线程堆
栈时, JVM 会把所有线程的状态存到日志文件或者输出到控制台。在 Windows
可以使用 Ctrl + Break 组合键来获取线程堆栈, Linux 下用 kill -3 命令。你也可以
jstack 这个工具来获取,它对线程 id 进行操作,你可以用 jps 这个工具找到 id

如何创建线程安全的单例模式?

单例模式即一个 JVM 内存中只存在一个类的对象实例分类
1 、懒汉式
类加载的时候就创建实例
2 、饿汉式
使用的时候才创建实例

什么是阻塞式方法?

阻塞式方法是指程序会一直等待该方法完成期间不做其他事情, ServerSocket 1
accept() 方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前
线程会被挂起,直到得到结果之后才会返回。此外,还有异步和非阻塞式方法在任
务完成前就返回。

提交任务时线程池队列已满会时发会生什么?

当线程数小于最大线程池数 maximumPoolSize 时就会创建新线程来处理,而线程
数大于等于最大线程池数 maximumPoolSize 时就会执行拒绝策略。

Synchronized 用 过 吗 , 其 原 理 是 什 么 ?

这 是 一 道 Java 面 试 中 几 乎 百 分 百 会 问 到 的 问 题 , 因 为 没 有 任 何 写 过 并
发 程 序 的 开 发 者 会 没 听 说 或 者 没 接 触 过 Synchronized。
Synchronized 是 由 JVM 实 现 的 一 种 实 现 互 斥 同 步 的 一 种 方 式 , 如 果
你 查 看 被 Synchronized 修 饰 过 的 程 序 块 编 译 后 的 字 节 码 , 会 发 现 ,
被 Synchronized 修 饰 过 的 程 序 块 , 在 编 译 前 后 被 编 译 器 生 成 了 monitorenter 和 monitorexit 两 个 字 节 码 指 令 
这 两 个 指 令 是 什 么 意 思 呢 ?
在 虚 拟 机 执 行 到 monitorenter 指 令 时 , 首 先 要 尝 试 获 取 对 象 的 锁 :
如 果 这 个 对 象 没 有 锁 定 , 或 者 当 前 线 程 已 经 拥 有 了 这 个 对 象 的 锁 , 把 锁
的 计 数 器 +1; 当 执 行 monitorexit 指 令 时 将 锁 计 数 器 -1; 当 计 数 器
为 0 时 , 锁 就 被 释 放 了 。
如 果 获 取 对 象 失 败 了 , 那 当 前 线 程 就 要 阻 塞 等 待 , 直 到 对 象 锁 被 另 外 一
个 线 程 释 放 为 止 。
Java 中 Synchronize 通 过 在 对 象 头 设 置 标 记 , 达 到 了 获 取 锁 和 释 放
锁 的 目 的 。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值