(一)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 )线程池的执行原理图: