关于java:Java面试问题汇总线程

36次阅读

共计 7900 个字符,预计需要花费 20 分钟才能阅读完成。

线程

1. 如何保障线程平安

通过 正当的工夫调度 避开共享资源的存取抵触 。另外,在并行任务设计上能够通过适当的策略,保障工作与工作之间不存在共享资源,设计一个规定来 保障一个客户的计算工作和数据拜访只会被一个线程或一台工作机实现,而不是把一个客户的计算工作调配给多个线程去实现。

2. 线程的根本状态以及状态之间的关系

  • Running 示意 运行 状态
  • Runnable 示意 就绪 状态(万事俱备,只欠 CPU
  • Blocked 示意 阻塞 状态,阻塞状态又有多种状况,可能是因为 调用 wait()办法 进入期待池,也可能是 执行同步办法或同步代码块进入等锁池 ,或者是 调用了 sleep()办法或 join()办法期待休眠或其余线程完结 ,或是因为 产生了 I / O 中断

3. 线程池(thread pool)

在面向对象编程中,创立和销毁对象是很费时间的 ,因为创立一个对象要获取内存资源或者其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象,以便可能在对象销毁后进行垃圾回收。所以进步服务程序效率的一个伎俩就是尽可能减少创立和销毁对象的次数,特地是一些很耗资源的对象创立和销毁,这就是”池化资源”技术产生的起因。线程池顾名思义就是 当时创立若干个可执行的线程放入一个池(容器)中,须要的时候从池中获取线程 不必自行创立,应用结束 不须要销毁线程而是 放回池中,从而缩小创立和销毁线程对象的开销。
Java 5+ 中的 Executor 接口定义一个执行线程的工具。它的子类型即线程池接口是 ExecutorService。要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很分明的状况下,因而在工具类 Executors 面提供了一些动态工厂办法,生成一些罕用的线程池,如下所示:

  • newSingleThreadExecutor:创立一个 单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有工作。如果这个惟一的线程因为异样完结,那么会有一个新的线程来代替它。此线程池保障所有工作的执行程序依照工作的提交程序执行。
  • newFixedThreadPool:创立 固定大小的线程池。每次提交一个工作就创立一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会放弃不变,如果某个线程因为执行异样而完结,那么线程池会补充一个新线程。
  • newCachedThreadPool:创立一个 可缓存的线程池。如果线程池的大小超过了解决工作所须要的线程,那么就会回收局部闲暇(60 秒不执行工作)的线程,当工作数减少时,此线程池又能够智能的增加新线程来解决工作。此线程池不会对线程池大小做限度,线程池大小齐全依赖于操作系统(或者说 JVM)可能创立的最大线程大小。
  • newScheduledThreadPool:创立一个 大小有限的线程池。此线程池反对定时以及周期性执行工作的需要。
  • newSingleThreadExecutor:创立一个 单线程的线程池。此线程池反对定时以及周期性执行工作的需要。

4. 同步和异步

如果零碎中 存在临界资源 (资源数量少于竞争资源的线程数量的资源),例如正在写的数据当前可能被另一个线程读到,或者正在读的数据可能曾经被另一个线程写过了,那么这些数据就 必须进行同步存取 (数据库操作中的排他锁就是最好的例子)。当应用程序在对象上 调用了一个须要破费很长时间来执行的办法 ,并且 不心愿让程序期待办法的返回时 ,就应该 应用异步编程 ,在很多状况下采纳异步路径往往更有效率。事实上,所谓的 同步就是指阻塞式操作,而异步就是非阻塞式操作

5. 线程同步和线程调度的相干办法

  • wait():使一个线程处于 期待(阻塞)状态 ,并且 开释 所持有的对象的
  • sleep():使一个正在运行的线程处于 睡眠状态 ,是一个 静态方法 ,调用此办法 要解决 InterruptedException 异样
  • notify()唤醒 一个 处于期待状态的线程 ,当然在调用此办法的时候,并不能确切的唤醒某一个期待状态的线程,而是 由 JVM 确定唤醒哪个线程,而且与优先级无关;
  • notityAll()唤醒所有处于期待状态的线程 ,该办法并不是将对象的锁给所有线程,而是让它们竞争, 只有取得锁的线程能力进入就绪状态
    通过 Lock 接口提供了显式的锁机制(explicit lock),加强了灵活性以及对线程的协调。Lock 接口中定义了加锁(lock())和解锁(unlock())的办法,同时还提供了 newCondition()办法来产生用于线程之间通信的 Condition 对象;此外,Java 5 还提供了信号量机制(semaphore),信号量能够用来限度对某个共享资源进行拜访的线程的数量。在对资源进行拜访之前,线程必须失去信号量的许可(调用 Semaphore 对象的 acquire()办法);在实现对资源的拜访后,线程必须向信号量偿还许可(调用 Semaphore 对象的 release()办法)。

6. 一个线程进入一个对象的 synchronized 办法 A,之后其它线程是否可进入此对象的 synchronized 办法 B

不能。其它线程只能拜访该对象的非同步办法,同步办法则不能进入。因为非静态方法上的 synchronized 修饰符要求执行办法时要取得对象的锁,如果曾经进入 A 办法阐明对象锁曾经被取走,那么试图进入 B 办法的线程就只能在等锁池(留神不是期待池哦)中期待对象的锁。执行 synchronized 办法须要获取锁,进入 A 办法阐明对象锁曾经被取走,不能再执行 B 办法

7. 线程的 sleep()办法和 yield()办法有什么区别

  1. sleep()办法给其余线程运行机会时不思考线程的 优先级 ,因而会给低优先级的线程以运行的机会;yield() 办法只会给雷同优先级或更高优先级的线程以运行的机会;
  2. 线程执行 sleep()办法后转入 阻塞(blocked)状态 ,而执行 yield() 办法后转入 就绪(ready)状态
  3. sleep()办法申明抛出 InterruptedException,而 yield() 办法没有申明任何异样;
  4. sleep()办法比 yield()办法(跟操作系统 CPU 调度相干)具备更好的 可移植性

8.Java 中有几种办法能够实现一个线程

有三种形式能够用来创立线程:

  • 继承 Thread 类
  • 实现 Runnable 接口
  • 通过 Callable 和 FutureTask 创立线程

实现 Runnable 和实现 Callable 接口的形式基本相同,不过是后者执行 call()办法有返回值。
1、如果须要拜访以后线程,必须调用 Thread.currentThread()办法。
2、继承 Thread 类的线程类不能再继承其余父类(Java 单继承决定)。

注:个别举荐采纳实现接口的形式来创立多线程

应用程序能够应用 Executor 框架来创立线程池
Executor 框架包含 3 大部分:

(1)工作。也就是工作单元,包含被执行工作须要实现的接口:Runnable 接口或者 Callable 接口;

(2)工作的执行。也就是把工作分派给多个线程的执行机制,包含 Executor 接口及继承自 Executor 接口的 ExecutorService 接口。

(3)异步计算的后果。包含 Future 接口及实现了 Future 接口的 FutureTask 类。

9.stop()和 suspend()办法为何不举荐应用

拥护应用 stop(),是因为它 不平安 。它会 解除由线程获取的所有锁定 ,而且如果对象处于一种 不连贯状态 ,那么 其余线程能 在那种状态下 检查和批改它们。后果很难查看出真正的问题所在。

suspend()办法容易产生死锁 。调用 suspend() 的时候,指标线程会停下来,但却依然持有在这之前取得的锁定。此时,其余任何线程都不能拜访锁定的资源,除非被”挂起”的线程复原运行。对任何线程来说,如果它们想复原指标线程,同时又试图应用任何一个锁定的资源,就会造成死锁。所以不应该应用 suspend(),而 应在本人的 Thread 类中置入一个标记,指出线程应该流动还是挂起 标记指出线程 应该挂起 便用 wait()命其进入期待状态。 标记指出线程 该当复原 则用 一个 notify() 重新启动线程。

10. 启动一个线程是用 run()还是 start()

启动一个线程是调用 start()办法 ,使线程所代表的虚构处理机处于 可运行状态 ,这意味着它能够由 JVM 调度并执行。这 并不意味着线程 就会 立刻运行 run() 办法能够产生必须退出的标记来进行一个线程

11. 外部类实现 4 个线程, 其中两个线程每次对 j 减少 1,另外两个线程对 j 每次缩小 1

public class ThreadTest1{
    private int j;
    public static void main(String args[]){ThreadTest1 tt=new ThreadTest1();
        Inc inc=tt.new Inc();
        Dec dec=tt.new Dec();
        for(int i=0;i<2;i++){Thread t=new Thread(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(int i=0;i<100;i++){inc();
            }
        }
    }
    class Dec implements Runnable{public void run(){for(int i=0;i<100;i++){dec();
            }
        }
    }
}

12.sleep() 和 wait() 有什么区别

  • sleep 是线程类(Thread)的办法,导致 此线程暂停 执行指定工夫,把 执行机会给其余线程 ,然而 监控状态仍然放弃 ,到时后会 主动恢 复。调用 sleep不会开释对象锁
  • wait 是 Object 类的办法,对此对象调用 wait 办法导致 本线程放弃对象锁 进入 期待此对象的 期待锁定池 只有 针对此对象收回notify 办法(或 notifyAll)后本线程才进入对象锁定池筹备取得对象锁进入运行状态

13. 监视器(Monitor)

监视器和锁在 Java 虚拟机中是一块应用的。监视器监督 一块 同步代码块 确保一次只有一个 线程执行同步代码块。每一个监视器都和一个对象援用相关联。线程在 获取锁之前不容许执行同步代码

14. 同步办法和同步代码块的区别

  • 同步办法默认 用 this 或者以后类 class 对象作为锁
  • 同步代码块 能够抉择以什么来加锁 ,比同步办法要更细颗粒度,咱们能够 抉择 只同步会产生同步问题的 局部代码 而不是整个办法。

15. 线程从创立到死亡的几种状态

  1. 新建(new):新创建了一个线程对象。
  2. 可运行 (runnable):线程对象创立后,其余线程(比方 main 线程)调用了该对象 的 start () 办法。该状态的线程位于可运行线程池中,期待被线程调度选中,获 取 cpu 的使用权。
  3. 运行 (running):可运行状态(runnable) 的线程取得了 cpu 工夫片(timeslice),执行程序代码。
  4. 阻塞 (block):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,临时进行运行。直到线程进入可运行(runnable) 状态,才有 机会再次取得 cpu timeslice 转到运行 (running) 状态。阻塞的状况分三种:

    • 期待阻塞 :运行(running) 的线程执行 o . wait ()办法,JVM 会把该线程放 入期待队列 ( waitting queue) 中。
    • 同步阻塞 :运行(running) 的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池 (lock pool) 中。
    • 其余阻塞 : 运行(running) 的线程执行 Thread . sleep (long ms)或 t . join ()办法,或者收回了 I / O 申请时,JVM 会把该线程置为阻塞状态。当 sleep ()状态超时、join ()期待线程终止或者超时、或者 I / O 处理完毕时,线程从新转入可运行 ( runnable) 状态。
  5. 死亡 (dead):线程 run ()、main () 办法执行完结,或者因异样退出了 run () 办法,则该线程完结生命周期。死亡的线程不可再次复活。

16.Java 多线程回调

所谓回调,就是客户程序 C 调用服务程序 S 中的某个办法 A,而后 S 又在某个时候反过来调用 C 中的某个办法 B,对于 C 来说,这个 B 便叫做回调办法。

17. 启动线程有哪几种形式

第一种:继承 Thread 类创立线程类

  1. 定义 Thread 类的子类,并重写该类的 run 办法,该 run 办法的办法体就代表了线程要实现的工作。因而把 run()办法称为执行体。
  2. 创立 Thread 子类的实例,即创立了线程对象。
  3. 调用线程对象的 start()办法来启动该线程。
public class FirstThreadTest extends Thread{
    int i = 0;
    // 重写 run 办法,run 办法的办法体就是现场执行体
    public void run()
    {for(;i<100;i++){System.out.println(getName()+" "+i);
         
        }
    }
    public static void main(String[] args)
    {for(int i = 0;i< 100;i++)
        {System.out.println(Thread.currentThread().getName()+":"+i);
            if(i==20)
            {new FirstThreadTest().start();
                new FirstThreadTest().start();
            }
        }
    }
  
}

上述代码中 Thread.currentThread()办法返回以后正在执行的线程对象。GetName()办法返回调用该办法的线程的名字。

第二种:通过 Runnable 接口创立线程类

  1. 定义 runnable 接口的实现类,并重写该接口的 run()办法,该 run()办法的办法体同样是该线程的线程执行体。
  2. 创立 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创立 Thread 对象,该 Thread 对象才是真正的线程对象。
  3. 调用线程对象的 start()办法来启动该线程。
public class RunnableThreadTest implements Runnable
{
  
    private int i;
    public void run()
    {for(i = 0;i <100;i++)
        {System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
    public static void main(String[] args)
    {for(int i = 0;i < 100;i++)
        {System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20)
            {RunnableThreadTest rtt = new RunnableThreadTest();
                new Thread(rtt,"新线程 1").start();
                new Thread(rtt,"新线程 2").start();}
        }
  
    }
  
}

第三种:通过 Callable 和 Future 创立线程

  1. 创立 Callable 接口的实现类,并实现 call()办法,该 call()办法将作为线程执行体,并且有返回值。
  2. 创立 Callable 实现类的实例,应用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call()办法的返回值。
  3. 应用 FutureTask 对象作为 Thread 对象的 target 创立并启动新线程。
  4. 调用 FutureTask 对象的 get()办法来取得子线程执行完结后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
  
public class CallableThreadTest implements Callable<Integer>
{public static void main(String[] args)
    {CallableThreadTest ctt = new CallableThreadTest();
        FutureTask<Integer> ft = new FutureTask<>(ctt);
        for(int i = 0;i < 100;i++)
        {System.out.println(Thread.currentThread().getName()+"的循环变量 i 的值"+i);
            if(i==20)
            {new Thread(ft,"有返回值的线程").start();}
        }
        try
        {System.out.println("子线程的返回值:"+ft.get());
        } catch (InterruptedException e)
        {e.printStackTrace();
        } catch (ExecutionException e)
        {e.printStackTrace();
        }
  
    }
  
    @Override
    public Integer call() throws Exception
    {
        int i = 0;
        for(;i<100;i++)
        {System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }
  
}

18.cyclicbarrier 和 countdownlatch 的区别

CountDownLatch 和 CyclicBarrier 都可能实现线程之间的期待,只不过它们侧重点不同:

CountDownLatch 个别用于 某个线程 A 期待若干个其余线程执行完工作之后,它才执行

而 CyclicBarrier 个别用于 一组线程相互期待至某个状态 ,而后这一组线程再 同时执行

另外,CountDownLatch 是不可能重用的,而 CyclicBarrier 是能够重用的。

CountDownLatch CyclicBarrier
减计数形式 加计数形式
计算为 0 时开释所有期待的线程 计数达到指定值时开释所有期待线程
计数为 0 时,无奈重置 计数达到指定值时,计数置为 0 从新开始
调用 countDown()办法计数减一,调用 await()办法只进行阻塞,对计数没任何影响 调用 await()办法计数加 1,若加 1 后的值不等于构造方法的值,则线程阻塞
不可反复利用 可反复利用

具体应用:https://blog.csdn.net/tolcf/a…

19. 简短阐明一下你对 AQS 的了解

AQS 其实就是一个能够给咱们实现锁的框架
外部实现的要害是:先进先出的队列、state 状态
定义了外部类 ConditionObject
领有两种线程模式独占模式和共享模式。
在 LOCK 包中的相干锁 (罕用的有 ReentrantLock、ReadWriteLock) 都是基于 AQS 来构建,个别咱们叫 AQS 为同步器。

20. 多线程中的 i ++ 线程平安吗

不平安。i++ 不是原子性操作。i++ 分为读取 i 值,对 i 值加一,再赋值给 i ++,执行期中任何一步都是有可能被其余线程抢占的。

正文完
 0