史上最全的Java线程面试题

Java面试题 同时被 2 个专栏收录
33 篇文章 3 订阅

1、java 中有几种方法可以实现一个线程?用什么关键字修饰同步方法? stop()和 和 suspend() 方法为何不推荐使用?
java5以前,有如下两种:
第一种:
new Thread(){}.start();这表示调用 Thread 子类对象的 run 方法,new Thread(){}表示一个
Thread 的匿名子类的实例对象,子类加上 run 方法后的代码如下:
new Thread(){
public void run(){
}
}.start();
第二种:
new Thread(new Runnable(){}).start();这表示调用Thread对象接受的Runnable对象的run
方法,new Runnable(){}表示一个 Runnable 的匿名子类的实例对象,runnable 的子类加上
run 方法后的代码如下:
new Thread(new Runnable(){
public voidrun(){
}
}
).start();
从 java5开始,还有如下一些线程池创建多线程的方式:
ExecutorService pool = Executors.newFixedThreadPool(3)
for(int i=0;i<10;i++)
{
pool.execute(newRunable(){public void run(){}});
}
Executors.newCachedThreadPool().execute(new Runable(){publicvoid run(){}});
Executors.newSingleThreadExecutor().execute(new Runable(){publicvoid run(){}});
有两种实现方法,分别使用 new Thread()和 new Thread(runnable)形式,第一种直接调用
thread 的 run 方法,所以,我们往往使用 Thread 子类,即 new SubThread()。第二种调用
runnable 的 run 方法。
有两种实现方法,分别是继承 Thread 类与实现 Runnable 接口
用 synchronized 关键字修饰同步方法
反对使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一
种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题
所在。suspend()方法容易发生死锁。调用 suspend()的时候,目标线程会停下来,但却仍
然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的
线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定
的资源,就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread 类中置入一个
标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状
态。若标志指出线程应当恢复,则用一个 notify()重新启动线程。
2、sleep() 和 wait() 有什么区别?
(网上的答案:sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,
给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放
对象锁。 wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程入
对象锁定池准备获得对象锁进入运行状态。)
sleep 就是正在执行的线程主动让出 cpu,cpu 去执行其他线程,在 sleep 指定的时间过后,
cpu 才会回到这个线程上继续往下执行,如果当前线程进入了同步锁,sleep 方法并不会释
放锁,即使当前线程使用 sleep 方法让出了 cpu,但其他被同步锁挡住了的线程也无法得到执行。wait 是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有其他线程调用了 notify 方法(notify 并不释放锁,只是告诉调用过 wait 方法的线程可以去参与获得锁的竞争了,但不是马上得到锁,因为锁还在别人手里,别人还没释放。如果 notify 方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在 notfiy 方法后增加一个等待和一些代码,看看效果),调用 wait方法的线程就会解除 wait 状态和程序可以再次得到锁后继续向下运行。对于 wait 的讲解一定要配合例子代码来说明,才显得自己真明白。
package com.huawei.interview;
publicclass MultiThread {
/**

  • @paramargs
    */
    public static voidmain(String[] args) {
    // TODO Auto-generated method stub
    new Thread(newThread1()).start();
    try {
    Thread.sleep(10);
    } catch (InterruptedException e) {
    // TODO Auto-generated catchblock
    e.printStackTrace();
    }
    new Thread(newThread2()).start();
    }
    private static classThread1implements Runnable
    {
    @Override
    public void run() {
    // TODO Auto-generated methodstub
    //由于这里的 Thread1和下面的 Thread2内部 run 方法要用同一对象作为监视器,我们这里
    不能用 this,因为在 Thread2里面的 this 和这个 Thread1的 this 不是同一个对象。我们用
    MultiThread.class 这个字节码对象,当前虚拟机里引用这个变量时,指向的都是同一个对
    象。
    synchronized (MultiThread.class){
    System.out.println(“enterthread1…”);
    System.out.println(“thread1is waiting”);
    try {
    //释放锁有两种方式,第一种方式是程序自然离开监视器的范围,也就是离开
    了 synchronized 关键字管辖的代码范围,另一种方式就是在 synchronized 关键字管辖的代
    码内部调用监视器对象的 wait 方法。这里,使用 wait 方法释放锁。
    MultiThread.class.wait();
    } catch(InterruptedException e) {
    // TODO Auto-generatedcatch block
    e.printStackTrace();
    }
    System.out.println(“thread1is going on…”);
    System.out.println(“thread1is being over!”);
    }
    }
    }
    private static classThread2implements Runnable
    {
    @Override
    public void run() {
    // TODO Auto-generated methodstub
    synchronized (MultiThread.class){
    System.out.println(“enterthread2…”);
    System.out.println(“thread2notify other thread can release wait status…”);
    //由于 notify 方法并不释放锁,即使 thread2调用下面的 sleep 方法休息了10毫秒,但 thread1
    仍然不会执行,因为 thread2没有释放锁,所以 Thread1无法得不到锁。
    MultiThread.class.notify();
    System.out.println(“thread2is sleeping ten millisecond…”);
    try {
    Thread.sleep(10);
    } catch (InterruptedExceptione) {
    // TODO Auto-generatedcatch block
    e.printStackTrace();
    }
    System.out.println(“thread2is going on…”);
    System.out.println(“thread2is being over!”);
    }
    3、同步和异步有何异同,在什么情况下分别使用他们?举例说明。
    如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数
    据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。
    当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方
    法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
    4. 下面两个方法同步吗?(自己发明)
    class Test
    {
    synchronizedstatic void say Hello3()
    {
    }
    synchronizedvoid getX(){}
    }
    5、多线程有几种实现方法? 同步有几种实现方法?
    1.继承Thread类实现多线程
    2.实现Runnable接口方式实现多线程
    3.使用线程池:如ExecutorService,Callable,Future
    第一种和第二种方式相比:java类只能允许继承一个父类,可以实现多个接口;其次,在第一种方式中用this可以获取当前线程,第二种方式获取当前线程只能通过Thread.currentThread()
    同步的实现方面有两种,分别是 synchronized,wait 与 notify
    wait():使一个线程处于等待状态,并且释放所持有的对象的 lock。
    sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉
    InterruptedException(中断异常)异常。
    notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒
    某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级。
    Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是
    让它们竞争。
    6、启动一个线程是用 run() 还是 start()? .
    启动一个线程是调用 start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程
    必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。

7、当一个线程进入一个对象的一个 synchronized 方法后,其它线程是否可进入此对象的其它方法? 分几种情况:

  1. 其他方法前是否加了 synchronized 关键字,如果没加,则能。
  2. 如果这个方法内部调用了 wait,则可以进入其他 synchronized 方法。
  3. 如果其他个方法都加了 synchronized 关键字,并且内部没有调用 wait,则不能。
  4. 如果其他方法是 static,它用的同步锁是当前类的字节码,与非静态的方法不能同
    步,因为非静态的方法用的是 this。

8、线程的基本概念、线程的基本状态以及状态之间的关系 线程的基本概念、线程的基本状态以及状态之间的关系:
一个程序中可以有多条执行线索同时执行,一个线程就是程序中的一条执行线索,每个线程
上都关联有要执行的代码,即可以有多段程序代码同时运行,每个程序至少都有一个线程,
即 main 方法执行的那个线程。如果只是一个 cpu,它怎么能够同时执行多段程序呢?这是
从宏观上来看的,cpu 一会执行 a 线索,一会执行 b 线索,切换时间很快,给人的感觉是
a,b 在同时执行,好比大家在同一个办公室上网,只有一条链接到外部网线,其实,这条网
线一会为 a 传数据,一会为 b 传数据,由于切换时间很短暂,所以,大家感觉都在同时上
网。
状态:就绪,运行,synchronize 阻塞,wait 和 sleep 挂起,结束。wait 必须在 synchronized
内部调用。
调用线程的 start 方法后线程进入就绪状态,线程调度系统将就绪状态的线程转为运行状
态,遇到 synchronized 语句时,由运行状态转为阻塞,当 synchronized 获得锁后,由阻塞
转为运行,在这种情况可以调用 wait 方法转为挂起状态,当线程关联的代码执行完后,线
程变为结束状态。
9 、简述 synchronized 和 和 java.util.concurrent.locks.Lock 的异同?
主要相同点:Lock 能完成 synchronized 所实现的所有功能
主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 会自
动释放锁,而 Lock 一定要求程序员手工释放,并且必须在 finally 从句中释放。Lock 还有更
强大的功能,例如,它的 tryLock 方法可以非阻塞方式去拿锁。
举例说明(对下面的题用 lock 进行了改写):
package com.huawei.interview;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
publicclass ThreadTest {
/**

  • @paramargs
    */
    private int j;
    private Lock lock =newReentrantLock();
    public static voidmain(String[] args) {
    // TODO Auto-generated method stub
    ThreadTest tt = new ThreadTest();
    for(int i=0;i<2;i++)
    {
    new Thread(tt.new Adder()).start();
    new Thread(tt.new Subtractor()).start();
    }
    }
    private class SubtractorimplementsRunnable
    {
    @Override
    public void run() {
    // TODO Auto-generated methodstub
    while(true)
    {
    /synchronized (ThreadTest.this) {
    System.out.println(“j–=”+ j–);
    //这里抛异常了,锁能释放吗?
    }
    /
    lock.lock();
    try
    {
    System.out.println(“j–=”+ j–);
    }finally
    {
    lock.unlock();
    }
    }
    }
    }
    private class AdderimplementsRunnable
    {
    @Override
    public void run() {
    // TODO Auto-generated methodstub
    while(true)
    {
    /synchronized (ThreadTest.this) {
    System.out.println(“j++=”+ j++);
    }
    /
    lock.lock();
    try
    {
    System.out.println(“j++=”+ j++);
    }finally
    {
    lock.unlock();
    }
    10 、设计 4 个线程,其中两个线程每次对 j 增加 1 ,另外两个线程对 j 每次减少 每次减少
    1 。写出程序。

    以下程序使用内部类实现线程,对 j 增减的时候没有考虑顺序问题。
    public class ThreadTest1
    {
    private int j;
    public static void main(String args[]){
    ThreadTest1 tt=newThreadTest1();
    Inc inc=tt.new Inc();
    Dec dec=tt.new Dec();
    for(inti=0;i<2;i++){
    Thread t=newThread(inc);
    t.start();
    t=new Thread(dec);
    t.start();
    }
    }
    private synchronized void inc(){
    j++;
    System.out.println(Thread.currentThread().getName()+"-inc:"+j);
    }
    private synchronized void dec(){
    j–;
    System.out.println(Thread.currentThread().getName()+"-dec:"+j);
    }
    class Inc implements Runnable{
    public void run(){
    for(inti=0;i<100;i++){
    inc();
    }
    }
    }
    class Dec implements Runnable{
    public void run(){
    for(inti=0;i<100;i++){
    dec();
    }
    }
    }
    }
    ----------随手再写的一个-------------
    class A
    {
    JManger j =new JManager();
    main()
    {
    new A().call();
    }
    void call
    {
    for(int i=0;i<2;i++)
    {
    new Thread(
    newRunnable(){ public void run(){while(true){j.accumulate()}}}
    ).start();
    new Thread(newRunnable(){ public void run(){while(true){j.sub()}}}).start();
    }
    }
    }
    class JManager
    {
    private j = 0;
    public synchronized voidsubtract()
    {
    j–
    }
    public synchronized voidaccumulate()
    {
    j++;
    }
    }
    11 、子线程循环 10 次,接着主线程循环 100 ,接着又回到子线程循环 10 次, 次,接着再回到主线程又循环 接着再回到主线程又循环 100 ,如此循环 50 次,请写出程序最终的程序代码如下:
    public class ThreadTest {
    public static voidmain(String[] args) {
    // TODO Auto-generated method stub
    new ThreadTest().init();
    }
    public void init()
    {
    final Business business =newBusiness();
    new Thread(
    new Runnable()
    {
    public voidrun() {
    for(inti=0;i<50;i++)
    {
    business.SubThread(i);
    }
    }
    }
    ).start();
    for(int i=0;i<50;i++)
    {
    business.MainThread(i);
    }
    }
    private class Business
    {
    booleanbShouldSub =true;//这里相当于定义了控制该谁执行的一个信号灯
    public synchronized voidMainThread(int i)
    {
    if(bShouldSub)
    try {
    this.wait();
    } catch(InterruptedException e) {
    // TODO Auto-generatedcatch block
    e.printStackTrace();
    }
    for(int j=0;j<5;j++)
    {
    System.out.println(Thread.currentThread().getName()+ “:i=” + i +",j=" + j);
    }
    bShouldSub =true;
    this.notify();
    }
    public synchronized voidSubThread(int i)
    {
    if(!bShouldSub)
    try {
    this.wait();
    } catch (InterruptedExceptione) {
    // TODO Auto-generatedcatch block
    e.printStackTrace();
    }
    for(intj=0;j<10;j++)
    {
    System.out.println(Thread.currentThread().getName()+ “:i=” + i +",j=" + j);
    }
    bShouldSub =false;
    this.notify();
    }
    备注:不可能一上来就写出上面的完整代码,最初写出来的代码如下,问题在于两个线程的
    代码要参照同一个变量,即这两个线程的代码要共享数据,所以,把这两个线程的执行代码
    搬到同一个类中去:
    package com.huawei.interview.lym;
    publicclass ThreadTest {
    private static booleanbShouldMain=false;
    public static void main(String[]args) {
    // TODO Auto-generated method stub
    /new Thread(){
    public void run()
    {
    for(int i=0;i<50;i++)
    {
    for(int j=0;j<10;j++)
    {
    System.out.println(“i=”+ i + “,j=” + j);
    }
    }
    }
    }.start();
    /
    //final String str = newString("");
    new Thread(
    new Runnable()
    {
    public voidrun()
    {
    for(inti=0;i<50;i++)
    {
    synchronized(ThreadTest.class) {
    if(bShouldMain)
    {
    try {
    ThreadTest.class.wait();}
    catch(InterruptedException e) {
    e.printStackTrace();
    }
    }
    for(intj=0;j<10;j++)
    {
    System.out.println(
    Thread.currentThread().getName()+
    “i=”+ i + “,j=” + j);
    }
    bShouldMain= true;
    ThreadTest.class.notify();
    }
    }
    }
    }
    ).start();
    for(int i=0;i<50;i++)
    {
    synchronized (ThreadTest.class){
    if(!bShouldMain)
    {
    try {
    ThreadTest.class.wait();}
    catch(InterruptedException e) {
    e.printStackTrace();
    }
    }
    for(intj=0;j<5;j++)
    {
    System.out.println(
    Thread.currentThread().getName()+
    “i=” + i +",j=" + j);
    }
    bShouldMain =false;
    ThreadTest.class.notify();
    }
    }
    }
    }
    下面使用 jdk5中的并发库来实现的:
    import java.util.concurrent.Executors;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.locks.Condition;
    public class ThreadTest
    {
    private static Locklock = new ReentrantLock();
    private staticCondition subThreadCondition = lock.newCondition();
    private staticboolean bBhouldSubThread = false;
    public static voidmain(String [] args)
    {
    ExecutorServicethreadPool = Executors.newFixedThreadPool(3);
    threadPool.execute(newRunnable(){
    publicvoid run()
    {
    for(inti=0;i<50;i++)
    {
    lock.lock();
    try
    {
    if(!bBhouldSubThread)
    subThreadCondition.await();
    for(intj=0;j<10;j++)
    {
    System.out.println(Thread.currentThread().getName()+ “,j=” + j);
    }
    bBhouldSubThread= false;
    subThreadCondition.signal();
    }catch(Exceptione)
    {
    }
    finally
    {
    lock.unlock();
    }
    }
    }
    });
    threadPool.shutdown();
    for(inti=0;i<50;i++)
    {
    lock.lock();
    try
    {
    if(bBhouldSubThread)
    subThreadCondition.await();
    for(intj=0;j<10;j++)
    {
    System.out.println(Thread.currentThread().getName()+ “,j=” + j);
    }
    bBhouldSubThread= true;
    subThreadCondition.signal();
    }catch(Exceptione)
    {
    }
    finally
    {
    lock.unlock();
    }
    12. sleep()和 wait()有什么区别?
    sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会
    给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对
    象锁。
    wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入
    等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程
    才进入对象锁定池准备获得对象锁进入运行状态。
    1.关于sleep和wait,以下描述错误的是(D)
    A.sleep是线程类的方法,wait是object的方法
    B.sleep不释放对象锁,wait放弃对象锁
    C.sleep暂停线程,但监控状态依然保持,结束后会自动恢复
    D.wait进入等待锁定池,只有针对此对象发出notify方法获得对象锁进入运行状态
    wait()方法会导致线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。注意是准备获取对象锁进入运行状态,而不是立即获得
    2.下列说法错误的有( CD)
    A. 在类方法中可用this来调用本类的类方法
    B.在类方法中调用本类的类方法时可直接调用
    C. 在类方法中只能调用本类中的类方法
    D. 在类方法中绝对不能调用实例方法
    c是明显的错误,类方法可以调用外部其他类的方法。
    至于D选项中,也是有问题的,只要实力化对象,也是可以调用实例方法的。
    所有这里要选CD。
    13.请说出你所知道的线程同步的方法。
    wait():使一个线程处于等待状态,并且释放所持有的对象的 lock;
    sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要
    捕捉 InterruptedException 异常;
    notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且不是按优先级;
    使对象队列的第一个线程进行就绪状态
    notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象
    的锁,而是让它们竞争。
    13.2 其他:join():合并当前线程,相当于方法调用
    yield():让出 cpu;具有优先级线程获得处理器
    notityAll():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象
    的锁,而是让它们竞争。
    suspended() –挂起,该方法已过时: 在临时停止或中断线程的执行时,线程就处于挂起状态。
    resume()—恢复,该方法已过时 : 在挂起的线程被恢复执行时,可以说它已被恢复。
    stop()–暴力结束当前正在执行的线程,该方法已过时
    14.网络编程中设计并发服务器,使用多进程与多线程,请问有什么区别?
    1)进程:子进程是父进程的复制品子进程获得父进程数据空间堆和栈的复制品
    2)线程:相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进
    程的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列
    两者都可以提高程序的并发度,提高程序运行效率和响应时间
    线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;
    而进程正相反同时,线程适合于在 SMP 机器上运行,而进程则可以跨机器迁移
    15.线程的基本概念、线程的本状态以及状态之间的关系
    新建 (Born) : 新建的线程处于新建状态,也叫新生状态
    就绪 (Ready) : 在创建线程后,它将处于就绪状态,等待 start() 方法被调用
    运行 (Running) : 线程在开始执行时进入运行状态
    睡眠 (Sleeping) : 线程的执行可通过使用 sleep() 方法来暂时中止。在睡眠后,线程将进入
    就绪状态
    等待 (Waiting) : 如果调用了 wait() 方法,线程将处于等待状态。用于在两个或多个线程并
    发运行时。
    挂起 (Suspended) : 在临时停止或中断线程的执行时,线程就处于挂起状态。
    恢复 (Resume) : 在挂起的线程被恢复执行时,可以说它已被恢复。
    阻塞 (Blocked) – 在线程等待一个事件时(例如输入/输出操作),就称其处于阻塞状态。
    死亡 (Dead) – 在 run() 方法已完成执行或其 stop() 方法被调用之后,线程就处于死亡状
    态。
    串行化的注意事项以及如何实现串行化答:如果有循环引用是不可以串行化的。对象输出流
    的 WriteObject 方法和 对象输入流的 ReadObect 方法

16.stop 方法.这个方法将终止所有未结束的方法,包括 run 方法。当一个线程停止时候,他会立即释放 所有他锁住对象上的锁。这会导致对象处于不一致的状态。 当线程想终止另一个线程的时候,它无法知道何时调用 stop 是安全的,何时会导致对象被破坏。所以这个方法被弃用了你应该中断一个线程而不是停止他。被中断的线程会在安全的时候停止。
17.关于 sleep()和 wait(),以下描述错误的一项是( D)
A. sleep 是线程类(Thread)的方法,wait 是 Object 类的方法;
B. sleep 不释放对象锁,wait 放弃对象锁;
C. sleep 暂停线程、但监控状态仍然保持,结束后会自动恢复;
D. wait 后进入等待锁定池,只有针对此对象发出 notify 方法后获得对象锁进入运行状态。
sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。
wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或 notifyAll)后本线程才进入对象锁定池准备得对象锁进入运行状态。
sleep:导致此线程暂停执行指定时间
stop: 这个方法将终止所有未结束的方法,包括 run 方法。
synchronized():对象锁
yield:当前正在被服务的线程可能觉得 cpu 的服务质量不够好,于是提前退出,这就是它
wait:当前正在被服务的线程需要睡一会,醒来后继续被服务
18.有关线程的哪些叙述是对的( BCD)
A 一旦一个线程被创建,它就立即开始运行。
B 使用 start()方法可以使一个线程成为可运行的,但是它不一定立即开始运行。
C 当一个线程因为抢先机制而停止运行,它被放在可运行队列的前面。
D 一个线程可能因为不同的原因停止并进入就绪状态。
抢占式线程模型中操作系统可以在任何时候打断线程。通常会在它运行了一段时间(就是所谓的一个时间片)后才打断它这样的结果自然是没有线程能够不公平地长时间霸占处理器。
19.以下哪个方法用于定义线程的执行体?(C)
A.start()
B.init()
C.run()
D.main()
E.synchronized()
20. 关于线程设计,下列描述正确的是 C
A. 线程对象必须实现 Runnable 接口
B. 启动一个线程直接调用线程对象的 run()方法
C. Java 提供对多线程同步提供语言级的支持
D. 一个线程可以包含多个进程
22. 启动一个线程是用 run()还是 start()?
解答:start()。
23. 简述 synchronized 和 java.util.concurrent.locks.Lock 的异同?
主要相同点:Lock 能完成 synchronized 所实现的所有功能
主要不同点:Lock 有比 synchronized 更精确的线程语义和更好的性能。synchronized 会自动释放锁,
而 Lock 一定要求程序员手工释放,并且必须在 finally 从句中释放。
24. 多线程有几种实现方法,都是什么?同步的方法有几种,都是什么?
解答:多线程有两种实现方法:继承 Thread 类或者实现 Runnable 接口。
实现同步也有两种方法:一种是同步方法,另一种是同步代码块。
同步方法是在方法返回类型前面加上 synchronized 关键字
同步代码块是 synchronized (这里写需要同步的对象){…}
25. 死锁的必要条件?怎么克服?
解答:产生死锁的四个必要条件:
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的解决方法:
a 撤消陷于死锁的全部进程;
b 逐个撤消陷于死锁的进程,直到死锁不存在;
c 从陷于死锁的进程中逐个强迫放弃所占用的资源,直至死锁消失。
d 从另外一些进程那里强行剥夺足够数量的资源分配给死锁进程,以解除死锁状态
26. 什么是垃圾回收?什么时候触发垃圾回收?如何降低垃圾回收的触发频率?它能保证程序有足够的可用内存吗?
解答:垃圾回收(GC)是 Java 语言的一个重要特性,作用是释放不再被使用的内存。垃圾回收由系统进行管理。在系统认为需要的时候自动启动一个线程进行处理。(垃圾回收线程)
27. Servlet 是线程安全吗?以下代码中使用 synchronized 关键字的意义是什么?
Synchronized (aList){
aList.remove (1);
}
解答:默认不是线程安全的,但是 servlet 实现了 SingthreadModel 接口 就能单线程执行。此题中代码的意思是给 aList 对象加同步锁,保证 aList 对象在多线程任务环境中,每次只能够有一个线程调用 remove 方法。从而提高对 aList 对象操作的安全性和正确性。
28. String, StringBuffer StringBuilder的区别。【基础】
答:String的长度是不可变的;
StringBuffer的长度是可变的,如果你对字符串中的内容经常进行操作,特
别是内容要修改时,那么使用StringBuffer,如果最后需要String,那么使用
StringBuffer的toString()方法;线程安全;
StringBuilder是从 JDK 5 开始,为StringBuffer该类补充了一个单个线程
使用的等价类;通常应该优先使用 StringBuilder 类,因为它支持所有相同的
操作,但由于它不执行同步,所以速度更快。
29 进程和线程分别该怎么理解?
解答:进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块 PCB 中。以表示该进程拥有这些资源或正在使用它们。另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。
线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表 TCB 组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。发生进程切换与发生线程切换时相比较,进程切换时涉及到有关资源指针的保存以及地址空间的变化等问题;线程切换时,由于同不进程内的线程共享资源和地址 空间,将不涉及资源信息的保存和地址变化问题,从而减少了操作系统的开销时间。而且,进程的调度与切换都是由操作系统核完成,
而线程则既可由操作系统内 核完成,也可由用户程序进行。
30. 同步和异步有何异同,在什么情况下分别使用他们?请举例说明
解答:如果数据将在线程间共享。例如正在写的数据以后可能被另一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就是共享数据,必须进行同步存取。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
32. 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?【基础】
答:对于 GC 来说,当程序员创建对象时,GC 就开始监控这个对象的地址、大小
以及使用情况。通常,GC 采用有向图的方式记录和管理堆(heap)中的所有对象。
通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当 GC 确定一
些对象为"不可达"时,GC 就有责任回收这些内存空间。可以。程序员可以手动
执行 System.gc(),通知 GC 运行,但是 Java 语言规范并不保证 GC 一定会执行。
33. 当一个线程进入一个对象的一个 synchronized 方法后,其它线程是否可进入此对象的其它方法? 【基础】
答:其它线程只能访问该对象的其它非同步方法,同步方法则不能进入。
34. 线程的基本概念、线程的基本状态以及状态之间的关系?【基础】
答:线程指在程序执行过程中,能够执行程序代码的一个执行单位,每个程序至
少都有一个线程,也就是程序本身;
Java 中的线程有四种状态分别是:运行、就绪、挂起、结束
35. 什么是 ThreadLocal?
ThreadLocal 用于创建线程的本地变量,我们知道一个对象的所有线程会共享它的全
局变量,所以这些变量不是线程安全的,我们可以使用同步技术。但是当我们不想使用
同步的时候,我们可以选择 ThreadLocal 变量。
每个线程都会拥有他们自己的 Thread 变量,它们可以使用 get()\set() 方法去获取他
们的默认值或者在线程内部改变他们的值。ThreadLocal 实例通常是希望它们同线程
状态关联起来是 private static 属性
36. run() 和 start() 区别。
run( ):只是调用普通 run 方法
start( ):启动了线程, 由 Jvm 调用 run 方法
启动一个线程是调用 start() 方法,使线程所代表的虚拟处理机处于可运行状态,这意
味着它可以由 JVM 调度并执行。这并不意味着线程就会立即运行。run() 方法可以产
生必须退出的标志来停止一个线程。
37. 线程调度和线程控制。 线程调度(优先级):
与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线
程获取 CPU 资源的概率较大,优先级低的并非没机会执行。 线程的优先级用 1-10 之
间的整数表示,数值越大优先级越高,默认的优先级为 5。 在一个线程中开启另外一
个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。
线程控制 • sleep( ) // 线程休眠 join( ) // 线程加入 yield( ) // 线程礼让
setDaemon( ) // 线程守护
中断线程
• stop( ) interrupt( ) (首先选用)
38. 什么是线程饿死,什么是活锁?
当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。
JavaAPI 中线程活锁可能发生在以下情形:
• 当所有线程在序中执行 Object.wait(0),参数为 0 的 wait 方法。程序将发生
活锁直到在相应的对象上有线程调用 Object.notify() 或者 Object.notifyAll()。
• 当所有线程卡在无限循环中。
39. 多线程中的忙循环是什么?
忙循环就是程序员用循环让一个线程等待,不像传统方法 wait(), sleep() 或 yield() 它
们都放弃了 CPU 控制,而忙循环不会放弃 CPU,它就是在运行一个空循环。这么做
的目的是为了保留 CPU 缓存。
在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。
为了避免重建缓存和减少等待重建的时间就可以使用它了。
40. volatile 变量是什么?volatile 变量和 atomic 变量有什么不同?
volatile 则是保证了所修饰的变量的可见。因为 volatile 只是在保证了同一个变量在
多线程中的可见性,所以它更多是用于修饰作为开关状态的变量,即 Boolean 类型的
变量。 volatile 多用于修饰类似开关类型的变量、Atomic 多用于类似计数器相关的变量其
它多线程并发操作用 synchronized 关键字修饰。1.可见性2.原子性3、禁止代码重排序性
volatile 有两个功用:
• 这个变量不会在多个线程中存在复本,直接从内存读取。
• 这个关键字会禁止指令重排序优化。也就是说,在 volatile 变量的赋值操作后
面会有一个内存屏障(生成的汇编代码上),读操作不会被重排序到内存屏障之前。
41. volatile 类型变量提供什么保证?能使得一个非原子操作变成原子操作吗?
volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。
在 Java 中除了 long 和7 double 之外的所有基本类型的读和赋值,都是原子性操作。
而 64 位的 long 和 double 变量由于会被 JVM 当作两个分离的 32 位来进行操
作,所以不具有原子性,会产生字撕裂问题。但是当你定义 long 或 double 变量时,
如果使用 volatile 关键字,就会获到(简单的赋值与返回操作的)原子性。
42、)现在有 T1、T2、T3 三个线程,你怎样保证 T2 在 T1 执行完后执行,T3 在 T2 执行完后执 行?
这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟 悉。这个多线程问题比较简单,可以用 join 方法实现。
2)在 Java 中 Lock 接口比 synchronized 块的优势是什么?你需要实现一个高效的缓存,它允 许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
lock 接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写 像 ConcurrentHashMap 这样的高性能数据结构和有条件的阻塞。Java 线程面试的问题越来 越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下 Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。
43. 用 Java 实现阻塞队列。
这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是 否能实际的用 Java 线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根 据这个问很多问题。如果他用 wait()和 notify()方法来实现阻塞队列,你可以要求他用最新 的 Java 5 中的并发类来再写一次。
44. )用 Java 写代码来解决生产者——消费者问题。
与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在 Java 中怎 么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现 的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。
45)用 Java 编程一个会导致死锁的程序,你将怎么解决?
这是我最喜欢的 Java 线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍, 但是很多侯选者并不能写 deadlock free code(无死锁代码?),他们很挣扎。只要告诉他 们,你有 N 个资源和 N 个线程,并且你需要所有的资源来完成一个操作。为了简单这里的 n 可以替换为 2,越大的数据会使问题看起来更复杂。通过避免 Java 中的死锁来得到关于 死锁的更多信息。
46) 什么是原子操作,Java 中的原子操作是什么?
非常简单的 java 线程面试问题,接下来的问题是你需要同步一个原子操作。
47. 你在多线程环境中遇到的常见的问题是什么?你是怎么解决它的?
多线程和并发程序中常遇到的有 Memory-interface、竞争条件、死锁、活锁和饥饿。问题 是没有止境的,如果你弄错了,将很难发现和调试。这是大多数基于面试的,而不是基于 实际应用的 Java 线程问题。
50. 概括的解释下线程的几种可用状态。
就绪(Runnable):线程准备运行,不一定立马就能开始执行。
运行中(Running):进程正在执行线程的代码。
等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
睡眠中(Sleeping):线程被强制睡眠。
I/O 阻塞(Blocked on I/O):等待 I/O 操作完成。
同步阻塞(Blocked on Synchronization):等待获取锁。
死亡(Dead):线程完成了执行。
51. 同步方法和同步代码块的区别是什么?
在 Java 语言中,每一个对象有一把锁。线程可以使用 synchronized 关键字来获取对象上的锁。 synchronized 关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁)。
52.在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?
监视器和锁在 Java 虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一 个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许 执行同步代码。
53. 什么是死锁(deadlock)?
两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程 都陷入了无限的等待中。
54.如何确保 N 个线程可以访问 N 个资源同时又不导致死锁?
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程 按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出 现死锁了。
55. 什么是 JAVA 的反射?
答:程序集包含模块,而模块包含类型,类型又包含成员。反射则提供了封装程 序集、模块和类型的对象。可以使用反射动态地创建类型的实例,将类型绑定到 现有对象,或从现有对象中获取类型。然后,可以调用类型的方法或访问其字段 和属性。
57 、同步和异步有何异同,在什么情况下分别使用他们?举例说明。
如果有一个资源需要被一个或多个线程共享,这个资源就变成了“竞争”资源,此时多 条线程必须按某种既定的规则、依次访问、修改这个“竞争”资源,当一条线程正在访问、 修改该“竞争”资源时,其他线程不能同时修改这份“竞争”资源,这就是同步处理。 对于一个银行账户,如果有多个线程试图去访问这个账户时,如果不对多个线程进行同 步控制,有可能账户余额只有 1000 块,但多个线程都试图取款 800 块时,这些线程同时判 断余额之后,都会显示余额足够,从而导致每个线程都取款成功。这显然不是我们希望看到 结果。 当程序试图执行一个耗时操作时,程序不希望阻塞当前执行流,因此程序也不应该试图 立即获取该耗时操作返回的结果,此时就使用异步编程了,典型的应用场景就是 Ajax。当
浏览器通过 JavaScript 发出一个异步请求之后,JavaScript 执行流并不会停下来,而是继续 向下执行,这就是异步。程序会通过监听器来监听远程服务器响应的到来。
58、垃圾回收的优点和原理。并考虑 2 种回收机制。【基础】
答:Java 语言中一个显著的特点就是引入了垃圾回收机制,使 c++程序员头疼 的内存管理的问题迎刃而解,它使得 Java 程序员在编写程序的时候不再需要考 虑内存管理。由于有个垃圾回收机制,Java 中的对象不再有“作用域”的概念, 只有对象的引用才有“作用域”。垃圾回收可以有效的防止内存泄露,有效的使 用可以使用的内存。垃圾回收器通常是作为一个单独的低级别的线程运行,不可 预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回 收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回 收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。
59、ThreadLocal是什么
首先,它是一个数据结构,有点像HashMap,可以保存"key : value"键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。
ThreadLocal localName = new ThreadLocal();
localName.set(“占小狼”);
String name = localName.get();
在线程1中初始化了一个ThreadLocal对象localName,并通过set方法,保存了一个值占小狼,同时在线程1中通过localName.get()可以拿到之前设置的值,但是如果在线程2中,拿到的将是一个null。
这是为什么,如何实现?不过之前也说了,ThreadLocal保证了各个线程的数据互不干扰。
看看set(T value)和get()方法的源码
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings(“unchecked”)
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以发现,每个线程中都有一个ThreadLocalMap数据结构,当执行set方法时,其值是保存在当前线程的threadLocals变量中,当执行set方法中,是从当前线程的threadLocals变量获取。
所以在线程1中set的值,对线程2来说是摸不到的,而且在线程2中重新set的话,也不会影响到线程1中的值,保证了线程之间不会相互干扰。
那每个线程中的ThreadLoalMap究竟是什么?
ThreadLoalMap
本文分析的是1.7的源码。
从名字上看,可以猜到它也是一个类似HashMap的数据结构,但是在ThreadLocal中,并没实现Map接口。
在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,是不是很神奇,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。

这里需要注意的是,ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了。
hash冲突
没有链表结构,那发生hash冲突了怎么办?
先看看ThreadLoalMap中插入一个key-value的实现
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);

for (Entry e = tab[i];
     e != null;
     e = tab[i = nextIndex(i, len)]) {
    ThreadLocal<?> k = e.get();

    if (k == key) {
        e.value = value;
        return;
    }

    if (k == null) {
        replaceStaleEntry(key, value, i);
        return;
    }
}

tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();

}
每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647。
在插入过程中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程如下:
1、如果当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上;
2、不巧,位置i已经有Entry对象了,如果这个Entry对象的key正好是即将设置的key,那么重新设置Entry中的value;
3、很不巧,位置i的Entry对象,和即将设置的key没关系,那么只能找下一个空位置;
这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,然后判断该位置Entry对象中的key是否和get的key一致,如果不一致,就判断下一个位置
可以发现,set和get如果冲突严重的话,效率很低,因为ThreadLoalMap是Thread的一个属性,所以即使在自己的代码中控制了设置的元素个数,但还是不能控制其它代码的行为。
内存泄露
ThreadLocal可能导致内存泄漏,为什么?
先看看Entry的实现:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
    super(k);
    value = v;
}

}
通过之前的分析已经知道,当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中。
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
如何避免内存泄露
既然已经发现有内存泄露的隐患,自然有应对的策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。
如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。
ThreadLocal localName = new ThreadLocal();
try {
localName.set(“占小狼”);
// 其它业务逻辑
} finally {
localName.remove();
}

ThreadLocal归纳下来就2类用途:
• 保存线程上下文信息,在任意需要的地方可以获取!!!
• 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!
保存线程上下文信息,在任意需要的地方可以获取!!!
由于ThreadLocal的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。
常用的比如每个请求怎么把一串后续关联起来,就可以用ThreadLocal进行set,在后续的任意需要记录日志的方法里面进行get获取到请求id,从而把整个请求串起来。
还有比如Spring的事务管理,用ThreadLocal存储Connection,从而各个DAO可以获取同一Connection,可以进行事务回滚,提交等操作。
备注: ThreadLocal的这种用处,很多时候是用在一些优秀的框架里面的,一般我们很少接触,反而下面的场景我们接触的更多一些!
线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!
ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。但是ThreadLocal也有局限性,我们来看看阿里规范:

每个线程往ThreadLocal中读写数据是线程隔离,互相之间不会影响的,所以ThreadLocal无法解决共享对象的更新问题!
由于不需要共享信息,自然就不存在竞争问题了,从而保证了某些情况下线程的安全,以及避免了某些情况需要考虑线程安全必须同步带来的性能损失!!!
这类场景阿里规范里面也提到了:
ThreadLocal一些细节!
ThreaLocal使用示例代码:
public class ThreadLocalTest {
private static ThreadLocal threadLocal = new ThreadLocal<>();

public static void main(String[] args) {

    new Thread(() -> {
        try {
            for (int i = 0; i < 100; i++) {
                threadLocal.set(i);
                System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            threadLocal.remove();
        }
    }, "threadLocal1").start();


    new Thread(() -> {
        try {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            threadLocal.remove();
        }
    }, "threadLocal2").start();
}

}
代码截图:

代码运行结果:

从运行的结果我们可以看到threadLocal1进行set值对threadLocal2并没有任何影响!
Thread、ThreadLocalMap、ThreadLocal总览图

Thread类有属性变量threadLocals (类型是ThreadLocal.ThreadLocalMap),也就是说每个线程有一个自己的ThreadLocalMap ,所以每个线程往这个ThreadLocal中读写隔离的,并且是互相不会影响的。
一个ThreadLocal只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个ThreadLocal!!!
如图:

看到上面的几个图,大概思路应该都清晰了,我们Entry的key指向ThreadLocal用虚线表示弱引用 ,下面我们来看看ThreadLocalMap:

java对象的引用包括 : 强引用,软引用,弱引用,虚引用 。
因为这里涉及到弱引用,简单说明下:
弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,该对象仅仅被弱引用关联,那么就会被回收。
当仅仅只有ThreadLocalMap中的Entry的key指向ThreadLocal的时候,ThreadLocal会进行回收的!!!
ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是Entry是强引用,那么Entry里面存储的Object,并没有办法进行回收,所以ThreadLocalMap 做了一些额外的回收工作。

虽然做了但是也会存在内存泄漏风险(我没有遇到过,网上很多类似场景,所以会提到后面的ThreadLocal最佳实践!!!)
ThreadLocal的最佳实践!
ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是Entry是强引用,那么Entry里面存储的Object,并没有办法进行回收,所以ThreadLocalMap 做了一些额外的回收工作。

备注: 很多时候,我们都是用在线程池的场景,程序不停止,线程基本不会销毁!!!
由于线程的生命周期很长,如果我们往ThreadLocal里面set了很大很大的Object对象,虽然set、get等等方法在特定的条件会调用进行额外的清理,但是ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是后续在也没有操作set、get等方法了。
所以最佳实践,应该在我们不使用的时候,主动调用remove方法进行清理。

这里把ThreadLocal定义为static还有一个好处就是,由于ThreadLocal有强引用在,那么在ThreadLocalMap里对应的Entry的键会永远存在,那么执行remove的时候就可以正确进行定位到并且删除!!!
最佳实践做法应该为:
try {
// 其它业务逻辑
} finally {
threadLocal对象.remove();
}

思考
如果面试的时候,可以把上面的内容都可以讲到,个人觉得就非常好了,回答的就挺完美了。但是如果你可以进行下面的回答,那么就更完美了。对于ThreadLocal,我在看Netty源码的时候,还了解过FastThreadLocal,xxxxx一些列内容,那就是一个升级

在我本地进行测试,FastThreadLocal的吞吐量是jdkThreadLocal的3倍左右。
60、关于锁池和等待池
在Java中,每个对象都有两个池,锁(monitor)池和等待池
锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.
深入理解:
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
61、关于wait() ,notifyAll(),notify() 三个方法
wait()
public final void wait() throws InterruptedException,IllegalMonitorStateException
该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用 wait()之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait()方法。进入 wait()方法后,当前线程释放锁。在从 wait()返回前,线程与其他线程竞争重新获得锁。如果调用 wait()时,没有持有适当的锁,则抛出 IllegalMonitorStateException,它是 RuntimeException 的一个子类,因此,不需要 try-catch 结构。
notify()
public final native void notify() throws IllegalMonitorStateException
该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用 notify()时没有持有适当的锁,也会抛出 IllegalMonitorStateException。
该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个 wait()状态的线程来发出通知,并使它等待获取该对象的对象锁(notify 后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify的线程们。当第一个获得了该对象锁的 wait 线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用 notify 语句,则即便该对象已经空闲,其他 wait 状态等待的线程由于没有得到该对象的通知,会继续阻塞在 wait 状态,直到这个对象发出一个 notify 或 notifyAll。这里需要注意:它们等待的是被 notify 或 notifyAll,而不是锁。这与下面的 notifyAll()方法执行后的情况不同。
notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException
该方法与 notify ()方法的工作方式相同,重要的一点差异是:notifyAll 使所有原来在该对象上 wait 的线程统统退出 wait 的状态(即全部被唤醒,不再等待 notify 或 notifyAll,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll 线程退出调用了 notifyAll 的 synchronized 代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized 代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。
4.1.2.3. ExecutorService、Callable、Future 有返回值线程
有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行
Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务
返回的 Object 了,再结合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程
了。
//创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List list = new ArrayList();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " “);
// 执行任务并获取 Future 对象
Future f = pool.submit©;
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从 Future 对象上获取任务的返回值,并输出到控制台
System.out.println(“res:” + f.get().toString());
}
4.1.2.4. 基于线程池的方式
线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销
毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。
// 创建线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() { // 提交多个线程任务,并执行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running …”);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
4.1.3. 4 种线程池
Java 里面线程池的顶级接口是 Executor,但是严格意义上讲 Executor 并不是一个线程池,而
只是一个执行线程的工具。真正的线程池接口是 ExecutorService。
4.1.3.1. newCachedThreadPool
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行
很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造
的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并
从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资
源。
4.1.3.2. newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

4.1.3.3. newScheduledThreadPool
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(newRunnable(){
@Override
public void run() {
System.out.println(“延迟三秒”);
}
}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(newRunnable(){
@Override
public void run() {
System.out.println(“延迟 1 秒后每三秒执行一次”);
}
},1,3,TimeUnit.SECONDS);
4.1.3.4. newSingleThreadExecutor
Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去

  • 1
    点赞
  • 0
    评论
  • 6
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值