天天看点

(一)线程相关面试问题详解

(一)Runnable接口和Callable接口的区别?

1.相同点:①都是一种类型的接口(废话)②都可应用于Executors

2.不同点:①Callable接口的call()方法,Runnable实现run()方法。

                 ②call()可以有返回值(),run()方法没有返回值。

                 ③call()方法可以抛出checked exception异常,run()方法不能抛出异常【只能内部消化】。

                 ④Runnable接口JDK1.1就有了,Callble在JDK1.5才有。  

(二)wait()和sleep()方法的区别?

 1.区别:

①:sleep()属于Thread类的静态方法。 wait()属于Object类的方法。

②【锁】:sleep()睡着了,-->仍保持对象锁。【时间到了,自动唤醒过来】

                   wait()等待时,会释放对象锁。【没设置时间-->就需要其他线程调用notify/notifyAll唤醒】

Thread.sleep(0)的作用是:“触发 操作系统 立刻 重新进行一次CPU竞争”。

共同打断:通过interrupt()方法打断线程暂停状态-->会立刻抛出InterrruptedException(不建议这么用)。

③:使用范围:wait,notify 和 notifyAll只能在同步控制块儿里面使用,而sleep可以在任何地方使用。

synchronized(x){ 

      x.notify() 

     //或者wait() 

   }

④:当两者都定义在同步中时: 线程执行到sleep()不会释放锁。线程执行到wait(),会释放锁。

(三).synchronized、Lock、ReentrantLock、ReadWriteLock。

(1.0)synchronized:修饰方法或代码块儿【常用的同步机制】-->尽量只锁 少量 共享的数据块儿。

(2.0)Lock【是个接口】:常用的实现类 有ReentrantLock、ReadWriteLock(ReentrantReadWriteLock)。

    1.0:ReentrantLock【可序列化;分为公平锁 和 不公平锁(默认)】:是一个可重入的互斥锁Lock。

       【常用方法,以及应用场景:①tryLock(),②tryLock(5,TimeUnit.SECONDS),③lock(),④lockInterruptibly()-->详情请见博客 ReentrantLock常用方法-场景 详解】

        它的lock机制有2种-->忽略中断锁 和 响应中断锁

       (①)公平锁:如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。【类似于 会排队】。

       (②)非公平锁:只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中【类似于 不排队】             (③)非公平锁性能高于公平锁性能的原因:

         在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。非公平锁 会充分利用这段时间。并提高吞吐量。

    eg:假设线程A持有一个锁,并且线程B请求这个锁。由于锁被A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此B会再次尝试获取这个锁。与此同时,如果线程C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样就是一种双赢的局面:B获得锁的时刻并没有推迟,C更早的获得了锁,并且吞吐量也提高了。

      【注意】当持有锁的时间相对较长 或者 请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,插队带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)可能不会出现。

(四)什么是ThreadLocal?【实现 线程局部变量】

  (1.0)是JDK 1.2版本推出的 解决多线程并发的一种新思路。---> JDK 1.5 ThreadLocal<>;

   当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

  (2.0)ThreadLoacl类的常用方法:

    ①:protested T initialValue():-->返回此线程 局部变量 的当前线程的 “初始值”。 

    ②:void set(T value)-->将此线程局部变量的当前线程副本中的值设置为指定值。  

    ③:T get():-->返回此线程局部变量的当前线程的值。

    ④:void remove()--->移除此线程 局部变量当前线程的值。

   (3.0)ThreadLocal中的变量副本如何实现的?

    ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键 为线程对象,而值 对应线程的变量副本。

   (4.0)代码操作:

public class TestThreadLocal {
    //通过匿名内部类 覆盖ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Integer> seqNum=new ThreadLocal<Integer>(){
        public  Integer initialValue(){
            return 0;
        }
    };
   //获取下一个序列值
    public int getNextNum(){
        seqNum.set(seqNum.get()+1);
        return seqNum.get();
    }
    public static void main(String[] args) {
        TestThreadLocal ttl=new TestThreadLocal();
        //开启三个分线程
       TestClient t1=new TestClient(ttl);
       TestClient t2=new TestClient(ttl);
       TestClient t3=new TestClient(ttl);
       t1.start();
       t2.start();
       t3.start();
    }

    private static class TestClient extends Thread{
        private TestThreadLocal testThreadLocal;
        public TestClient(TestThreadLocal t){
            this.testThreadLocal=t;
        }
        public void run(){
            for (int i=0;i<3;++i){
                //每个线程打印三次
                System.out.println("Thread["+Thread.currentThread().getName()+"]----》testThreadLocal" +
                        "["+testThreadLocal.getNextNum()+"]");
            }
        }
    }
}      

   结果显示:

(一)线程相关面试问题详解

    结果分析:各自都打印三次值一 一对应变化;线程变量  之间  互不影响,相互独立。

(四)创建 线程池 的4种方式。

       (1.0) java通过Executors提供了四种线程池:

                   ①:newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

                   ②:newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

                   ③:newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

                   ④: newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务 按照指定顺序(FIFO, LIFO, 优先级)执行。

          (2.0)题外话

                       newFixedThreadPool、newSingleThreadExecutor、newCachedThreadPool,这三者都直接或间接调用了ThreadPoolExecutor,为什么它们三者没有直接是其子类,而是通过Executors来实例化呢?这是所采用的静态工厂方法,在java.util.Connections接口中同样也是采用的静态工厂方法来创建相关的类。这样有很多好处,静态工厂方法是用来产生对象的,产生什么对象没关系,只要返回原返回类型或原返回类型的子类型都可以,降低API数目和使用难度,在《Effective Java》中的第1条就是静态工厂方法。

(五)ThreadPoolExecutor的内部工作原理。

    (1.0)JDK1.7  构造函数 

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}      

            ① corePoolSize:核心线程池的线程数量

           ②maximumPoolSize:最大的线程池线程数量

           ③keepAliveTime:线程活动保持时间,线程池的工作线程空闲后,保持存活的时间。

           ④unit:线程活动保持时间的单位。

           ⑤ workQueue:指定任务队列所使用的阻塞队列

          (2.0 )线程池的执行原理图:

(一)线程相关面试问题详解

继续阅读