Java多线程面试题及回答

来源:互联网 发布:sql sum函数 小数点 编辑:程序博客网 时间:2024/06/05 22:42

1.现在有T1,T2,T3三个线程,你怎么样保证T2在T1执行完后执行,T3在T2执行完后执行。

这个考察的知识点是对join的使用

实际上先启动三个线程中哪一个都行,
因为在每个线程的run方法中用join方法限定了三个线程的执行顺序。
即便是第二个线程先启动执行了,由于t1.join()方法,
使得线程2需要等待线程1运行结束后才能继续运行。
所以三个线程的启动顺序无关紧要!!!
/** *  * @author zy * @date 2017年10月22日 下午7:17:50 * @Decription 现在有T1,T2,T3三个线程,你怎么样保证T2在T1执行完后执行,T3在T2执行完后执行。 */public class JoinTest {public static void main(String[] args) {final Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {// TODO Auto-generated method stubSystem.out.println("t1");}});final Thread t2 = new Thread(){@Overridepublic void run() {try {t1.join();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("t2");}};final Thread t3 = new Thread(new Runnable() {@Overridepublic void run() {try {t2.join();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("t3");}});t2.start();t1.start();t3.start();}}

2.在Java中Lock接口比synchronized块的优势是什么?

lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。

Lock:是Lock接口中使用最多的获取锁的方法,如果锁被其他线程占用就等待。由于Lock接口是基于JDK层面的,所以,锁的释放动作必须手动进行。不像synchronized是基于Java语言的特性,属于JVM层面,锁的获取和释放动作都由JVM自动进行,对开发者是透明的。

使用方式:
Lock lock = ...; lock.lock();
try{    
   //处理任务
}catch(Exception ex){
}finally{
     lock.unlock();   //释放锁
}
 
tryLock:表示用来尝试获取锁,如果获取成功即返回TRUE,如果锁被其他线程占用,则返回FALSE;该方法会立即返回结果,不会一直处于等待状态。
 
tryLock(long time,TimeUnit unit):与tryLock类似,区别在于这个方法在拿不到锁的情况下会等待一个时间time,在时间期限之内如果还拿不到锁,就返回FALSE;同时可以相应中断。如果在一开始或者等待期间获得了锁,则返回true。
问题:如果在开始没有拿到锁,那么代码块会进入到等待状态么?
猜想:我觉得会进入等待状态,不过在等待的这段时间内,会存在一个响应机制来监测别的线程是否释放了锁,如果释放了锁,则直接有该线程获取锁,该方法返回TRUE;如果没有释放,那么该方法将不再享有响应机制的提醒,并返回FALSE。
使用方式:
Lock lock = ...;
if(lock.tryLock()) {
      try{
         //处理任务     
   }catch(Exception ex){
     }finally{
          lock.unlock();   //释放锁      
  }  
}else {   
  //如果不能获取锁,则直接做其他事情
}
lockInterruptibly:当通过这个方法尝试获取锁时,如果线程正在等待获取锁,那么这个线程能够响应中断,即被自己或者其他线程中断线程的等待状态。例如,当两个线程同时通过lock.lockInterruptibly()方法获取锁时,假如线程A获取了锁,那么线程B就只能进入等待状态,那么对线程B调用ThreadB.interrupt()能够中断线程B的等待状态。
 
注意:lockInterruptibly方法必须放在try块中或者在调用lockInterruptibly的方法外声明抛出InterruptedException,推荐使用后者。
 
使用方式:
public void method() throws InterruptedException {     lock.lockInterruptibly();     try {        //.....     }     finally {         lock.unlock();     }   }
疑问:释放掉线程的等待状态有什么用处?
相比于synchronized,等待状态的线程无法响应中断。提高了代码的灵活性,当不想持续的等待下去时,响应中断去做其余的事情,更具灵活性。
 
ReadWriteLock
该锁提升了读操作的效率,不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程也会一直等待释放写锁。

 

synchronized字段:

不管该关键字是修饰方法还是修饰同步代码块,synchronzed拿到的都是对象。
当synchronized修饰的是方法时,synchronized所拿到的是调用该方法的对象的锁,一般情况下都是this的锁;
当synchronized()修饰的同步代码块时,synchronized拿到的是指定对象的锁。
 
扩展:
当synchronized修饰的静态方法时,由于静态方法不包含this,属于类层次的方法,所以,synchronized拿到的是这个方法所属Class对象的锁。
java中的对象每个只含有一个锁,通过synchronized来获取对象的锁。
 
注意:当方法异常退出时,其对象锁可以有JVM进行释放。
占有锁的线程释放锁时一般是以下三种情况:
1、 占有锁的线程执行完了代码块,然后释放对锁的占有;
2、 占有锁的线程发生了异常,此时JVM会让线程自动释放锁;
3、 占有锁的线程调用了wait()方法,从而进入了WAITING状态需要释放锁。
 
探究-->对象都含有什么锁?
对象锁:又称独占排它锁,通过名字我们便可知道,在多线程程序中,一旦一个线程到达了共享区(即synchronized修饰的区域)。那么该线程将拥有该共享区的对象锁,其他线程想要进入,只能等到该线程释放了锁才可进入。对应于非静态方法和非静态代码块。
类锁:方法或代码块所在类的类对象的锁。对应于静态方法和静态代码块。

Lock和synchronized的选择:
1、 Lock是一个接口,属于JDK层面的实现;而synchronized属于Java语言的特性,其实现有JVM来控制。
2、 synchronized在发生异常时,会自动释放掉锁,故不会发生死锁现(此时的死锁一般是代码逻辑引起的);而Lock必须在finally中主动unlock锁,否则就会出现死锁。
3、 Lock能够响应中断,让等待状态的线程停止等待;而synchronized不行。
4、 通过Lock可以知道线程是否成功获得了锁,而synchronized不行。
5、 Lock提高了多线程下对读操作的效率。


3.在Java中wait和sleep方法的不同?

最大的不同是等待时wait会释放锁(释放资源),而sleep一直持有锁(资源)。wait通常被用于线程间交互,sleep通常用于暂停执行。并且wait是Object类的方法,而sleep是Thead类的方法。


4.用Java实现阻塞队列(Java util.concurrent包下重要的数据结构)

使用wait()和notify()方法来实现阻塞队列(还可以使用Java 5中的并发类来实现,这个等有时间了再来研究)

阻塞队列:阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来,如从队列中移除一个或者多个元素,或者完全清空队列,下图展示了如何通过阻塞队列来合作:


    public class BlockingQueue {              private List queue = new LinkedList();        private int  limit = 10;              public BlockingQueue(int limit){          this.limit = limit;        }                    public synchronized void enqueue(Object item)        throws InterruptedException  {          while(this.queue.size() == this.limit) {            wait();          }          if(this.queue.size() == 0) {            notifyAll();          }          this.queue.add(item);        }                    public synchronized Object dequeue()        throws InterruptedException{          while(this.queue.size() == 0){            wait();          }          if(this.queue.size() == this.limit){            notifyAll();          }                return this.queue.remove(0);        }            } 


5.用Java实现生产者,消费者问题

同样使用“阻塞队列实现”

//生产者class Productor implements Runnable{Clerk clerk;public Productor(){}public Productor(Clerk clerk){this.clerk = clerk;}public void run() {System.out.println("生产者开始生产");while(true){try {Thread.sleep((int)Math.random()*1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}clerk.addProduct();}}}//消费者class Consumer implements Runnable{Clerk clerk;public Consumer(){}public Consumer(Clerk clerk){this.clerk = clerk;}public void run() {System.out.println("消费者开始消费产品");while(true){try {Thread.sleep((int)Math.random()*10000);} catch (InterruptedException e) {e.printStackTrace();}clerk.consumerProduct();}}}//店员class Clerk {static int product;public synchronized void consumerProduct(){if(product <= 0){try {wait();} catch (InterruptedException e) {e.printStackTrace();}}else {System.out.println(Thread.currentThread().getName()+":消费产品的第"+product);product--;notifyAll();}}public synchronized void addProduct(){if(product >= 20){try {wait();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}else {product++;System.out.println(Thread.currentThread().getName()+":生产产品"+product);notifyAll();}}}public class TestProduct {public static void main(String[] args) {Clerk clerk = new Clerk();Productor p1 = new Productor(clerk);Thread t1 = new Thread(p1);//创建了一个生产者Consumer c1 = new Consumer(clerk);Thread t2 = new Thread(c1);//创建了一个消费者t1.setName("生产者");t2.setName("消费者");t1.start();t2.start();}}


6.用Java编程一个会导致死锁的程序,你将怎么解决?

点击前往答案

http://blog.csdn.net/zy512638348/article/details/78243079


7.什么是原子操作?Java的原子操作是什么?

所谓原子操作,就是“不可中断的一个或一系列操作”,在确定一个操作是原子的情况下,多线程环境下,我们可以避免仅仅为保护这个操作在外围加上性能昂贵的锁(即不用加synchronized),甚至借助于原子操作,我们可以实现互斥锁。


8.Java关键字volatile与synchronized作用与区别

(1)volatile

 它所修饰的变量不保留拷贝,直接访问主内存中的。
   在Java内存模型中,有main memory,每个线程也有自己的memory (例如寄存器)。为了性能,一个线程会在自己的memory中保持要访问的变量的副本。这样就会出现同一个变 量在某个瞬间,在一个线程的memory中的值可能与另外一个线程memory中的值,或者main memory中的值不一致的情况。 一个变量声明为volatile,就意味着这个变量是随时会被其他线程修改的,因此不能将它cache在线程memory中。

(2)synchronized

 当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

     一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

     二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

     三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

     四、当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

     五、以上规则对其它对象锁同样适用.


区别:


  一、volatile是变量修饰符,而synchronized则作用于一段代码或方法。

 二、volatile只是在线程内存和“主”内存间同步某个变量的值;而synchronized通过锁定和解锁某个监视器同步所有变量的值。显然synchronized要比volatile消耗更多资源


9.为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

当你调用strat()方法时你将会创建新的线程,并且执行在run()方法里的代码,但如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码,仅仅只会执行run()方法里的代码。


10.什么是ThreadLocal类,怎么使用它?

ThreadLocal是一个线程级别的局部变量,并非“本地线程”。ThreadLocal为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本(译者注)。

下面是线程局部变量(ThreadLocal variables)的关键点:

一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。

ThreadLocal实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。

当多个线程访问ThreadLocal实例时,每个线程维护ThreadLocal提供的独立的变量副本。

常用的使用可在DAO模式中见到,当DAO类作为一个单例类时,数据库链接(connection)被每一个线程独立的维护,互不影响。(基于线程的单例)

ThreadLocal难于理解,下面这些引用连接有助于你更好的理解它。

《Good article on ThreadLocal on IBM DeveloperWorks 》、《理解ThreadLocal》、《Managing data : Good example》、《Refer Java API Docs》

11.Sleep(),suspend()和wait()之间有什么区别?

Thread.sleep()使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。如果另一线程调用了interrupt()方法,它将唤醒那个“睡眠的”线程。

注意:sleep()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep(),(这里的t是一个不同于当前线程的线程)。即便是执行t.sleep(),也是当前线程进入睡眠,而不是t线程。t.suspend()是过时的方法,使用suspend()导致线程进入停滞状态,该线程会一直持有对象的监视器,suspend()容易引起死锁问题。

object.wait()使当前线程出于“不可运行”状态,和sleep()不同的是wait是object的方法而不是thread。调用object.wait()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另一线程可以同步同一个对象锁来调用object.notify(),这样将唤醒原来等待中的线程,然后释放该锁。基本上wait()/notify()与sleep()/interrupt()类似,只是前者需要获取对象锁。

 =============-----------------------------------------------

wait
导致当前的线程等待,直到其他线程调用此对象的 notify方法或 notifyAll 方法。当前的线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行
sleep
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。该线程不丢失任何监视器的所属权。
wait与sleep

Wait是Object类的方法,范围是使该Object实例所处的线程。

Sleep()是Thread类专属的静态方法,针对一个特定的线程。

Wait方法使实体所处线程暂停执行,从而使对象进入等待状态,直到被notify方法通知或者wait的等待的时间到。Sleep方法使持有的线程暂停运行,从而使线程进入休眠状态,直到用interrupt方法来打断他的休眠或者sleep的休眠的时间到。Wait方法进入等待状态时会释放同步锁(如上例中的lock对象),而Sleep方法不会释放同步锁。所以,当一个线程无限Sleep时又没有任何人去interrupt它的时候,程序就产生大麻烦了notify是用来通知线程,但在notify之前线程是需要获得lock的。另个意思就是必须写在synchronized(lockobj) {...}之中。wait也是这个样子,一个线程需要释放某个lock,也是在其获得lock情况下才能够释放,所以wait也需要放在synchronized(lockobj) {...}之中。

Sleep与interrupt

interrupt是个很暴力的方法,打断一个线程的Sleep时并不需要获得该线程的lock。虽然暴力却也有暴力的用处。在一个线程无时限sleep的时候也只有interrupt能够唤醒他。在interrupt的时候会抛出InterruptedException,这个Exception是由Thread 类自动抛出的。因此Interrupt带有强烈的阻塞味道。

wait与interrupt

interrupt同样可以打断wait的等待,与打断sleep不同的是,被打断的wait的线程在重新获得lock之前是不会抛出InterruptedException。

resume和suspend已经被Java遗弃,因为他们天生会引起线程的死锁。

suspend是个贪婪的家伙,当一个线程在suspend的时候,线程会停下来,但却仍然持有在这之前获得的锁定。其他线程无法使用他锁定的任何资源,除非这个挂起的线程被resume之后,他才会继续运行。对于线程的同步,使用wait与notify要安全的多。

--------------------------------------

sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。

在调用sleep()方法的过程中,线程不会释放对象锁。

而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

获取对象锁进入运行状态。

 =============-----------------------------------------------


12.什么是死锁?

死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,除非一个用户进程被终止。就JavaAPI而言,线程死锁可能发生在一下情况。

●当两个线程相互调用Thread.join()

●当两个线程使用嵌套的同步块,一个线程占用了另外一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。

package com.thread.test2;/** *  * @author 周颖 * @date 2017年10月22日 下午9:35:08 * @Decription 死锁的问题,处理线程同步时,容易出现 *             本例为当两个线程使用嵌套的同步块,一个线程占用了另一个线程所必须的锁,互相等待时被阻塞而出现的死锁。 */public class TestDeadLock {static StringBuffer sb1 = new StringBuffer();static StringBuffer sb2 = new StringBuffer();//public static void main(String[] args) {new Thread() {public void run() {synchronized (sb1) {sb1.append("A");synchronized (sb2) {sb2.append("B");System.out.println(sb1);System.out.println(sb2);}}}}.start();new Thread() {public void run() {synchronized (sb2) {sb1.append("C");synchronized (sb1) {sb2.append("D");System.out.println(sb1);System.out.println(sb2);}}}}.start();}}


原创粉丝点击