程序优先级级为27的任务进入就绪状态,如何设置或编程

1、在 java中守护线程和本地线程区别

2、线程与进程的区别?

3、什么是多线程中的上下文切换

4、死锁与活锁的区别,死锁与饥饿的区别

5、Java 中用到的线程调度算法是什么?

6、什么是线程组为什么在 Java 中不推荐使用?

13、什么是阻塞队列阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产者-消费者模型

16、什么是并发容器的实现?

17、多线程同步和互斥有几种实现方法都是什么?

18、什么是竞争条件你怎样发现和解决竞争?

20、为什么我們调用 start()方法时会执行 run()方法为什么我们不能直接调用 run()方法?

21、Java 中你怎样唤醒一个阻塞的线程

23、什么是不可变对象,它对写并发应用有什麼帮助

24、什么是多线程中的上下文切换?

25、Java 中用到的线程调度算法是什么

26、什么是线程组,为什么在 Java 中不推荐使用

27、为什么使用 Executor 框架比使用应用创建和管理线程好?

28、java 中有几种方法可以实现一个线程

29、如何停止一个正在运行的线程?

31、什么是 Daemon 线程它有什么意义?

32、java 如何实现多线程之间的通讯和协作

34、当一个线程进入某个对象的一个 synchronized 的实例方法后,其它线程是否可进入此对象的其它方法

35、乐观鎖和悲观锁的理解及如何实现,有哪些实现方式

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

40、为什么代码会重排序

43、一个线程运行时发生异常會怎样?

44、如何在两个线程间共享数据

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

51、Java 中的同步集合与并发集合有什么区别?

52、什么是线程池 为什么要使用它?

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

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

60、什么是阻塞式方法

64、如何让正在运行的线程暂停┅段时间?

65、你对线程程序优先级级的理解是什么

67、你如何确保 main()方法所在的线程是 Java 程序最后结束的线程?

68、线程之间是如何通信的

72、洳何确保线程安全?

73、同步方法和同步块哪个是更好的选择?

74、如何创建守护线程

75、什么是 Java Timer 类?如何创建一个有特定时间间隔的任务

1、在 java 中守护线程和本地线程区别?

java 中的线程分为两种:守护线程(Daemon)和用户线程(User)

任何线程都可以设置为守护线程和用户线程,通過方法 Thread.setDaemon(boolon);true 则把该线程设置为守护线程反之则为用户线程。Thread.setDaemon()必须在 Thread.start()之前调用否则运行时会抛出异常。

唯一的区别是判断虚拟机(JVM)何时离开Daemon 是为其他线程提供服务,如果全部的 User Thread 已经撤离Daemon 没有可服务的线程,JVM 撤离也可以理解为守护线程是 JVM 自动创建的线程(但不一定),用戶线程是程序创建的线程;比如 JVM 的垃圾回收线程是一个守护线程当所有线程已经撤离,不再产生垃圾守护线程自然就没事可干了,当垃圾回收线程是 Java 虚拟机上仅剩的线程时Java 虚拟机会自动离开。

扩展:Thread Dump 打印出来的线程信息含有 daemon字样的线程即为守护进程,可能会有:服務守护进程、编译守护进程、windows 下的监听 Ctrl+break的守护进程、Finalizer守护进程、引用处理守护进程、GC 守护进程

2、线程与进程的区别?

进程是操作系统分配资源的最小单元线程是操作系统调度的最小单元。

一个程序至少有一个进程,一个进程至少有一个线程

3、什么是多线程中的上下文切換?

多线程会共同使用一组计算机上的 CPU而线程数大于给程序分配的 CPU 数量时,为了让各个线程都有执行的机会就需要轮转使用 CPU。不同的線程切换使用 CPU发生的切换数据等就是上下文切换

4、死锁与活锁的区别,死锁与饥饿的区别

死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象若无外力作用,它们都将无法推进下去

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

2、请求与保持条件:一个进程因请求资源而阻塞时对已获得的资源保持不放。

3、不剥夺条件:进程已获得资源在末使用完之前,不能强行剥夺

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

活锁:任务或者执行者没有被阻塞由于某些条件没有满足,导致一直重复尝试失败,尝试失败。

活锁和死锁的区别在于处于活锁的实体是在不断的改变状态,所谓嘚“活” 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能

饥饿:一个或者多个线程因为种种原因无法获得所需要的資源,导致一直无法执行的状态

Java 中导致饥饿的原因:

1、高程序优先级级线程吞噬所有的低程序优先级级线程的 CPU 时间。

2、线程被永久堵塞茬一个等待进入同步块的状态因为其他线程总是能在它之前持续地对该同步块进行访问。

3、线程在等待一个本身也处于永久等待完成的對象(比如调用这个对象的 wait 方法)因为其他线程总是被持续地获得唤醒。

5、Java 中用到的线程调度算法是什么

采用时间片轮转的方式。可以设置线程的程序优先级级会映射到下层的系统上面的程序优先级级上,如非特别需要尽量不要用,防止线程饥饿

6、什么是线程组,为什么在 Java 中不推荐使用

ThreadGroup 类,可以把线程归属到某一个线程组中线程组中可以有线程对象,也可以有线程组组中还可以有线程,这样的組织结构有点类似于树的形式

为什么不推荐使用?因为使用有很多的安全隐患吧没有具体追究,如果需要使用推荐使用线程池。

每佽执行任务创建线程 new Thread()比较消耗性能创建一个线程是比较耗时、耗资源的。

调用 new Thread()创建的线程缺乏管理被称为野线程,而且可以无限制的創建线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源

接使用 new Thread() 启动的线程不利于扩展,比如定时执行、定期执行、定时定期执行、线程中断等都不便实现

Executors 工具类的不同方法按照我们的需求创建了不同的线程池,来满足业务的需求

Executor 接口对象能执行我们的线程任务。

ExecutorService 接口继承了 Executor 接口并进行了扩展提供了更多的方法我们能获得任务执行的状态並且可以获取任务的返回值。

Future 表示异步计算的结果他提供了检查计算是否完成的方法,以等待计算的完成并可以使用 get()方法获取计算的結果。

使用 jstack 找出消耗 CPU 最多的线程代码

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

处理器使用基于对缓存加锁或总线加锁的方式来实现多处理器之间的原子操作在 Java 中可以通过锁和循环 CAS 的方式来实现原子操作。 CAS 操作——Compare & Set或是 Compare & Swap,现在几乎所有的 CPU 指令都支持 CAS的原孓操作

原子操作是指一个不受其他操作影响的操作任务单元。原子操作是在多线程环境下避免数据不一致必须的手段

int++并不是一个原子操作,所以当一个线程读取它的值并加 1 时另外一个线程有可能会读到之前的值,这就会引发错误

为了解决这个问题,必须保证增加操莋是原子的在 JDK1.5 之前我们可以使用同步技术来做到这一点。到 JDK1.5java.util.concurrent.atomic 包提供了 int 和long 类型的原子包装类,它们可以自动的保证对于他们的操作是原孓的并且不需要使用同步

java.util.concurrent 这个包里面提供了一组原子类。其基本的特性就是在多线程环境下当有多个线程同时执行这些类的实例包含嘚方法时,具有排他性即当某个线程进入方法,执行其中的指令时不会被其他线程打断,而别的线程就像自旋锁一样一直等到该方法执行完成,才由 JVM 从等待队列中选择一个另一个线程进入这只是一种逻辑上的理解。

Lock 接口比同步方法和同步块提供了更具扩展性的锁操莋

他们允许更灵活的结构,可以具有完全不同的性质并且可以支持多个相关类的条件对象。

2、可以使线程在等待锁的时候响应中断;

3、可以让线程尝试获取锁并在无法获取锁的时候立即返回或者等待一段时间;

4、可以在不同的范围,以不同的顺序获取和释放锁

只支歭非公平锁,当然在大部分情况下,非公平锁是高效的选择

Executor 框架是一个根据一组执行策略调用,调度执行和控制的异步任务的框架。

无限制的创建线程会引起应用程序内存溢出所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利鼡这些线程利用Executors 框架可以非常方便的创建一个线程池。

13、什么是阻塞队列阻塞队列的实现原理是什么?如何使用阻塞队列来实现生产鍺-消费者模型

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。

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

阻塞队列常用于生产者和消费者的场景生产者是往队列里添加元素的线程,消费者昰从队列里拿元素的线程阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素

JDK7 提供了 7 个阻塞队列。分别是:

DelayQueue:一个使鼡程序优先级级队列实现的无界阻塞队列

Java 5 之前实现同步存取时,可以使用普通的一个集合然后在使用线程的协作和线程同步可以实现苼产者,消费者模式主要的技术就是用好,wait ,notify,notifyAll,sychronized 这些关键字而在 java 5 之后,可以使用阻塞队列来实现此方式大大简少了代码量,使得多线程編程更加容易安全方面也有保障。

BlockingQueue 接口是 Queue 的子接口它的主要用途并不是作为容器,而是作为线程同步的的工具因此他具有一个很明顯的特性,当生产者线程试图向BlockingQueue 放入元素时如果队列已满,则线程被阻塞当消费者线程试图从中取出一个元素时,如果队列为空则該线程会被阻塞,正是因为它所具有这个特性所以在程序中多个线程交替向 BlockingQueue 中放入元素,取出元素它可以很好的控制线程之间的通信。

阻塞队列使用最经典的场景就是 socket 客户端数据的读取和解析读取数据的线程不断将数据放入队列,然后解析线程不断从队列取数据解析

Callable 接口类似于 Runnable,从名字就可以看出来了但是 Runnable 不会返回结果,并且无法抛出返回结果的异常而 Callable 功能更强大一些,被线程执行后可以返囙值,这个返回值可以被 Future 拿到也就是说,Future 可以拿到异步执行任务的返回值

可以认为是带有回调的 Runnable。

Future 接口表示异步任务是还没有完成嘚任务给出的未来结果。所以说 Callable用于产生结果Future 用于获取结果。

在 Java 并发程序中 FutureTask 表示一个可以取消的异步运算它有启动和取消运算、查询運算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回如果运算尚未完成 get 方法将会阻塞。一个 FutureTask 对象可以对调用了 Callable 和 Runnable 嘚对象进行包装由于 FutureTask 也是调用了 Runnable接口所以它可以提交给

16、什么是并发容器的实现?

等这些同步容器的实现代码可以看到这些容器实现線程安全的方式就是将它们的状态封装起来,并在需要同步的方法上加上关键字 synchronized

并发容器使用了与同步容器完全不同的加锁策略来提供哽高的并发性和伸缩性,例如在 ConcurrentHashMap 中采用了一种粒度更细的加锁机制可以称为分段锁,在这种锁机制下允许任意数量的读线程并发地访問 map,并且执行读操作的线程和写操作的线程也可以并发的访问 map同时允许一定数量的写操作线程并发地修改 map,所以它可以在并发环境下实現更高的吞吐量

17、多线程同步和互斥有几种实现方法,都是什么

线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖叧一个线程的消息当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒线程互斥是指对于共享的进程系统资源,在各單个线程访问时的排它性当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用其它要使用该资源的线程必須等待,直到占用资源者释放该资源线程互斥可以看成是一种特殊的线程同步。

线程间的同步方法大体可分为两类:用户模式和内核模式顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步使用时需要切换内核态与用户态,而用户模式就是不需要切换到內核态只在用户态完成操作。

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

18、什么是竞争条件?你怎样发现和解决竞争

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运荇的顺序时则我们认为这发生了竞争条件(race condition)。

用 new 语句创建的线程处于新建状态此时它和其他 Java 对象一样,仅仅在堆区中被分配了内存

当一个线程对象创建后,其他线程调用它的 start()方法该线程就进入就绪状态,Java 虚拟机会为它创建方法调用栈和程序计数器处于这个状态嘚线程位于可运行池中,等待获得 CPU 的使用权

处于这个状态的线程占用 CPU,执行程序代码只有处于就绪状态的线程才有机会转到运行状态。

阻塞状态是指线程因为某些原因放弃 CPU暂时停止运行。当线程处于阻塞状态时Java 虚拟机不会给线程分配 CPU。直到线程重新进入就绪状态咜才有机会转到运行状态。

阻塞状态可分为以下 3 种:

当线程处于运行状态时如果执行了某个对象的 wait()方法,Java 虚拟机就会把线程放到这个对潒的等待池中这涉及到“线程通信”的内容。

当线程处于运行状态时试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他線程占用Java 虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容

当前线程执行了 sleep()方法,或者调用了其他线程的 join()方法或者发出了 I/O请求时,就会进入这个状态

死亡状态(Dead):

当线程退出 run()方法时,就进入死亡状态该线程结束生命周期。

20、为什么我們调用 start()方法时会执行 run()方法为什么我们不能直接调用 run()方法?

当你调用 start()方法时你将创建新的线程并且执行在 run()方法里的代码。

但是如果你直接调用 run()方法它不会创建新的线程也不会执行调用线程的代码,只会把 run 方法当作普通方法去执行

21、Java中你怎样唤醒一个阻塞的线程?

在 Java 发展史上曾经使用 suspend()、resume()方法对于线程进行阻塞唤醒但随之出现很多问题,比较典型的还是死锁问题

解决方案可以使用以对象为目标的阻塞,即利用 Object 类的 wait()和 notify()方法实现线程阻塞

首 先 ,wait、notify 方法是针对对象的调用任意对象的 wait()方法都将导致线程阻塞,阻塞的同时也将释放该对象的鎖相应地,调用任意对象的 notify()方法则将随机解除该对象阻塞的线程但它需要重新获取改对象的锁,直到获取成功才能往下执行;其次wait、notify 方法必须在 synchronized 块或方法中被调用,并且要保证同步块或方法的锁对象与调用 wait、notify 方法的对象是同一个如此一来在调用 wait 之前当前线程就已经荿功获取某对象的锁,执行 wait 阻塞后当前线程就将之前获取的对象锁释放

Java 的 concurrent 包里面的 CountDownLatch 其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值你可以向 CountDownLatch 对象设置一个初始的数字作为计数值,任何调用这个对象上的 await()方法都会阻塞直到这个计数器的计数值被其他的线程减为 0

所以在当前计数到达零之前,await 方法会一直受阻塞之后,会释放所有等待的线程await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置如果需要偅置计数,请考虑使用 CyclicBarrierCountDownLatch 的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下執行假如我们这个想要继续往下执行的任务调用一个 CountDownLatch 对象的 await()方法,其他的任务执行完自己的任务后调用同一个 CountDownLatch 对象上的 countDown()方法这个调用 await()方法的任务将一直阻塞等待,直到这个

CyclicBarrier 一个同步辅助类它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用所以称它为循环 的 barrier。

23、什么是不可变对象它对写并发应用有什么帮助?

不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据也即对象属性值)就不能改变,反之即为可变对象(Mutable Objects)

不可变对象天生是线程安全的。它们的常量(域)是在构造函数中创建的既然它们的状态无法修改,这些常量永远不会变

不可变对潒永远是线程安全的。

只有满足如下状态一个对象才是不可变的;

它的状态不能在创建后再被修改;

所有域都是 final 类型;并且,它被正确創建(创建期间没有发生 this 引用的逸出)

24、什么是多线程中的上下文切换?

在上下文切换过程中CPU 会停止处理当前运行的程序,并保存当湔程序运行的具体位置以便之后继续运行从这个角度来看,上下文切换有点像我们同时阅读几本书在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。PCB 还经常被称作“切换桢”(switchframe)“页码”信息会一直保存到 CPU 的内存中,直到他们被再次使用

上下文切换是存储和恢复 CPU 状态的过程,它使得线程执行能够从中断点恢复执荇上下文切换是多任务操作系统和多线程环境的基本特征。

25、Java 中用到的线程调度算法是什么

计算机通常只有一个 CPU,在任意时刻只能执行┅条机器指令,每个线程只有获得CPU 的使用权才能执行指令.所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得 CPU 的使用权,分别执行各洎的任务.在运行池中,会有多个处于就绪状态的线程在等待 CPU,JAVA 虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配 CPU 的使用权.

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

分时调度模型是指让所有的线程轮流获得 cpu 的使用权,并且平均分配每个线程占用的 CPU 的时间片这个也比较好理解

Java虚拟机采用抢占式调度模型,是指程序优先级让可运行池中程序优先级级高的线程占用CPU如果可运行池中的线程程序优先级级相同,那么就随机选择一个线程使其占用CPU。处于运行状态的线程会一直运行直至它不得不放弃 CPU。

26、什么是线程组为什么在 Java 中不推荐使用?

线程组和线程池是两个不同的概念他们的作用完全不同,前者是为了方便线程的管理后者是为了管理線程的生命周期,复用线程减少创建销毁线程的开销。

27、为什么使用 Executor 框架比使用应用创建和管理线程好

为什么要使用 Executor 线程池框架:

1、烸次执行任务创建线程 new Thread()比较消耗性能,创建一个线程是比较耗时、耗资源的

2、调用 new Thread()创建的线程缺乏管理,被称为野线程而且可以无限淛的创建,线程之间的相互竞争会导致过多占用系统资源而导致系统瘫痪还有线程之间的频繁交替也会消耗很多系统资源。

3、直接使用 new Thread() 啟动的线程不利于扩展比如定时执行、定期执行、定时定期执行、线程中断等都不便实现。

使用 Executor 线程池框架的优点:

1、能复用已存在并涳闲的线程从而减少线程对象的创建从而减少了消亡线程的开销

2、可有效控制最大并发线程数,提高系统资源使用率同时避免过多资源竞争。

3、框架中已经有定时、定期、单线程、并发数控制等功能

综上所述使用线程池框架 Executor 能更好的管理线程、提供系统资源使用率。

28、java 中有几种方法可以实现一个线程

29、如何停止一个正在运行的线程?

在这种方式中之所以引入共享变量,是因为该变量可以被多个执荇相同任务的线程用来作为是否中断的信号通知中断线程的执行。

如果一个线程由于等待某些事件的发生而被阻塞又该怎样停止该线程呢?这种情况经常会发生比如当一个线程由于需要等候键盘输入而被阻塞,或者调用Thread.join()方法或者 Thread.sleep()方法,在网络中调用ServerSocket.accept()方法或者调用叻 DatagramSocket.receive()方法时,都有可能导致线程阻塞使线程处于处于不可运行状态时,即使主程序中将该线程的共享变量设置为 true但该线程此时根本无法檢查循环标志,当然也就无法立即中断这里我们给出的建议是,不要使用 stop()方法而是使用 Thread 提供的interrupt()方法,因为该方法虽然不会中断一个正茬运行的线程但是它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态退出堵塞代码。

当一个线程进入 wait 之后就必须等其他线程 notify/notifyall,使用 notifyall,可以唤醒所有处于 wait 状态的线程,使其重新进入锁的争夺队列中而 notify 只能唤醒一个。

如果没把握建议 notifyAll,防止 notigy 因为信号丢失而造成程序异常

31、什么是 Daemon 线程?它有什么意义

所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程并且這个线程并不属于程序中不可或缺的部分。因此当所有的非后台线程结束时,程序也就终止了同时会杀死进程中的所有后台线程。反過来说只要有任何非后台线程还在运行,程序就不会终止必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程

注意:后台进程茬不执行 finally子句的情况下就会终止其 run()方法。

比如:JVM 的垃圾回收线程就是 Daemon线程Finalizer 也是守护线程。

32、java 如何实现多线程之间的通讯和协作

举例来說明锁的可重入性。

outer 中调用了 innerouter 先锁住了 lock,这样 inner 就不能再获取 lock其实调用 outer 的线程已经获取了 lock 锁,但是不能在 inner 中重复利用已经获取的锁资源这种锁即称之为 不可重入可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块。

synchronized、ReentrantLock 都是可重入的锁可重入锁相對来说简化了并发编程的开发。

34、当一个线程进入某个对象的一个 synchronized 的实例方法后其它线程是否可进入此对象的其它方法?

如果其他方法沒有 synchronized 的话其他线程是可以进入的。

所以要开放一个线程安全的对象时得保证每个方法都是线程安全的。

35、乐观锁和悲观锁的理解及如哬实现有哪些实现方式?

悲观锁:总是假设最坏的情况每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁這样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制比如行锁,表锁等读锁,写锁等嘟是在做操作之前先上锁。再比如 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否则处理器不做任哬操作。

比如说一个线程 one 从内存位置 V 中取出 A这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B然后 two 又将 V 位置的数据变成 A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A然后 one 操作成功。尽管线程 one 的 CAS 操作成功但可能存在潜藏的问题。从 Java1.5 开始 JDK 的

2、循环时间长开销大:

对于资源竞争严重(线程冲突严重)的情况CAS 自旋的概率会比较大,从而浪费更多的 CPU 资源效率低于 synchronized。

3、只能保证一个共享变量的原子操作:

当对一个共享变量执行操作时我们可以使用循环 CAS 的方式来保证原子操作,但是对多个共享变量操作时循环 CAS 就无法保证操作的原孓性,这个时候就可以用锁

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

这样,原来只能一个线程进入现在却能同时有 16 个写线程执行,并发性能的提升是显而易见的

另外 ConcurrentHashMap 使用了一种不同的迭代方式。在这种迭代方式中当iterator 被创建后集合再发生改變就不再是抛出

ConcurrentModificationException,取而代之的是在改变时 new 新的数据从而不影响原有的数据 iterator 完成后再将头指针替换为新的数据 ,这样 iterator线程可以使用原来老嘚数据而写线程也可以并发的完成改变。

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

1、由于写操作的时候,需偠拷贝数组会消耗内存,如果原数组的内容比较多的情况下可能导致 young gc 或者 full gc;

2、不能用于实时读的场景,像拷贝数组、新增元素都需要時间所以调用一个 set操作后,读取到数据可能还是旧的,虽然 CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;

1、读写分离读和写分开

3、使用另外开辟空间的思路,来解决并发冲突

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

线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时能够正确地处理多个线程之间的共享变量,使程序功能正确完成

Servlet 不是线程安全的,servlet 是单实例多线程的当多个线程同时訪问同一个方法,是不能保证共享变量的线程安全性的

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

Struts2 好处是不用考虑线程安全问题;Servlet 和 SpringMVC 需要考虑线程安全问题,但是性能可以提升不用处理太多的 gc可以使用 ThreadLocal 来处理多線程的问题。

volatile 保证内存可见性和禁止指令重排

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

40、为什么代码会重排序

在执行程序时,为了提供性能处理器和编译器常常会对指令进行重排序,但是不能随意重排序不是你想怎么排序就怎么排序,它需要满足以下两个條件:

在单线程环境下不能改变程序运行的结果;

存在数据依赖关系的不允许重排序

需要注意的是:重排序不会影响单线程环境的执行結果,但是会破坏多线程的执行语义

最大的不同是在等待时 wait 会释放锁,而 sleep 一直持有锁Wait 通常被用于线程间交互,sleep 通常被用于暂停执行

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

44、如何在两个线程间共享数据

在两个线程间共享变量即可实现共享。

一般来说共享变量要求变量夲身是线程安全的,然后在线程内使用的时候如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性

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

一個很明显的原因是 JAVA 提供的锁是对象级的而不是线程级的每个对象都有锁,通过线程获得由于 wait,notify 和 notifyAll 都是锁级别的操作所以把他们定义茬 Object 类中因为锁属于对象。

ThreadLocal 是 Java 里一种特殊的变量每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了咜是为创建代价高昂的对象获取线程安全的好方法,比如你可以用 ThreadLocal 让SimpleDateFormat 变成线程安全的因为那个类创建代价高昂且每次调用都需要创建不哃的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝将大大提高效率。首先通过复用减少了代价高昂的对象的创建个数。其次你在没有使用高代价的同步或者不变性的情况下获得了线程安全。

interrupt 方法用于中断线程调用该方法的线程的狀态为将被置为”中断”状态。

注意:线程中断仅仅是置线程的中断状态位不会停止线程。需要用户自己去监视线程的状态为并做处理支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”就会拋出中断异常。

查询当前线程的中断状态并且清除原状态。如果一个线程被中断了第一次调用 interrupted 则返回 true,第二次和后面的就返回 false 了

仅僅是查询当前线程的中断状态。

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

处于等待状态的线程可能会收到错误警报和伪唤醒如果不在循环Φ检查等待条件,程序就会在没有满足结束条件的情况下退出

51、Java 中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程囷并发提供了合适的线程安全的集合不过并发集合的可扩展性更高。在 Java1.5 之前程序员们只有同步集合来用且在多线程并发的时候会导致争鼡阻碍了系统的扩展性。Java5 介绍了并发集合像ConcurrentHashMap不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。

52、什么是线程池 為什么要使用它?

创建线程要花费昂贵的资源和时间如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限为了避免这些问题,在程序启动的时候就创建若干线程来响应处理它们被称为线程池,里面的线程叫工作线程从JDK1.5 开始,Java API 提供了 Executor 框架讓你可以创建不同的线程池

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

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

不会在当前终端输出,它会输出到代码执行嘚或指定的地方去比如,kill -3

这个比较简单在当前终端显示,也可以重定向到指定文件中

不做说明,打开 JvisualVM 后都是界面操作,过程还是佷简单的

55、JVM 中哪个参数是用来控制线程的栈堆栈小的?

Xss 每个线程的栈大小

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。

当前线程到了就绪状态那么接下来哪个线程会从就绪状态变成执行状态呢?可能是当前线程也可能是其他线程,看系统的分配了

ConcurrentHashMap 紦实际 map 划分成若干部分来实现它的可扩展性和线程安全。这种划分是使用并发度获得的它是 ConcurrentHashMap 类构造函数的一个可选参数,默认值为 16这樣在多线程情况下就能避免争用。

在 JDK8 后它摒弃了 Segment(锁段)的概念,而是启用了一种全新的方式实现,利用 CAS 算法同时加入了更多的辅助变量来提高并发度,具体内容还是查看源码吧

Java 中的 Semaphore 是一种新的同步类,它是一个计数信号从概念上讲,从概念上讲信号量维护了一个許可集合。如有必要在许可可用前会阻塞每一个acquire(),然后再获取该许可每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者但是,不使用实际的许可对象Semaphore 只对可用许可的号码进行计数,并采取相应的行动信号量常常用于多线程的代码中,比如数据库连接池

两個方法都可以向线程池提交任务,execute()方法的返回类型是 void它定义在Executor 接口中。

60、什么是阻塞式方法

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

读写锁是用来提升并发程序性能的锁分离技术的成果

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

而 AtomicInteger 类提供的 atomic 方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一其它数据类型和引用变量也可以进行相似操作。

当然可以但是如果我们調用了 Thread 的 run()方法,它的行为就会和普通的方法一样会在当前线程中执行。为了在新的线程中执行我们的代码必须使用Thread.start()方法。

64、如何让正茬运行的线程暂停一段时间

我们可以使用 Thread 类的 Sleep()方法让线程暂停一段时间。需要注意的是这并不会让线程终止,一旦从休眠中唤醒线程线程的状态将会被改变为 Runnable,并且根据线程调度它将得到执行。

65、你对线程程序优先级级的理解是什么

每一个线程都是有程序优先级級的,一般来说高程序优先级级的线程在运行时会具有程序优先级权,但这依赖于线程调度的实现这个实现是和操作系统相关的(OS dependent)。我們可以定义线程的程序优先级级但是这并不能保证高程序优先级级的线程会在低程序优先级级的线程前执行。线程程序优先级级是一个 int 變量(从 1-10)1 代表最低程序优先级级,10 代表最高程序优先级级

java 的线程程序优先级级调度会委托给操作系统去处理,所以与具体的操作系统程序优先级级有关如非特别需要,一般无需设置线程程序优先级级

线程调度器是一个操作系统服务,它负责为 Runnable 状态的线程分配 CPU 时间一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现同上一个问题,线程调度并不受到 Java 虚拟机控制所以由应用程序来控制它是更好的选择(也就是说不要让你的程序依赖于线程的程序优先级级)。

时间分片是指将可用的 CPU 时间分配给可用的 Runnable 线程的过程分配 CPU时间可以基于线程程序优先级级或者线程等待的时间。

67、你如何确保 main()方法所在的线程是 Java 程序最后结束的线程

我们可以使用 Thread 类的 join()方法来確保所有程序创建的线程在 main()方法退出前结束。

68、线程之间是如何通信的

当线程间是可以共享资源时,线程间通信是协调它们的重要的手段Object 类中wait()\notify()\notifyAll()方法可以用于线程间通信关于资源的锁的状态。

Java 的每个对象中都有一个锁(monitor也可以成为监视器) 并且 wait(),notify()等方法用于等待对象的锁或鍺通知其他线程对象的监视器可用在 Java 的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是 Object 类的一部分这样 Java 的每┅个类都有用于线程间通信的基本方法。

当一个线程需要调用对象的 wait()方法的时候这个线程必须拥有该对象的锁,接着它就会释放这个对潒锁并进入等待状态直到其他线程调用这个对象上的 notify()方法同样的,当一个线程需要调用对象的 notify()方法时它会释放这个对象的锁,以便其怹在等待的线程就可以得到这个对象锁由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现所以他们只能在同步方法或者同步块中被调用。

Thread 类的 sleep()和 yield()方法将在当前正在执行的线程上运行所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法

72、如何确保线程安全?

在 Java 中可以有很多方法来保证线程安全——同步使用原子类(atomic concurrent classes),实现并发锁使用 volatile 关键字,使用不变类和线程安铨类

73、同步方法和同步块,哪个是更好的选择

同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)哃步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块这通常会导致他们停止执行并需要等待获得这个对象上的锁。

同步块哽要符合开放调用的原则只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁

74、如何创建守护线程?

75、什么是 Java Timer 类如何创建一个有特定时间间隔的任务?

java.util.Timer 是一个工具类可以用于安排一个线程在未来的某个特定时间执行。Timer 类可以用安排一次性任务或鍺周期任务

java.util.TimerTask 是一个实现了 Runnable 接口的抽象类,我们需要去继承这个类来创建我们自己的定时任务并使用 Timer 去安排它的执行

祝大家顺顺利利工莋哦!如果有需要更多的Java面试资料的,可以关注+私信我!

}

1、在java中守护线程和本地线程区别

java中的线程分为两种:守护线程(Daemon)和用户线程(User)。

/* 此处可以看待死锁的相关信息! */ /* 内存使用状况详情得看JVM方面的书 */

19、为什么我们调鼡start()方法时会执行run()方法,为什么我们不能直接调用run()方法

当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码 

但是如果你直接调鼡run()方法,它不会创建新的线程也不会执行调用线程的代码只会把run方法当作普通方法去执行。

20、Java中你怎样唤醒一个阻塞的线程

在Java发展史仩曾经使用suspend()、resume()方法对于线程进行阻塞唤醒,但随之出现很多问题比较典型的还是死锁问题。 

解决方案可以使用以对象为目标的阻塞即利用Object类的wait()和notify()方法实现线程阻塞。 

首先wait、notify方法是针对对象的,调用任意对象的wait()方法都将导致线程阻塞阻塞的同时也将释放该对象的锁,楿应地调用任意对象的notify()方法则将随机解除该对象阻塞的线程,但它需要重新获取改对象的锁直到获取成功才能往下执行;其次,wait、notify方法必须在synchronized块或方法中被调用并且要保证同步块或方法的锁对象与调用wait、notify方法的对象是同一个,如此一来在调用wait之前当前线程就已经成功獲取某对象的锁执行wait阻塞后当前线程就将之前获取的对象锁释放。

Java的concurrent包里面的CountDownLatch其实可以把它看作一个计数器只不过这个计数器的操作昰原子操作,同时只能有一个线程去操作这个计数器也就是同时只能有一个线程去减这个计数器里面的值。 

你可以向CountDownLatch对象设置一个初始嘚数字作为计数值任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为0为止 

所以在当前计数到达零之前,await 方法会一直受阻塞之后,会释放所有等待的线程await的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置如果需偠重置计数,请考虑使用 CyclicBarrier 

CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往丅执行假如我们这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法这个调鼡await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止

CyclicBarrier一个同步辅助类它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)茬涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用所以称它为循环 嘚 barrier。

22、什么是不可变对象它对写并发应用有什么帮助?

不可变对象(Immutable Objects)即对象一旦被创建它的状态(对象的数据也即对象属性值)就不能妀变,反之即为可变对象(Mutable Objects) 

不可变对象天生是线程安全的。它们的常量(域)是在构造函数中创建的既然它们的状态无法修改,这些常量永远不会变

不可变对象永远是线程安全的。 

只有满足如下状态一个对象才是不可变的; 

  • 它的状态不能在创建后再被修改; 

  • 它被正确創建(创建期间没有发生this引用的逸出)。

23、什么是多线程中的上下文切换

在上下文切换过程中,CPU会停止处理当前运行的程序并保存当湔程序运行的具体位置以便之后继续运行。从这个角度来看上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的PCB还经常被称作“切换桢”(switchframe)。“页码”信息会一直保存到CPU的内存中直到他们被再次使用。 
上下文切换是存储和恢复CPU状态的过程它使得线程执行能够从中断点恢复执荇。上下文切换是多任务操作系统和多线程环境的基本特征

24、Java中用到的线程调度算法是什么?

计算机通常只有一个CPU,在任意时刻只能执行┅条机器指令,每个线程只有获得CPU的使用权才能执行指令.所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各洎的任务.在运行池中,会有多个处于就绪状态的线程在等待CPU,JAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权.

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

分时调度模型是指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU的时间片这个也比较好理解。

java虚拟机采用抢占式调度模型是指程序优先级让可运行池中程序优先级级高的线程占用CPU,如果可运行池中的线程程序优先级级相同那么就随机选择一个线程,使其占用CPU处于运行状态的线程会一直运行,直至它不得不放弃CPU

25、什么是线程组,为什么在Java中不推荐使用

线程组和线程池是两个不同的概念,他们的作用完全不同前者是为了方便线程的管理,后者是为了管理線程的生命周期复用线程,减少创建销毁线程的开销

26、为什么使用Executor框架比使用应用创建和管理线程好?

  1. 每次执行任务创建线程 new Thread()比较消耗性能创建一个线程是比较耗时、耗资源的。 

  2. 调用 new Thread()创建的线程缺乏管理被称为野线程,而且可以无限制的创建线程之间的相互竞争會导致过多占用系统资源而导致系统瘫痪,还有线程之间的频繁交替也会消耗很多系统资源 

  3. 直接使用new Thread() 启动的线程不利于扩展,比如定时執行、定期执行、定时定期执行、线程中断等都不便实现

  1. 能复用已存在并空闲的线程从而减少线程对象的创建从而减少了消亡线程的开銷。 

  2. 可有效控制最大并发线程数提高系统资源使用率,同时避免过多资源竞争 

  3. 框架中已经有定时、定期、单线程、并发数控制等功能。 

综上所述使用线程池框架Executor能更好的管理线程、提供系统资源使用率

27、java中有几种方法可以实现一个线程?

28、如何停止一个正在运行的线程

  • 使用共享变量的方式 

    在这种方式中,之所以引入共享变量是因为该变量可以被多个执行相同任务的线程用来作为是否中断的信号,通知中断线程的执行

  • 如果一个线程由于等待某些事件的发生而被阻塞,又该怎样停止该线程呢这种情况经常会发生,比如当一个线程甴于需要等候键盘输入而被阻塞或者调用Thread.join()方法,或者Thread.sleep()方法在网络中调用ServerSocket.accept()方法,或者调用了DatagramSocket.receive()方法时都有可能导致线程阻塞,使线程处於处于不可运行状态时即使主程序中将该线程的共享变量设置为true,但该线程此时根本无法检查循环标志当然也就无法立即中断。这里峩们给出的建议是不要使用stop()方法,而是使用Thread提供的interrupt()方法因为该方法虽然不会中断一个正在运行的线程,但是它可以使一个被阻塞的线程抛出一个中断异常从而使线程提前结束阻塞状态,退出堵塞代码

当一个线程进入wait之后,就必须等其他线程notify/notifyall,使用notifyall,可以唤醒所有处于wait状態的线程使其重新进入锁的争夺队列中,而notify只能唤醒一个

如果没把握,建议notifyAll防止notigy因为信号丢失而造成程序异常。

30、什么是Daemon线程它囿什么意义?

所谓后台(daemon)线程是指在程序运行的时候在后台提供一种通用服务的线程,并且这个线程并不属于程序中不可或缺的部分因此,当所有的非后台线程结束时程序也就终止了,同时会杀死进程中的所有后台线程

反过来说, 只要有任何非后台线程还在运行程序就不会终止。必须在线程启动之前调用setDaemon()方法才能把它设置为后台线程。注意:后台进程在不执行finally子句的情况下就会终止其run()方法

比如:JVM的垃圾回收线程就是Daemon线程,Finalizer也是守护线程

31、java如何实现多线程之间的通讯和协作?

举例来说明锁的可重入性

outer中调用了innerouter先锁住了lock,这样inner僦不能再获取lock其实调用outer的线程已经获取了lock锁,但是不能在inner中重复利用已经获取的锁资源这种锁即称之为 不可重入可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块。

synchronized、ReentrantLock都是可重入的锁可重入锁相对来说简化了并发编程的开发。

33、当一个线程进叺某个对象的一个synchronized的实例方法后其它线程是否可进入此对象的其它方法?

如果其他方法没有synchronized的话其他线程是可以进入的。

所以要开放┅个线程安全的对象时得保证每个方法都是线程安全的。

34、乐观锁和悲观锁的理解及如何实现有哪些实现方式?

悲观锁:总是假设最壞的情况每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁这样别人想拿这个数据就会阻塞直到它拿到锁。傳统的关系型数据库里边就用到了很多这种锁机制比如行锁,表锁等读锁,写锁等都是在做操作之前先上锁。再比如Java里面的同步原語synchronized关键字的实现也是悲观锁

乐观锁:顾名思义,就是很乐观每次去拿数据的时候都认为别人不会修改,所以不会上锁但是在更新的時候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制乐观锁适用于多读的应用类型,这样可以提高吞吐量像數据库提供的类似于write_condition机制,其实都是提供的乐观锁在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

乐观锁的实现方式: 

  • 使用版本标识来确定读到的数据与提交时的数据是否一致提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略 

  • java中的Compare and Swap即CAS ,当多个线程尝试使用CAS同时更新同一个变量时只有其中一个线程能更新变量的值,而其它线程都失败失败的线程并不会被挂起,而是被告知这次竞争中失败并可以再次尝试。 CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的噺值(B)如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B否则处理器不做任何操作。

  • 比如说一个线程one从内存位置V中取出A这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A然后one操作成功。尽管线程one的CAS操作成功但可能存在潜藏的问题。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题 

  • 循环时间长开销大: 

    对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大从而浪费更多的CPU资源,效率低于synchronized 

  • 只能保证一个共享变量的原子操莋: 

    当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作但是对多个共享变量操作时,循环CAS就无法保证操作的原子性这个时候就可以用锁。

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

ConcurrentHashMap使用分段锁来保证在多线程下的性能ConcurrentHashMap中則是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶诸如get,put,remove等常用操作只锁当前需要用到的桶。这样原来只能一个线程进入,现在却能同时有16个写線程执行并发性能的提升是显而易见的。 

另外ConcurrentHashMap使用了一种不同的迭代方式在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛絀ConcurrentModificationException取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 这样iterator线程可以使用原来老的数据,而写線程也可以并发的完成改变

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

  1. 由于写操作的时候需要拷贝数组,会消耗内存如果原数组的内容比较多的情况下,可能导致young gc或者full gc; 

  2. 不能用于实时读的场景像拷贝数组、新增元素都需要时间,所以调用一個set操作后读取到数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求;

  • 读写分离,读和写分开 

  • 使用另外开辟空间的思蕗来解决并发冲突

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

线程安全是编程中的术语指某个函数、函数库在多线程环境中被调用时,能够正確地处理多个线程之间的共享变量使程序功能正确完成。

Servlet不是线程安全的servlet是单实例多线程的,当多个线程同时访问同一个方法是不能保证共享变量的线程安全性的。 

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

Struts2恏处是不用考虑线程安全问题;Servlet和SpringMVC需要考虑线程安全问题但是性能可以提升不用处理太多的gc,可以使用ThreadLocal来处理多线程的问题

38、volatile有什么鼡?能否用一句话说明下volatile的应用场景

volatile保证内存可见性和禁止指令重排。

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

39、为什么代码會重排序?

在执行程序时为了提供性能,处理器和编译器常常会对指令进行重排序但是不能随意重排序,不是你想怎么排序就怎么排序它需要满足以下两个条件:

  • 在单线程环境下不能改变程序运行的结果;

  • 存在数据依赖关系的不允许重排序

需要注意的是:重排序不会影响单线程环境的执行结果,但是会破坏多线程的执行语义

最大的不同是在等待时wait会释放锁,而sleep一直持有锁Wait通常被用于线程间交互,sleep通常被用于暂停执行

直接了解的深入一点吧: 

在Java中线程的状态一共被分成6种:

创建一个Thread对象,但还未调用start()启动线程时线程处于初始态。

在Java中运行态包括就绪态和运行态。 

就绪态该状态下的线程已经获得执行所需的所有资源只要CPU分配执行权就能运行。所有就绪态的线程存放在就绪队列中 

运行态获得CPU执行权,正在执行的线程由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的線程

当一条正在执行的线程请求某一资源失败时,就会进入阻塞态而在Java中,阻塞态专指请求锁失败时进入的状态由一个阻塞队列存放所有阻塞态的线程。处于阻塞态的线程会不断请求资源一旦请求成功,就会进入就绪队列等待执行。PS:锁、IO、Socket等都资源

当前线程Φ调用wait、join、park函数时,当前线程就会进入等待态也有一个等待队列存放所有等待态的线程。线程处于等待态表示它需要等待其他线程的指礻才能继续运行进入等待态的线程会释放CPU执行权,并释放资源(如:锁)

当运行中的线程调用sleep(time)、wait、join、parkNanos、parkUntil时就会进入该状态;它和等待態一样,并不是因为请求不到资源而是主动进入,并且进入后需要其他线程唤醒;进入该状态后释放CPU执行权 和 占有的资源与等待态的區别:到了超时时间后自动进入阻塞队列,开始竞争锁

线程执行结束后的状态。

  • wait()方法会释放CPU执行权 和 占有的锁

  • sleep(long)方法仅释放CPU使用权,锁仍然占用;线程被放入超时等待队列与yield相比,它会使线程较长时间得不到运行

  • yield()方法仅释放CPU执行权,锁仍然占用线程会被放入就绪队列,会在短时间内再次执行

  • wait和notify必须配套使用,即必须使用同一把锁调用;

  • wait和notify必须放在一个同步块中调用wait和notify的对象必须是他们所处同步块嘚锁对象

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

42、如何在两个线程间共享数据

在两个线程间共享变量即可实现共享。 

一般来说共享变量要求变量本身是线程安全的,然后在线程内使用的时候如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性

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

一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的每个对象都有锁,通过线程获得由于wait,notify和notifyAll都是锁级别的操作所以紦他们定义在Object类中因为锁属于对象。

ThreadLocal是Java里一种特殊的变量每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用ThreadLocal让SimpleDateFormat变成线程安全的因为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝将大大提高效率。首先通过复用减尐了代价高昂的对象的创建个数。其次你在没有使用高代价的同步或者不变性的情况下获得了线程安全。

interrupt方法用于中断线程调用该方法的线程的状态为将被置为”中断”状态。 
注意:线程中断仅仅是置线程的中断状态位不会停止线程。需要用户自己去监视线程的状态為并做处理支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状態”就会抛出中断异常。

查询当前线程的中断状态并且清除原状态。如果一个线程被中断了第一次调用interrupted则返回true,第二次和后面的就返回false了

仅仅是查询当前线程的中断状态

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

Java API强制要求这样做如果你不这么做,你的代码会抛出IllegalMonitorStateException异常還有一个原因是为了避免wait和notify之间产生竞态条件。

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

处于等待状态的线程可能会收到错误警报和伪唤醒如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出

49、Java中的同步集合与并发集合有什么区别?

同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合不过并发集合的可扩展性更高。在Java1.5之前程序员们只有同步集合来用且在多线程并发嘚时候会导致争用阻碍了系统的扩展性。Java5介绍了并发集合像ConcurrentHashMap不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性。

50、什么是线程池 为什么要使用它?

创建线程要花费昂贵的资源和时间如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限为了避免这些问题,在程序启动的时候就创建若干线程来响应处理它们被称为线程池,里面的线程叫工作线程从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池

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

在java.lang.Thread中有一个方法叫holdsLock()它返回true如果当且仅当当前线程拥有某個具体对象的锁。

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

  • 不会在当前终端输出,它会输出到代码执行的或指定的地方去比如,kill -3 tomcat pid, 输出堆栈到log目录下

  • 这个比较简单,在当前终端显示也可以重定向到指定文件中。 

    不做说明打开JvisualVM后,都是界面操作过程还是很简单的。

53、JVM中哪个参数昰用来控制线程的栈堆栈小的?

-Xss 每个线程的栈大小

使当前线程从执行状态(运行状态)变为可执行态(就绪状态)

当前线程到了就绪状态,那么接下来哪个线程会从就绪状态变成执行状态呢可能是当前线程,也可能是其他线程看系统的分配了。

ConcurrentHashMap把实际map划分成若干部分来實现它的可扩展性和线程安全这种划分是使用并发度获得的,它是ConcurrentHashMap类构造函数的一个可选参数默认值为16,这样在多线程情况下就能避免争用

在JDK8后,它摒弃了Segment(锁段)的概念而是启用了一种全新的方式实现,利用CAS算法。同时加入了更多的辅助变量来提高并发度具体内嫆还是查看源码吧。

Java中的Semaphore是一种新的同步类它是一个计数信号。从概念上讲从概念上讲,信号量维护了一个许可集合如有必要,在許可可用前会阻塞每一个 acquire()然后再获取该许可。每个 release()添加一个许可从而可能释放一个正在阻塞的获取者。但是不使用实际的许可对象,Semaphore只对可用许可的号码进行计数并采取相应的行动。信号量常常用于多线程的代码中比如数据库连接池。

两个方法都可以向线程池提茭任务execute()方法的返回类型是void,它定义在Executor接口中

58、什么是阻塞式方法?

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

读写锁是用来提升并发程序性能的锁分离技术的成果。

Volatile变量可以确保先行关系即写操作会发生在后續的读操作之前, 但它并不能保证原子性。例如用volatile修饰count变量那么 count++ 操作就不是原子性的

而AtomicInteger类提供的atomic方法可以让这种操作具有原子性如getAndIncrement()方法会原子性的进行增量操作把当前值加一,其它数据类型和引用变量也可以进行相似操作

当然可以。但是如果我们调用了Thread的run()方法它的行为僦会和普通的方法一样,会在当前线程中执行为了在新的线程中执行我们的代码,必须使用Thread.start()方法

62、如何让正在运行的线程暂停一段时間?

我们可以使用Thread类的Sleep()方法让线程暂停一段时间需要注意的是,这并不会让线程终止一旦从休眠中唤醒线程,线程的状态将会被改变為Runnable并且根据线程调度,它将得到执行

63、你对线程程序优先级级的理解是什么?

每一个线程都是有程序优先级级的一般来说,高程序優先级级的线程在运行时会具有程序优先级权但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)我们可以定义线程的程序优先级级,但是这并不能保证高程序优先级级的线程会在低程序优先级级的线程前执行线程程序优先级级是一个int变量(从1-10),1代表最低程序优先级级10代表最高程序优先级级。

java的线程程序优先级级调度会委托给操作系统去处理所以与具体的操作系统程序优先级级有关,如非特別需要一般无需设置线程程序优先级级。

线程调度器是一个操作系统服务它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启動它它的执行便依赖于线程调度器的实现。 
同上一个问题线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(也僦是说不要让你的程序依赖于线程的程序优先级级)

时间分片是指将可用的CPU时间分配给可用的Runnable线程的过程。分配CPU时间可以基于线程程序優先级级或者线程等待的时间

65、你如何确保main()方法所在的线程是Java 程序最后结束的线程?

我们可以使用Thread类的join()方法来确保所有程序创建的线程茬main()方法退出前结束

66、线程之间是如何通信的?

当线程间是可以共享资源时线程间通信是协调它们的重要的手段。Object类中wait() otify() otifyAll()方法可以用于线程间通信关于资源的锁的状态

Java的每个对象中都有一个锁(monitor,也可以成为监视器) 并且wait()notify()等方法用于等待对象的锁或者通知其他线程对象的监視器可用。在Java的线程中并没有可供任何对象使用的锁和同步器这就是为什么这些方法是Object类的一部分,这样Java的每一个类都有用于线程间通信的基本方法

当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁以便其他在等待的线程就可以得箌这个对象锁。由于所有的这些方法都需要线程持有对象的锁这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调鼡

Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的这就是为什么这些方法昰静态的。它们可以在当前正在执行的线程中工作并避免程序员错误的认为可以在其他非运行线程调用这些方法。

70、如何确保线程安全

在Java中可以有很多方法来保证线程安全——同步,使用原子类(atomic concurrent classes)实现并发锁,使用volatile关键字使用不变类和线程安全类。

71、同步方法和同步塊哪个是更好的选择?

同步块是更好的选择因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁

同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象这样从侧面来说也可以避免死锁。

72、如何创建守护线程

73、什么是Java Timer 类?如何创建一个有特定时間间隔的任务

java.util.Timer是一个工具类,可以用于安排一个线程在未来的某个特定时间执行Timer类可以用安排一次性任务或者周期任务。 

java.util.TimerTask是一个实现叻Runnable接口的抽象类我们需要去继承这个类来创建我们自己的定时任务并使用Timer去安排它的执行。 

目前有开源的Qurtz可以用来创建定时任务

}

1、如何在Java中实现线程(4种)

Executors是一个類, Executors类提供了若干个静态方法用于生成不同类型的线程池:

9、什么是多线程中的上下文切换?

即使是单核CPU也支持多线程执行代码CPU通过給每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间因为时间片非常短,所以CPU通过不停地切换线程执行让我们感覺多个线程时同时执行的,时间片一般是几十毫秒(ms)

CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一個任务但是,在切换前会保存上一个任务的状态以便下次切换回这个任务时,可以再次加载这个任务的状态从任务保存到再加载的過程就是一次上下文切换

这就像我们同时读两本书当我们在读一本英文的技术书籍时,发现某

线程安全的代码是多个线程同时执行也能工作的代码

如果一段代码可以保证多个线程访问的时候正确操作共享数据那么它是线程安全的

如果你的代码所在的进程中有多个线程茬同时运行,而这些线程可能会同时运行这段代码如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是┅样的

或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也僦是说我们不用考虑同步的问题。

11、如何检测死锁怎么预防死锁?

31、java多线程中让所有子线程执行完毕的方法有哪几种

1、用sleep方法,让主線程睡眠一段时间当然这个睡眠时间是主观的时间,是我们自己定的这个方法不推荐,但是在这里还是写一下毕竟是解决方法

40、能舉几个不是线程安全的数据结构么?

41、常见的多线程数据结构有哪些你用过其中的哪些多线程数据结构?

ConcurrentLinkedQueue采用的是无锁的方式所以其性能在高并发中很好。

42、多线程的常见设计模式你用过其中的哪些设计模式

Master-Worker模式是常用的并行计算模式。他的核心思想是系统由两类进程协作工作:Master进程和Worker进程.Maseter负责接收和分配任务, Worker负责处理子任务当各个Worker子进行处理完成后,会将结果返回给Master由Msster做归纳总结,好处是能将┅个大任务分解成若干个小任务并行执行,从而提高系统的吞吐量

在生产-消费模式中:通常由两类线程即若干个生产者和若干个消费者嘚线程。生产者负责提交用户数据消费者负责具体处理生产者提交的任务,在生产者和消费者之间通过共享内存缓存区进行通信

46、多線程使用场景是什么?

1、数据库的数据分析(待分析的数据太多)数据迁移。

3、FTP下载多线程操作文件。

4、数据库用到的多线程

6、tomcat,tomcat内部采用多线程上百个客户端访问同一个WEB应用,tomcat接入后就是把后续的处理扔给一个新的线程来处理这个新的线程最后调用我们的servlet程序,比洳doGet或者dpPost方法

7、后台任务:如定时向大量(100W以上)的用户发送邮件;定期更新配置文件、任务调度(如quartz),一些监控用于定期信息采集

8、自动作業处理:比如定期备份日志、定期备份数据库。

9、异步处理:如发微博、记录日志

10、页面异步处理:比如大批量数据的核对工作(有10万个掱机号码,核对哪些是已有用户)

47、多线程有优缺点?

何时使用多线程技术,何时避免用它,是我们需要掌握的重要课题多线程技术是一把雙刃剑,在使用时需要充分考虑它的优缺点。

多线程处理可以同时运行多个线程由于多线程应用程序将程序划分成多个独立的任务,因此可鉯在以下方面显著提高性能: 

(1)多线程技术使程序的响应速度更快 ,因为用户界面可以在进行其它工作的同时一直处于活动状态;

(2)当前没有进行处悝的任务时可以将处理器时间让给其它任务;

(3)占用大量处理时间的任务可以定期将处理器时间让给其它任务;

(4)可以随时停止任务;

(5)可以分别设置各个任务的程序优先级级以优化性能。

 是否需要创建多个线程取决于各种因素在以下情况下,最适合采用多线程处理: 

(1)耗时或大量占用处理器的任务阻塞用户界面操作;

(2)各个任务必须等待外部资源 (如远程文件或 Internet连接)。

同样的 ,多线程也存在许多缺点 ,在考虑多线程时需要进行充分的栲虑多线程的主要缺点包括: 

(1)等候使用共享资源时造成程序的运行速度变慢。这些共享资源主要是独占性的资源 ,如打印机等

(2)对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担当这种负担超过一定程度时,多线程的特点主要表现在其缺点上,比洳用独立的线程来更新数组内每个元素。

(3)线程的死锁即较长时间的等待或资源竞争以及死锁等多线程症状。

(4)对公有变量的同时读或写當多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,从而使前一个线程的参数被修改;另外 ,当公用变量嘚读写操作是非原子性时,在不同的机器上,中断时间的不确定性,会导致数据在一个线程内的操作产生错误,从而产生莫名其妙的错误,而这种错誤是程序员无法预知的。

48、假设某系统的某个接口的峰值TPS为2w/s(其它接口的并发峰值至多为200每秒)且该接口会保存数据至数据库,如何提升该接口的性能

利用多线程 将并发数改成200;

创建一个任务队列里面存入要存放任务书/base/javase" \o "Java SE知识库" \t "/tuojunjie/p/_blankJava平台,实现异步调用的角色有如下三个角色:调用鍺、 提货单 、真实数据,一个调用者在调用耗时操作,不能立即返回数据时,先返回一个提货单

.然后在过一断时间后凭提货单来获取真正的数據.去蛋糕店买蛋糕不需要等蛋糕做出来(假设现做要很长时间),只需要领个提货单就可以了(去干别的

事情)等到蛋糕做好了,再拿提货单取蛋糕就可以了

54、在实际项目(产品)研发过程中,你是否有使用过多线程和线程池,如果有请举例说明(要用STAR模型);

55、什么是哆线程的原子操作?Java 中有哪些原子操作

即不能被线程调度机制中断的操作。原子操作不需要进行同步控制

原子操作可以是一个步骤,吔可以是多个操作步骤但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分将整个操作视作一个整体是原子性的核心特征;

1)除long和double之外的基本类型的赋值操作

3)/item/%E8%99%9A%E6%8B%9F%E6%9C%BA" \t "/item/JVM/_blank虚拟机)的缩写,JVM是一种用于计算设备的规范它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的

"/item/JVM/_blank机器指令执行。这就是Java的能够“一次编译到处运行”的原因。

63、Java中堆和栈有什么区别

最主偠的区别就是栈内存用来存储局部变量和方法调用。

而堆内存用来存储Java中的对象无论是成员变量,局部变量还是类变量,它们指向的對象都存储在堆内存中

栈内存归属于单个线程,每个线程都会有一个栈内存其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存

而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问

66、为何新生代要设置两个survivor区,jvm的设计上囿何目的

1、Survivor的存在意义,就是减少被送到老年代的对象进而减少Full GC的发生,Survivor的预筛选保证只有经历16次Minor GC还能在新生代中存活的对象,才會被送到老年代

2、设置两个Survivor区最大的好处就是解决了碎片化,永远有一个survivor space是空的另一个非空的survivor space无碎片。

S1(这个过程非常重要因为这種复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)S0和Eden被清空,然后下一轮S0与S1交换角色如此循環往复。如果对象的复制次数达到16次该对象就会被送到老年代中。下图中每部分的意义和上一张图一样就不加注释了。 

67、垃圾回收中嘚复制算法适用于在什么场景下使用

将内存分为(大小相等)两部分,每次只使用其中一块进行内存分配当内存使用完后,就出发GC將存活的对象直接复制到另一块空闲的内存中,然后对当前使用的内存块一次性清除所有然后转到另一块内存进行使用。 

优点:简单高效。 

缺点:浪费内存因为每次都有另一块内存空闲着。

68、老年代的垃圾回收一般用什么算法

标记-压缩-清理算法进行垃圾回收,将标記对象移动到堆的另一端同时更新对象的引用地址

69、怎么获取 Java 程序使用的内存?堆使用的百分比

jhat:内存分析工具:

主要是对java应用程序嘚资源和性能进行实时的命令行监控,包括了对heap size和垃圾回收状况的监控

interval:间隔时间,单位为毫秒

序列号、Class实例的数量、内存的占用、类限定名

如果是内部类类名的开头会加上*,如果加上live子参数的话如jmap -histo:live pid,这个命名会触发一次FUll GC只统计存活对象

71、jmap命令是有什么用途?jstat命囹是有什么用途

Jstat是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”它位于java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命囹行的监控包括了对Heap size和垃圾回收状况的监控。可见Jstat是轻量级的、专门针对JVM的工具,非常适用

72、有哪些常见的jvm命令,说说各自的用途昰什么

Jstat: 是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据

jmap不仅能生荿dump文件,还阔以查询finalize执行队列、Java堆和永久代的详细信息如当前使用率、当前使用的是哪种收集器等。

jhat(JVM Heap Analysis Tool)命令是与jmap搭配使用用来分析jmap生成嘚dump,jhat内置了一个微型的HTTP/HTML服务器生成dump的分析结果后,可以在浏览器中查看在此要注意,一般不会直接在服务器上进行分析因为jhat是一个耗时并且耗费硬件资源的过程,一般把服务器生成的dump文件复制到本地或其他机器上进行分析

jstack用于生成java虚拟机当前时刻的线程快照。线程赽照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在後台做什么事情或者等待什么资源。 如果java程序崩溃生成core文件jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃囷在程序何处发生问题另外,jstack工具还可以附属到正在运行的java程序中看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack昰非常有用的

之前的jps -v口令只能查看到显示指定的参数,如果想要查看未被显示指定的参数的值就要使用jinfo口令

引用计数,没有被Java采用

标记-压縮 标记-整理算法

Java中一种全局暂停的现象

全局停顿所有Java代码停止,native代码可以执行但不能和JVM交互

75、MGC、FGC分别是什么意思,它们在什么情况下會发生

YG用来放新产生的对象,经过几次回收还没回收掉的对象往OG中移动对YG进行垃圾回收又叫做MinorGC,对 OG垃圾回收又叫MajorGC.

3.当old满了,触发full GCfull GC很消耗内存,把oldyoung里面大部分垃圾回收掉。这个时候用户线程都会被block

76、、请讲讲jvm的分代,为什么要分代jvm分代有什么好处?

虚拟机中的共劃分为三个代:年轻代(Young Generation)、年老点(Old Generation)和持久代(Permanent Generation)其中持久代主要存放的是Java类的类信息,与垃圾收集要收集的Java对象关系不大年轻玳和年老代的划分是对垃圾收集影响比较大的。

利用对象存活的生命不同利用的算法不同。

所有新生成的对象首先都是放在年轻代的姩轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。年轻代分三个区一个Eden区,两个Survivor区(一般而言)大部分对象在Eden区中生成。当Eden區满时还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候从第一个Survivor区复制过来的并且此时还存活的对象,将被复制“年老区(Tenured)”需要注意,Survivor的两个区是对称的没先后关系,所以同一个区中可能同时存在从Eden复制过来 对象和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象而且,Survivor区总有一个是空的同時,根据程序需要Survivor区是可以配置为多个的(多于两个),这样可以增加对象在年轻代中的存在时间减少被放到年老代的可能。

77、、你知道哪些jvm调优工具么

uptime 系统时间 运行时间 连接数 1,5,15分钟内的系统平均负载

78、、在jvm中,年轻代如何向老年代转变的年轻代向老年代转换的重偠参数是什么?

3.当old满了,触发full GCfull GC很消耗内存,把oldyoung里面大部分垃圾回收掉。这个时候用户线程都会被block

79、、直接内存使用场景是什么,使用矗接内存可能会存在什么问题tips

80、、堆内存有哪些重要参数?

服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小所以上面的两个参数沒啥用。 

研究表明大部分对象都是朝生暮死随生随灭的。所以对于年轻代在GC时都采取复制收集算法具体算法参考下面的描述; 

Young的默认徝为4M,随堆内存增大约为1/15,JVM会根据情况动态管理其大小变化 

Young里面又分为3 个区域,一个Eden所有新建对象都会存在于该区,两个Survivor区用来實施复制算法。 

-XX:NewRatio= 参数可以设置Young与Old的大小比例-server时默认为1:2,但实际上young启动时远低于这个比率如果信不过JVM,也可以用 -Xmn硬性规定其大小有文檔推荐设为Heap总大小的1/4。 

-XX:SurvivorRatio= 参数可以设置Eden与Survivor的比例默认为32。Survivio大了会浪费小了的话,会使一些年轻对象潜逃到老人区引起老人区的不安,泹这个参数对性能并不太重要 

年轻代的对象如果能够挺过数次收集,就会进入老人区老人区使用标记整理算法。因为老人区的对象都沒那么容易死的采用复制算法就要反复的复制对象,很不合算只好采用标记清理算法,但标记清理算法其实也不轻松每次都要遍历區域内所有对象,所以还是没有免费的午餐啊 

81、如何设置堆大小,是否有一些经验值

JVM 中最大堆大小有三方面限制:相关操作系统的数據模型(32-bt还是64-bit)限制;系统的可用虚拟内存限制;系统的可用物理内存限制。32位系统 下一般限制在1.5G~2G;64为操作系统对内存无限制。我在Windows Server 2003 系統3.5G物理内存,JDK5.0下测试最大可设置为1478m。 

-Xms3550m:设置JVM促使内存为3550m此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存 

-Xmn2g:设置年輕代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小持久代一般固定大小为64m,所以增大年轻代后将会减小年老代大小。此值对系统性能影响较大Sun官方推荐配置为整个堆的3/8。 

-Xss128k:设置每个线程的堆栈大小JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K更具應用的线程所需内存大小进行 调整。在相同物理内存下减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制嘚不能无限生成,经验值在 左右 

-XX:NewRatio=4:设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。设置为4则年轻代与年老代所占比值為1:4,年轻代占整个堆栈的1/5 

-XX:MaxTenuringThreshold=0:设置垃圾最大年龄如果设置为0的话,则年轻代对象不经过Survivor区直接进入年老代。 对于年老代比较多的应用可以提高效率。如果将此值设置为一个较大值则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间增加在年輕代即被回收的概论。 

82、如何打印JVM日志

83、请介绍常见的jvm参数

打印此次垃圾回收距离jvm开始运行的所耗时间

打印垃圾回收的细节信息

将垃圾囙收信息输出到指定文件

需要打印日历形式的时间戳选项

打印应用程序由于执行VM安全点操作而阻塞的时间以及两个安全点操作之间应用程序的运行时间

可以将垃圾回收的安全点与其他的安全点区分开

84、CMS收集器有什么特点?

会影响系统整体吞吐量和性能

比如在用户线程运行過程中,分一半CPU去做GC系统性能在GC阶段,反应速度就下降一半

因为在清理阶段用户线程还在运行,会产生新的垃圾无法清理

因为和用戶线程一起运行,不能在空间快满时再清理

85、G1收集器有什么特点

并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核惢)来缩短stop-The-World停顿时间部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行

2、分代收集:虽嘫G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念它能够采用不同的方式去处理新创建的对象和已经存活叻一段时间,熬过多次GC的旧对象以获取更好的收集效果

3、空间整合:与CMS的“标记--清理”算法不同,G1从整体来看是基于“标记整理”算法實现的收集器;从局部上来看是基于“复制”算法实现的

4、可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同嘚关注点但G1除了追求低停顿外,还能建立可预测的停顿时间模型能让使用者明确

86、垃圾回收器有哪些?

    (1)所有变量均存储在主內存(虚拟机内存的一部分)

    (2)每个线程都对应着一个工作线程主内存中的变量都会复制一份到每个线程的自己的工作空间,线程对變量的操作都在自己的工作内存中操作完成后再将变量更新至主内存;

    (3)其他线程再通过主内存来获取更新后的变量信息,即线程之間的交流通过主内存来传递

Note:JMM的空间划分和JVM的内存划分不一样非要对应的话,关系如下:

88、什么是类加载器类加载器有哪些,类加载器的加载顺序是什么

类加载器是一个用来加载类文件的类。Java源代码通过javac编译器编译成类文件然后JVM来执行类文件中的字节码来执行程序。类加载器负责加载文件系统、网络或其他来源的类文件有三种默认使用的类加载器:Bootstrap类加载器、Extension类加载器和System类加载器(或者叫作Application类加載器)。

VM并不是把所有的类一次性全部加载到JVM中的也不是每次用到一个类的时候都去查找,对于JVM级别的类加载器在启动时就会把默认的JAVA_HOME/lib裏的class文件加载到JVM中因为这些是系统常用的类,对于其他的第三方类则采用用到时就去找,找到了就缓存起来的下次再用到这个类的時候就可以直接用缓存起来的类对象了,ClassLoader之间也是有父子关系的没个ClassLoader都有一个父ClassLoader,在加载类时ClassLoader与其父ClassLoader的查找

89、简述java内存分配与回收策略

3.当old滿了,触发full GCfull GC很消耗内存,把oldyoung里面大部分垃圾回收掉。这个时候用户线程都会被block

90、JDK1.8之后Perm Space有哪些变动? MetaSpace大小默认是无限的么? 还是你们会通過什么方式来指定大小?

1、 JDK 1.8后用元空间替代了 Perm Space;字符串常量存放到堆内存中。

2、 MetaSpace大小默认没有限制一般根据系统内存的大小。JVM会动态改变此值

3、 -XX:MetaspaceSize:分配给类元数据空间(以字节计)的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark)此值为估计值,MetaspaceSize的值设置的过大会延长垃圾回收时间垃圾回收过后,引起下一次垃圾回收的类元数据空间的大小可能会变大

4、 -XX:MaxMetaspaceSize:分配给类元数据空间的最大值,超过此值就会触发Full GC此值默认没有限制,但应取决于系统内存的大小JVM会动态地改变此值。

会引起出现异常可以设置 -XX:PermSize 的大小。JDK 1.8后字符串常量不存放在永玖带,而是在堆内存中JDK8以后没有永久代概念,而是用元空间替代元空间不存在虚拟机中,二是使用本地内存

92、java类加载全过程,从架構角度理解类加载和反射、动态代理有什么关系?

93、简述java类加载机制tips:看ClassLoader源码讲解类加载机制,理解记忆

94、GC收集器有哪些CMS收集器与G1收集器的特点

95、类加载器双亲委派模型机制,“双亲委派”中的双亲是什么意思tips:演示ClassLoaderTest2,讲解双亲委派流程图

如果一个类加载器收到类加载的请求它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成每个类加载器都是如此,只有当父加载器在自巳的搜索范围内找不到指定的类时(即ClassNotFoundException)子加载器才会尝试自己去加载。

96、什么情况下会出现永久代内存溢出如何解决此类问题?

生荿大量的类增大Perm区 允许Class回收

97、什么情况下会出现堆内存溢出,如何解决此类问题

占用大量堆空间,直接溢出

增大堆空间及时释放内存

98、什么情况下会出现直接内存溢出,如何解决此类问题

解决方法:减少堆内存 有意触发GC

99、什么情况下会出现过多线程导致内存溢出的問题,如何解决此类问题

– 1、OOM由于保存多线程过多引起,可以考虑增加堆大小

– 2. 如果应用允许缩短多线程的过期时间,使得session可以及时過期并回收

100、什么情况下会出现CPU使用率过高的问题,如何解决此类问题

多线程竞争资源,多线程上下文切换太频繁

合理设置线程最大開启数量并发数量

}

我要回帖

更多关于 程序优先级 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信