1. 什么是线程?

一个程序可以看作是一个进程,线程是进程的一个实体单位,是一条独立的执行路径,线程是操作系统能够进行运算调度的最小单位,可以使用多线程对运算进行提速。
例如:一个线程完成一个任务需要100ms,那么我们可以使用多个线程来共同完成这个任务,例如10个线程来执行,只需要10ms。

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

简单来说,如果线程没有加锁,那么就是不安全的;反之,如果加锁了,那么线程就是安全的。
线程安全问题都是由全局变量及静态变量引起的。
线程安全:
多个线程共同执行任务访问同一个资源对象,线程安全采用了加锁同步机制,当前线程进行数据访问时,其他线程不能进行访问,只有当前线程读取完成后,其他线程才能进行访问。
线程不安全:
多个线程共同执行任务访问同一个资源对象,线程没有采用了加锁同步机制,多个线程会同时对这个数据进行访问操作,导致数据没有按照预期进行操作。

3. 什么是自旋锁?

线程A进入一段程序想要获取自旋锁,但此时自旋锁又被其他线程所拥有,那么线程A会不断循环中自选以检测当前锁是否可用。

4. 什么是Java内存模型?

5. 什么是CAS?

(compare and swap)比较并交换,主要应用在CAS有三个操作数,内存值V,旧的预期值A,要修改的新值B,如果内存值和设置的预期值A相同,将内存值V修改为B。
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
优点:
CAS操作确保对内存的读-改-写操作都是原子操作
缺点:
循环时间长、开销大、只能保证一个共享变量的原子操作。
总结:

  1. 1. 使用CAS在线程冲突严重时,会大幅降低程序性能;CAS只适合于线程冲突较少的情况使用。
  2. 1. synchronizedjdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS

6. 什么是乐观锁和悲观锁?

悲观锁:JDK1.5之前都是通过synchronized关键词进行线程同步的,这种方式通过一致的锁定协议来协调对共享数据的访问,确认无论是哪一个线程来访问数据,都采用独占锁的方式来访问这些变量。

乐观锁:乐观锁的思想认为数据在一般情况下都不会造成冲突,所以只有当对数据进行更新的时候,才会对数据的是否冲突进行检测,如果发生了冲突,会返回让用户自己去觉得如何去做。

7. 什么是AQS?

Abstract queued Synchronizer,是一个用于构建锁和同步容器的框架。许多concurrent类中许多类,例如ReentrantLock、Semphore等都是基于AQS实现加锁和释放锁的功能
6、多线程面试题 - 图1
包含了state变量(代表加锁的状态)、加锁线程(记录当前是哪个线程调用了加锁功能)、等待队列等并发中的核心组件。
例如,线程A调用ReentrantLock.lock()方法,此时没有其他线程进行加锁操作(state变量为0),吗那么线程1成功加锁(state变量为1),加锁对象的线程指向线程1。
若线程1没有调用unlock()方法释放锁,其他线程想要调用lock()加锁判断state变量是否为0以及当前加锁线程是否为其本身,如若都不满足,则线程2就加锁失败,线程2就会把自身放入AQS的一个等待队列,等待加锁的线程释放锁,然后进行加锁操作。

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

原子操作是指一个不受其他操作影响的操作任务单元。不会被打断地的操作。原子操作是在多线程环境下避免数据不一致必须的手段。
为了保证增加操作是原子的,在JDK1.5之前我们可以使用同步技术来做到这一点。到JDK1.5,java.util.concurrent.atomic包提供了int和long类型的装类,它们可以自动的保证对于他们的操作是原子的并且不需要使用同步。

9. 什么是Executors框架?

无限制的线程创建会导致内存的溢出,通过Executors框架可以创建出很方便的创建出一个线程池:

  1. - **newCachedThreadPool**创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. - **newFixedThreadPool** 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. - **newScheduledThreadPool** 创建一个定长线程池,支持定时及周期性任务执行。
  4. - **newSingleThreadExecutor** 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

10. 什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?

阻塞队列的概念:
阻塞队列是指一个支持附加操作的队列,
在队列为空时,获取元素的线程会等待队列变为非空。
当队列满时,存储元素的线程会等待队列可用。
应用场景
生产者往队列里面添加元素的线程,而消费者是往队列里面拿元素的线程,基于线程的通信,就是当生产者往满的队列里添加元素时会阻塞住生产者,当消费者消费了一个队列中的元素后,会通知生产者当前队列可用。
BlockingQueue是一种数据结构,支持一个线程往里存资源,一个线程往外取资源

11. 什么是Callable和Future?

当我们需要创建线程时,可以采用实现Callable接口并重写call方法来完成。
当我们需要获取线程结果时,Calllabale一般用于产生线程的结果,Future一般用于获取线程的结果
一般我们使用callable创建线程时,Executors类提供了一些方法在线程池中执行Callable内的任务,但由于Callable是并行的,需要等待结果的返回 ,Future类很好的解决了这个问题。
在线程池提交Callable任务后返回了一个Future对象,使用它可以知道Callable任务的状态和得到Callable返回的执行结果

12. 什么是FutureTask?

基本概念:
FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
应用场景:
(1)利用FutureTask和ExecutorService,可以用多线程的方式提交计算任务,主线程继续执行其他任务,当主线程需要子线程的计算结果时,在异步获取子线程的执行结果。
(2)FutureTask在高并发环境下确保任务只执行一次

13. 什么是同步容器和并发容器的实现?

14. 什么是多线程?优缺点?

什么是多线程?
多线程:是指从软件或者硬件上实现多个线程的并发技术。Cpu是通过时间片的方式为进程分配cpu处理时间的,例如我们从网上下载文件的时候,如果仅仅使用单线程,那么我们需要按照下载-显示进度-保存的顺序依次进行,不仅会拖慢速度,而且会浪费cpu的内存。利用多线程可以实现下载文件的一边下载、一边显示进度,一边保存,充分利用了cpu资源空间。
多线程的好处:

  1. 1. 使用多线程可以把程序中占据时间长的任务放到后台去处理,如图片、视屏的下载
  2. 1. 发挥多核处理器的优势,并发执行让系统运行的更快、更流畅,用户体验更好

多线程的缺点:

  1. 1. 大量的线程降低代码的可读性;
  2. 1. 更多的线程需要更多的内存空间
  3. 1. 当多个线程对同一个资源出现争夺时候要**注意线程安全的问题。**

15. 什么是多线程的上下文切换?

即使是单核CPU也支持多线程执行,**CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程时同时执行的,时间片一般是几十毫秒(ms)
上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换 回这个任务时,可以再次加载这个任务的状态。**

  • 从任务保存到再加载的过程就是一次上下文切换

    16. ThreadLocal的设计理念与作用?

    Java中的ThreadLocal类允许我们创建只能被同一个线程读写的变量。因此,如果一段代码含有一个ThreadLocal变量的引用,即使两个线程同时执行这段代码,它们也无法访问到对方的ThreadLocal变量
    若多个线程读取ThreadLocal的对象,每个线程只能读取自身创建的ThreadLocal变量设置的值。

    17. ThreadPool(线程池)用法与优势?

    1、为什么要用线程池:
    减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
    可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
    Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
    2、new Thread 缺点
    每次new Thread新建对象性能差。
    线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
    缺乏更多功能,如定时执行、定期执行、线程中断。
    3、ThreadPool 优点
    减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
    可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)
    减少在创建和销毁线程上所花的时间以及系统资源的开销
    如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存
    4、线程池的核心方法使用ThreadPoolExecutor创建线程池
    核心线程池数
    阻塞队列
    最大线程池数
    保持存活时间
    时间单位
    创建线程的工厂
    拒绝策略

    18. Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。

    ArrayBlockingQueue
    此队列按照先进先出(FIFO)的原则对元素进行排序,但是默认情况下不保证线程公平的访问队列,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(即先阻塞,先插入)的。
    CountDownLatch**
    CountDownLatch 允许一个或多个线程等待其他线程完成操作。

    19. synchronized和ReentrantLock的区别?

    基本概念

    1. - **可重复入锁**
    2. - **中断锁**
    3. - **公平锁和非公平锁**
    4. - CAS操作

    20. Semaphore有什么作用?

    • semphore通过维护若干个许可证来控制线程对共享资源的访问。 如果许可证剩余数量大于零时,线程则允许访问该共享资源;如果许可证剩余数量为零时,则拒绝线程访问该共享资源。 Semaphore所维护的许可证数量就是允许访问共享资源的最大线程数量。 所以,线程想要访问共享资源必须从Semaphore中获取到许可证。
    • semphore有两个核心方法:
      • acquire():从信号量中获取一个许可,进入阻塞状态。
      • release():释放持有许可证的线程;
    • 然而,实际上并没有真实的许可证对象供线程使用,Semaphore只是对可用的数量进行管理维护。
    • 底层原理:

      • Semaphore内部主要通过AQS(Abstract Queued Synchronizer)实现线程的管理。Semaphore在构造时,需要传入许可证的数量,它最后传递给了AQS的state值。线程在调用acquire方法获取许可证时,如果Semaphore中许可证的数量大于0,许可证的数量就减1,线程继续运行,当线程运行结束调用release方法时释放许可证时,许可证的数量就加1。如果获取许可证时,Semaphore中许可证的数量为0,则获取失败,线程进入AQS的等待队列中,等待被其它释放许可证的线程唤醒。
      • 总结:如果线程要访问一个资源就必须先获得信号量。如果信号量内部计数器大于0,信号量减1,然后允许共享这个资源;否则,如果信号量的计数器等于0,信号量将会把线程置入休眠直至计数器大于0.当信号量使用完时,必须释放
      • 注:Semphore底层有两个构造函数

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

        Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
        它的优势有:

      • 可以使锁更公平。

      • 可以使线程在等待锁的时候响应中断。
      • 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间。
      • 可以在不同的范围,以不同的顺序获取和释放锁。

        22. Hashtable的size()方法中明明只有一条语句”return count”,为什么还要做同步?

        固定类的同步方法,同一时间只能有一个线程执行。
        对于固定类的非同步方法,可能会有多个线程同时进行访问,此时该方法是不安全的:如果线程A访问Hashtable类向其添加数据,同时线程B访问Hashtable类读取size()方法。
        此时可能出现的问题为:线程A已经添加完数据后,size还没有++;而线程B读取的size()方法是还没有size没有++的数据,导致数据读取的不正确。

        23. ConcurrentHashMap的并发度是什么?

        ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势。

        24. ReentrantReadWriteLock读写锁的使用?

25. CyclicBarrier和CountDownLatch的用法及区别?

相同点:
CountDownLatch和CyclicBarrier都有让多个线程等待同步然后再开始下一步动作的意思,等待其他线程完成操作
区别:
CountDownLatch : 一个线程(或者多个), 等待另外N个线程完成某个事情之后才能执行。
CyclicBarrier : N个线程相互等待,任何一个线程没有到达或完成时,所有的线程都必须互相等待。
这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个线程”, 是它在等待, 而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。
而对于CyclicBarrier来说,重点是那N个线程,他们之间任何一个没有完成,所有的线程都必须等待.
6、多线程面试题 - 图2

26. LockSupport工具?

  • LockSupport核心类的方法就两个:
    • part() :使当前线程阻塞,
    • unpark():唤醒指定线程进行调用
  • LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。初始时,permit为0,当调用unpark()方法时,线程的permit加1,当调用park()方法时,如果permit为0,则调用线程进入阻塞状态。

    27. Condition接口及其实现原理?

    Condition是一个多线程间协调通信的工具类。Condition作为一个条件类,很好的自己维护了一个等待信号的队列,并在适时的时候将结点加入到AQS的等待队列中来实现的唤醒操作。
    线程1调用ReentrantLock.lock()方法,线程加入AQS等待队列。
    调用线程1中Condition类的await()方法,该线程从AQS中移除,对应是锁的释放。
    然后马上加入到Condition类的等待队列中,此时需要signal()方法来唤醒该线程。
    线程2因为线程1锁的释放,判断可以获取锁,此时线程2获取锁,加入AQS等待队列。
    线程2调用Condition类的signal()方法,这时候Condition等待队列中只有线程1,于是线程1被取出来加入到AQS的等待队列中,但此时线程1并没有被真正唤醒。
    线程2调用ReentrantLock.unlock()方法,释放锁,此时因为AQS等待队列中只有线程1,此时AQS释放锁后从头到尾按顺序唤醒线程时,线程1被唤醒,然后线程1开始执行。
    直至线程1调用ReentrantLock.unlock()方法,整个过程执行完毕。

    28. Fork/Join框架的理解?

    29. wait()和sleep()的区别?

    1) 原理不同
    sleep( ) 是Thread类的静态方法,它的功能是使线程暂停一段时间,一段时间后线程会自动苏醒;
    wait () 是Object类的方法,它的功能是使线程暂停执行,需要notify() 或者是notifyAll()方法来唤醒;
    2)对锁的机制不同
    sleep()不涉及线程之间的通信问题,因此不会释放锁;
    wait()方法会使当前线程释放掉所占用的锁,供其他线程使用 ;
    3)使用区域不同
    sleep()可以放在任何地方使用,但是必须捕获异常;
    wait()方法只能在同步方法或者同步代码块中使用,一般配合synchronized使用;

    30. 线程的五个状态(五种状态,创建、就绪、运行、阻塞和死亡)?

31. start()方法和run()方法的区别?

一个线程调用start()方法以后,并不会马上执行,而是进入就绪状态,被放入等待队列,等待调度。—一旦该线程获得了CPU调度执行,那么该线程会通过JVN内存,调用线程内部的run()方法,执行线程体。

32. Runnable接口和Callable接口的区别?

1)重写的方法不同
Runnable接口重写的run()方法返回值类型为void,单纯执行线程执行体;
Callable接口重写的run()方法具有返回值,是一个泛型,可以结合FutureTask和Future获取执行的结果;
2)抛出异常值
Callable接口可以抛出异常值,而Runnable接口不可以抛出异常值。

33. volatile关键字的作用?

volatile是java线程提供的一种轻量级同步机制,基于 volatile 关键字修时多线程的共享变量来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。
原子性: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
有序性:即程序执行的顺序按照代码的先后顺序执行。
总结:volatile关键词保证了可见性,但不保证原子性!

34. Java中如何获取到线程dump文件?

基本概念:
线程dump也就是线程堆栈!
获取方法
(1)取到线程的pid,可以通过使用jps命令,在Linux环境下还可以使用ps -ef | grep java
(2)打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid
另外提一点,Thread类提供了一个getStackTrace()方法也可以用于获取线程堆栈。这是一个实例方法,因此此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈

35. 线程和进程有什么区别?

一个程序即为一个进程,线程是进程的实体单元,一个线程为一条独立路径。
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1—n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)

多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。

36. 线程实现的方式有几种(四种)?

1、继承Thread
2、实现Runable接口,重写run()方法
3、实现Callable接口,重写call()方法,产生线程,结合FutureTask获取线程的结果。
4、利用线程池创建多个线程,ThreadPoolExector

37. 高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换

2)并发不高、任务执行时间长的业务要区分开看:

a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务

b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换

3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考其他有关线程池的文章。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

38. 如果你提交任务时,线程池队列已满,这时会发生什么?

(1)如果当前线程池队列使用的无界队列LinkedBlockingQueue,那么继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;
(2)如果当前线程池队列使用的是有界队列如ArrayListBlockingQueue,任务首先还是会被添加到ArrayListBlockingQueue中,当前队列会根据线程数是否超过了最大线程数maximunPoolSize,如果超过了最大线程数maximunPoolSize,则会使用拒绝策略处理ArrayListBlockingQueue中的任务。

39. 锁的等级:方法锁、对象锁、类锁?

方法锁和对象锁其本质相同,因此只存在对象锁和类锁的区别?
对象锁:java每个对象都可以做一个实现同步的锁,称为内置锁。线程只有进入同步方法或同步代码块才能获得这个内置锁,然后再退出方法或代码块的时候释放锁,获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。对象锁是用于对象实例方法,或者一个实例对象上的。
类锁:**类锁是用于类的calss对象或者类的静态方法中。

★总结:对象锁是用来控制实例方法之间的同步,而类锁是用来控制静态方法(或者静态变量互斥体)之间的同步的。

40. 如果同步块内的线程抛出异常会发生什么?

如果这个异常没有被捕获的话,这个线程就停止执行了。
另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放

41. 并发编程(concurrency)并行编程(parallellism)有什么区别?

  • 并行是指两个或多个事件在同一时刻发生,并发是指两个或多个事件在同一时间间隔内发生!
  • 并行是指不同实体上的两个或多个事件,并发是指同一实体上的多个事件!
  • 并行是指在多台处理器上同时处理多个任务,并发是指在同台处理器上处理多个任务!

注:并发是因为多进程/多线程都是需要去完成的任务,不并行是因为并行与否由操作系统的调度器决定,可能会让多个进程/线程被调度到同一个CPU核心上。只不过调度算法会尽量让不同进程/线程使用不同的CPU核心,所以在实际使用中几乎总是会并行,但却不能以100%的角度去保证会并行。也就是说,并行与否程序员无法控制,只能让操作系统决定。
再次注明,并发是一种现象,之所以能有这种现象的存在,和CPU的多少无关,而是和进程调度以及上下文切换有关的。
并行是通过操作系统决定,而并发是通过进程来调度的!!!

42. 如何保证多线程下 i++ 结果正确?

解释i++和++i为什么是线程不安全的:
i++的执行过程:
1、先是寄存器从内存中读取i,
2、然后在寄存器中进行+1操作,
3、最后+1后的值回传内存,
因为没有保证原子操作,万一出现3步没有执行完而线程的CPU时间片已经用完,导致操作失败,所以并不安全。
如何保证i++和++i线程安全:
1、利用CAS技术**(compare and swap),通过总线加锁的方式,指令能保证CPU寄存器和内存交换数据是原子操作
2、利用ReentrantLock 进行同步加锁
3、利用synchronized给对象加锁,但是影响运行效率**

43. 一个线程如果出现了运行时异常会怎么样?

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

44. 如何在两个线程之间共享数据?

Java 内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题。
method1:将需要操作的数据封装成类,并创建操作这些数据的方法,并利用synchronized对这些方法加锁;
method2:将Runnable对象作为类的内部类,需要操作的数据作为类的成员变量,每个线程对数据操作方法定义在外部类,以实现数据的各个操作的同步与互斥;

45. 生产者消费者模型的作用是什么?

生产者消费者问题就是要控制线程的阻塞状态,保证生产者和消费者进程在一定条件下,一直稳定运行.
其实质在于使用阻塞队列保证线程生产数据和消费数据的强耦合,生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

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

1、synchronized的wait() 和notify()/notiftAll()
2、ReentrantLock创建的Condition可重入锁调用await()和signal()/signalAll()方法
3、LockSupport的park和unpark()实现线程的阻塞和唤醒;
注:ReentrantLock底层是基于AQS实现的,而AQS的底层是基于LockSupport实现的,所以LockSupport的效率相比更高!

47. Java中用到的线程调度算法是什么

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

48. 单例模式的线程安全性?

饿汉式(程序一开始就创建实例化对象):线程安全
懒汉式(只有等需要使用时才创建实例化对象):线程不安全—>
懒汉式线程不安全解决方案:
1、使用同步机制synchronized关键词修饰实例化方法
2、创建内部类
3、添加关键字volatile 修饰成员变量

49. 线程类的构造方法、静态块是被哪个线程调用的?

线程类的构造方法、静态块是被new 这个线程类的对象所调用的:
例如Thread2.new Thread(thread1),那么线程1的构造方法、静态块是由线程2所调用的,而线程1内部的run()方法执行体是由其自身所调用;
main函数中new了Thread2,那么线程2的构造方法、静态块是由main主线程所调用的,内部的run()方法是尤其自身所调用

50. 同步方法和同步块,哪个是更好的选择?

遵循一条原则:同步的范围越小越好!

同步块,意味着同步块之外的是异步执行的,这样效率更高!而且同步块不会锁住整个对象,而同步方法会锁着整个对象。

51. 如何检测死锁?怎么预防死锁?

(1)死锁产生的四大必要条件:
1、互斥性:即当资源被一个线程使用时,其他线程不得使用
2、不可抢占:资源请求者不能强制从资源占有着从获取资源,只能等资源占有着主动释放
3、请求和保持:当资源请求这对资源请求的同时保持对原有资源的占用
4、循环等待:即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
(2)死锁的根本原因
1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环;
2)默认的锁申请操作是阻塞的。所以要避免死锁,就要在一遇到多个对象锁交叉的情况,就要仔细审查这几个对象的类中的所有方法,是否存在着导致锁依赖的环路的可能性。要采取各种方法来杜绝这种可能性。
(3)如何避免死锁
1、我们可以使用ReentrantLock.tryLock()方法,在一个循环中,如果tryLock()返回失败,那么就释放以及获得的锁,并睡眠一小段时间
2、信号量可以控制资源能被多少线程访问,这里我们指定只能被一个线程访问,就做到了类似锁住。而信号量可以指定去获取的超时时间,我们可以根据这个超时时间,去做一个额外处理。
对于无法成功获取的情况,一般就是重复尝试,或指定尝试的次数,也可以马上退出。
(4)死锁的检测:
1、jstack命令
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。
Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
2、jConsole工具
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。
如何预防死锁
1、以确定的顺序获得锁:如果必须获取多个锁,那么在设计的时候需要充分考虑不同线程之前获得锁的顺序。
2、超时放弃:当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了boolean tryLock(long time, TimeUnit unit) throws InterruptedException方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。