线程面试题整理

整理了线程相关的面试题,有什么不对的请指出。

1、多线程中的i++线程安全吗?为什么?

        是不安全的,因为i++可以分为3步,1.线程读取i。2.对i进行加一操作。3.把i放进内存中。线程1,在进行i++操作的第一步的时候,线程2,进入操作完i++。线程1再进行i++剩下未完成的两个操作。这样就是出现数据安全问题。即线程1更新的可能会覆盖线程2更新后的值。

        那么在i上加 volatile可以吗。答案是不可以volatile只是保证了数据的可见性,并没有保证数据的原子性。多线程同时读取这个共享变量的值,就算保证了其他线程修改的可见性,也不能保证线程之间读取到的同样的值然后相互覆盖对象的值的情况 。这里可以使用  AtomicInteger可以保证变量的原子性

        原子性就是一个同一时间只有一个线程操作,一套操作要么执行完毕,要么都不执行。

        可见性:多线程时一个线程对共享数据更新。其他线程就会立即看见这这数据更新后的值。可以使用volatile修饰变量。

        有序性我们编写的代码,编译器编译后可能并不是我们写的代码的顺序,只要不影响最终结果如:int i = 0;int j = 2;可能顺序不一样。

 

2、如何线程安全的实现一个计数器?

        使用AtomicInteger类来作为变量,它保证了数据的原子性。并且加锁。

 

3、多线程同步的方法

  • 使用 synchronize同步方法
  • 使用同步代码块
  • 使用 volatile修饰变量
  • 使用锁重入  Lock lock = new ReentrantLock()使用lock.lock(),lock.nulock()来获取锁和释放锁。
  • 使用局部变量实现同步 使用ThreadLocal()管理变量,每一个使用该变量的线程都获得 该变量的副本。
  • 使用阻塞队列实现同步
  • 使用原子变量AtomicInteger

        

4、介绍一下生产者消费者模式?

        一个模块负责生产,一个模块负责处理消费。他们共用一个储存空间,生产者想其中存数据,消费者从其中取数据。

  • 使用wait()和nitify()可以实现。
  • 使用阻塞队列实现

5、线程,进程,然后线程创建有很大开销,怎么优化?

        使用线程池,线程池维护了多个线程,当我们的程序需要用到线程时,就直接从其中获取,不用的是时候放回池中。使用线程池可以减低资源的消耗。可以提高响应的速度。可以方便线程的管理。可以提高线程的使用率。

    

6、线程池运行流程,参数,策略

运行流程:

  • 线程池先判断池中是否有没执行任务的线程,如果有,侧创建出来一个来使用(这里指的是corePollSize)。
  • 如果线程池中的线程都在执行任务。将任务放在一个队列中等待分配线程。
  •  如果这个队列也满,则在线程池中创建一个线程来给新的任务。如果运行的线程大于我们设置的最大线程数量(maximumPollSize)时,就会把任务交给饱和策略(默认是抛出异常)来处理。
  • new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,milliseconds,runnableTaskQueue,handler);我们看到最后一个参数handler,这个就是饱和策略。有4中:AbortPolicy:直接抛出异常、 CallerRunsPolicy:只用调用者所在线程来运行任务、 DiscardOldestPolicy:丢弃队列中最近的一个任务,并执行当前任务、 DiscardPolicy:不处理,丢弃掉

参数:

  • corePollSize:核心线程数。在创建线程池之后,池中还没有线程,当新的任务来的时候就会在池中创建,当创建的线程数达到corePollSize的数量的时候,就会吧任务放进一个队列中等待。
  • maximumPollSize:最大线程数.
  • keepAliveTime:空闲线程保留的时间。
  • TimeUnit:空闲时间保留的时间单位。TimeUnit.DAYS  天。TimeUnit.HOURS 小时。等等
  • BlockingQueue<Runnable>队列
  • ThreadFactory:线程工厂
  • RejectedExecutionHandler:策略

 

    策略:饱和策略。

7、讲一下AQS吧。

        AbstractQueuedSynchronizer抽象队列同步器。实现锁的一个框架

        

8、创建线程的方法,哪个更好,为什么?

        一共有3中创建线程的方式1.继承Thread类,重写run()方法。2.实现Runnable接口。3.使用线程池。

        使用Runnable更好,因为实现了Runnable,还需要用Thread类进行包装,才能使用start方法。而对象的包装实现了资源的共享。

        一个类可以实现多个接口,避免单继承带来的局限

9、Java中有几种方式启动一个线程?

  • 继承Thread类,重写它的run类。然后实例化调用start()方法
  • 实现Runnable接口,实现run方法。使用Thread包装该类。实例化、调用start()方法
  • 使用线程池,使用execute把任务添加到其中

        

10、Java中有几种线程池?

        一共有5种线程池。ExecutorService pool = Executor.newCachedThreadPool();

                

  • newCachedThreadPool():必要是创建新线程,空闲线程会被保留60秒
  • newFixedThreadPoll():该池包含固定数量的线程,空线程会被一直保留
  • newSingleThreadExecutor():只有一个线程的“池”,该线程顺序执行每一个提交的任务
  • newScheduledThreadPool():用于预定执行而构建的固定线程池,代替java.util.Timer;
  • newSingleThreadScheduledExecutor():用于预定执行的单线程“池”。

 

11、线程池有什么好处?

        线程的创建需要很大的开销,会导致系统效率缓慢。使用是线程池可以直接从池中获取,减少了创建的开销。池也可以提供定时、定期、单线程并发数的管理。

12、cyclicbarrier和countdownlatch的区别

        cyclicbarrier详解: 

栅栏”,栅栏类可以让线程在栅栏处汇集,当任一一个线程到达栅栏处,就会调用await()方法,使之阻塞。当所有的线程都汇集在栅栏处,栅栏将会打开,释放出所有的线程。使用 await()设置栅栏处。使用reset()可以使栅栏处于初始状态。

        countdownlatch详解: 

闭锁”,闭锁类可以延缓其他线程的执行,相当于一扇门,闭锁在达到闭锁之前,这扇门将一个关着,当某个条件使闭锁处于结束状态。这扇们就会打开,这事其他线程就会继续执行了。这扇门是一次性的,打开过后就不会再关闭了。同样使用await()处于等待状态。前面提到的“条件”,就是一个int数据,可以对他进行初始化。当发生什么事了可以使用countDown()方法,对int数值递减。当int 等于一的时候,这扇门就打开了 。打开了就不会恢复到初始状态。

 

        区别:

  • Cyclicbarrier类可以使用reset()可以重用。而CountDownLatch类,释放线程过后就不会再使用了。
  • Cyclicbarrier类中有getNumberWaiting()正处于等待状态的线程数。getParties()使栅栏打开还需要等待多少线程。等这些方法。

                    而CountDownLatch类有获取计数值,和递减的方法

 

13、如何理解Java多线程回调方法?

        java回调机制;

        java多线程回调: 

        

14、概括的解释下线程的几种可用状态。

        线程的状态:

  • New(新建): 当使用new操作符创建一个线程时,那么这个线程就处于新建状态。但是还没有开始运行。
  • Runnable(可运行):当使用了start()方法线程处于runnable状态。一个可运行状态的线程可能运行可可能还没有运行,这取决于线程调度器是否给该线程分配资源。
  •  Blocked(被阻塞):当一个线程试图获取一个对象锁,而该锁被其他线程锁持有,则该线程进入阻塞状态,当其他线程释放锁,并且线程调度器运行它持有的时候,该线程将会变成一个非阻塞状态。
  • Waiting(等待):当线程等待另一个线程通知调度器一个条件是,它自己进入等待状态,调用Object.wait()或者Thread.join()方法时。
  • Timed waiting(计时等待):有一个方法有一个超时参数。调用他们导致线程进入计时等待状态。比如sleep方法。
  • Terminated(被终止):两个原因一个是执行完run。第二个原因是在run中没有捕获一个异常导致线程意外死亡。         

                                          

java线程常见面试题 java线程安全面试题_等待状态

    

15、同步方法和同步代码块的区别是什么?

            同步方法:是有 synchronize修饰的方法,他的锁就是当前类对象

            同步代码块:是有synchronize修饰的代码块,默认为当前类对象,但是也可以放其他的类对象。

 

16、在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?

        在jvm中,每一个对象都有一把锁,一旦被synchronize修饰,那么这部分就会被监视器监视,确保一次只有一个线程执行该部分的代码。

17、sleep() 和 wait() 有什么区别?

sleep():

  • 让线程处于计时等待状态,到倒计时为0时就继续执行,这个过程它将不会释放锁。
  • 它会主动让出cpu,并且他可以在任何地方使用。Thread.sleep().
  • 属于Thread的方法

wait():

  • 让线程处于等待状态,直到出现notify(),或者notifyAll()就会唤醒这个线程继续执行,这个过程他将会释放锁
  • 它只能在synchronize方法或代码块中使用。
  • 属于Object方法

 

 

18、同步和异步有何异同,在什么情况下分别使用他们?举例说明。

        同步:当多个线程对相同的数据进行操作的时候,使用同步。

        异步: 当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。

19、设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。使用内部类实现线程,对j增减的时候没有考虑顺序问题。

    

20、启动一个线程是用run()还是start()?

        start()

21、请说出你所知道的线程同步的方法

  • 同步方法
  • 同步代码块
  • wait()和notify()
  • volatile
  • Lock
  • 局部变量
  • blockQueue

 

22、stop()和suspend()方法为何不推荐使用?

stop():该方法会终止所有未结束的方法,包括run()方法。当线程终止,它会立即释放被它锁住的所有对象的锁。这会导致对象处于不一致的状态。例如当一个账户向另一个账户转账的时候,调用了stop()方法,钱已经转出了,但是另一个账户还没有收到钱。银行对象遭到的破坏。stop之所以被弃用,是因为无法知道什么时候终止线程时安全的。

 

suspend():挂起线程。这个方法不会破坏对象。但是如果用suspend挂起一个持有一个锁的线程,那么该锁在恢复之前是不可用的。如果调用suspend()方法的线程试图获得同一个锁,那么程序就会锁死:被挂起的线程等待恢复,而将其挂起的线程等待获得锁。假如有A,B两个线程,A线程在获得某个锁之后被suspend阻塞,这时A不能继续执行,线程B在获得相同的锁之后才能调用resume方法将A唤醒,但是此时的锁被A占有,B不能继续执行,也就不能及时的唤醒A,此时A,B两个线程都不能继续向下执行而形成了死锁。这就是suspend被弃用的原因。

        

23、线程的sleep()方法和yield()方法有什么区别?

sleep():休眠方法,该方法可以使当前线程处理阻塞状态,是Thread类中的静态方法,并且它不会释放锁。可以让其他所有线程都有机会执行,无优先级之分。有 InterruptedException异常

 

yield():让步方法,它也是Thread中的静态方法,使用它可以让线程处于就绪状态。yield()只是让线程暂停一下,让调度器重新分配,只有优先级和当前线程相同,或者比之高的线程并且也处于就绪状态的, 可以获得执行的机会。yield没有声明异常。

24、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?

    不能。因为synchronize方法使用的是当前类对象。当一个线程进入A方法时,它还没有释放锁。必须等A方法释放锁了,B在有机会执行。

25、请说出与线程同步以及线程调度相关的方法。

            wait(): 使线程处于等待状态,并且会释放锁

            sleep():时线程处于计时等待状态。并且不会释放锁,还可能抛出异常

            notify():唤醒一个处于等待状态的线程。至于唤醒哪一个由jvm决定,并且与优先级无关 

            notifyAll():唤醒全部处于等待状态的线程。

26、举例说明同步和异步

           同步:当多个线程对同一个内存进行读写的时候,可能数据会被覆盖,所以就需要用到同步,同步的“同”并不是同时的意思,而是协同,协助、相互配合。你说完之后,我在说。你在调用这个方法的时候,我就不能调用。     

           异步:比如你发送一个请求,不用等待我的回应,你就可以继续给我发

27、说说线程的基本状态以及状态之间的关系?

         

            线程有五个状态:new创建状态,Runnable就绪状态,Running运行状态,Dead消亡状态,Blocked阻塞状态。创建线程通过start方法进入就绪状态,获取cpu的执行权进入运行状态,失去cpu执行权会回到就绪状态,运行状态完成进入消亡状态,运行状态通过sleep方法和wait方法进入阻塞状态,休眠结束或者通过notify方法或者notifyAll方法释放锁进入就绪状态

28、如何保证线程安全?

        使用同步机制。

29、引起线程中断的常见原因是什么?

 

30、一个线程执行完run方法之后,进入了什么状态,该线程还能在调用start方法吗

            将会产生java.lang.IllegalThreadStateException

 

31、线程在什么状态下调用isAlive方法返回的值是false

        新建状态的时候。

 

32、线程调用interrupt的作用是什么?

        线程中没有强制中断的方法,然而interrupt方法可以用来请求终止线程,当对一个线程调用interrupt方法的时候,线程的中断状态将会被置位,每一个线程都具有一个boolean标志。每个线程都应该不时地检查这个标志。

        要想弄清中断知否被置位可以使用Thread.currentThread方法获得当前线程,然后调用isInterrupted()来判断。如果线程被阻塞就无法判断,并且会抛出一个异常,当在一个被阻塞的线程上使用interrupt()方法的时候也会被抛出异常。

        没有任何语言方面的需求要求一个被中断的线程应该终止。中断一个线程只不过是引起他的注意。被中断的线程可以决定如何响应中断。某些线程时很重要以至于应该处理完异常后,继续执行,而不理会中断。但是,更靠谱的情况是,线程将简单的将中断做为一个请求。