1、线程创建的方式一共有4种方式继承 Thread 类并重写 run 方法实现 Runnable 接口并实现 run 方法实现 Callable 接口并实现 call 方法配合 FutureTask 获取返回值使用线程池ExecutorService。实现Callable 接口的线程一般配合线程池使用因为可以使用返回值对多个线程进行调度。关于线程创建我觉得有必要自己手敲一下体会一下创建方式。以下是我的实测代码packagecom.company;importjava.util.concurrent.*;publicclassThreadTest{publicstaticvoidmain(String[]args){//继承Tread类Threadthread1newMyThread();Threadthread2newMyThread();thread1.start();thread2.start();//实现Runnable接口MyRunnablerunnable1newMyRunnable();MyRunnablerunnable2newMyRunnable();Threadrun1newThread(runnable1);Threadrun2newThread(runnable2);run1.start();run2.start();//实现Callable接口配合FutureTask才能使用MyCallablecallable1newMyCallable();MyCallablecallable2newMyCallable();FutureTaskBooleanfutureTask1newFutureTask(callable1);FutureTaskBooleanfutureTask2newFutureTask(callable2);Threadcall1newThread(futureTask1);Threadcall2newThread(futureTask2);call1.start();call2.start();//使用线程池ExecutorServiceservicenewThreadPoolExecutor(2,2,0L,TimeUnit.SECONDS,newLinkedBlockingQueue());//只支持Runnable和Thread它其实就是Runnable实现类service.execute(()-{System.out.println(线程池创建的线程Thread.currentThread().getName()启动了);});//可支持Callableservice.submit(()-{System.out.println(线程池创建的线程Thread.currentThread().getName()启动了);});service.shutdown();}staticclassMyThreadextendsThread{Overridepublicvoidrun(){System.out.println(继承Thread的Thread.currentThread().getName()启动了);}}staticclassMyRunnableimplementsRunnable{Overridepublicvoidrun(){System.out.println(实现Runnable的Thread.currentThread().getName()启动了);}}staticclassMyCallableimplementsCallableBoolean{OverridepublicBooleancall(){System.out.println(实现Callable的Thread.currentThread().getName()启动了);returntrue;}}}2、线程池的核心参数线程池的核心参数有七个corePoolSize 核心线程数maximumPoolSize 最大线程数keepAliveTime 空闲线程存活时间unit 时间单位workQueue 阻塞队列threadFactory 线程工厂handler 拒绝策略。任务执行流程先创建核心线程核心线程满了就放入队列队列满了就创建非核心线程达到最大线程数后执行拒绝策略。为什么任务到来时核心线程已满就放入队列而不是开非核心线程我的理解是设计线程池的参数的时候队列长度一定程度上可以代表该线程池处理的那一类任务对延迟时间的接受程度。队列没满就代表了仅靠核心线程完全足够处理完所有请求只不过部分任务需要等待。而如果选择立马创建非核心线程那么大多数情况下非核心线程创建/销毁的资源成本是远大于任务排队等待的时间开销的。同时该任务当前核心线程完全有能力处理开非核心线程属于资源浪费。3、线程池的拒绝策略有四种默认拒绝策略AbortPolicy 直接抛异常这是默认策略CallerRunsPolicy 由调用者线程执行任务DiscardPolicy 直接丢弃任务DiscardOldestPolicy 丢弃队列中最老的任务。各策略使用场景AbortPolicy默认策略最安全系统必须感知过载需要快速失败、触发告警、自动扩容示例支付回调处理、库存扣减。核心原则宁可让系统暂时不可用也不能静默丢任务。CallerRunsPolicy适用场景不希望任务丢失但也不希望抛异常示例用户行为埋点、操作日志写入。丢几条没关系但能跑更好。核心原则用调用者的时间换系统稳定提供一种简单的反馈控制。DiscardPolicy高风险慎重使用适用场景极其低频、非关键任务对数据一致性要求极低丢了无所谓示例调试日志、统计计数允许误差。生产环境慎用。任务被丢弃时没有任何提示排查问题极困难。绝大多数场景下不推荐。DiscardOldestSetPolicy特殊场景适用场景新数据覆盖老数据旧值无意义示例股票价格推送、实时位置更新。核心原则新请求比旧请求更重要。适用于数据快速过期、只关心最新值的场景。要确保旧任务丢弃不会造成业务问题。4、synchronized 和 ReentrantLock 的区别两者都是可重入锁也就是线程可以再次获取自己的内部锁。synchronized 是 JVM 层面的非公平锁自动释放不可中断ReentrantLock 是 API 层面的锁需要手动 unlock可以中断可以设置公平锁或非公平锁并且支持多个 Condition来实现分组唤醒和选择性通知。在 JDK 1.8 中 synchronized 做了锁升级优化性能已经不输 ReentrantLock。4.1、synchronized 锁升级过程无锁 → 偏向锁 → 轻量级锁 → 重量级锁偏向锁第一个线程直接拿锁对象头存线程ID。无竞争时几乎零开销。轻量级锁当有另一个线程竞争时升级为轻量级锁通过自旋CAS尝试获取锁不阻塞线程。重量级锁自旋超过一定次数或竞争激烈升级为重量级锁线程阻塞挂起等待。目的尽量用低开销的锁应对低竞争场景只有在真需要时才用重量级锁。4.2、ReentrantLock 怎么获取锁它是 API 锁靠 AQS 实现。无竞争时CAS 直接拿到锁可以理解为“轻量级行为”有竞争时线程进入 AQS 队列可能被阻塞挂起可以理解为“重量级行为”5、volatile 的作用volatile 保证变量的可见性和禁止指令重排序但不保证原子性。可见性写 volatile 会立即刷新到主内存读 volatile 从主内存读。重排序修饰的变量前后插入内存屏障防止指令重排。原子性volatile不能保证i这类复合操作的原子性因为读、改、写三步不是一起完成的。所以 volatile 适合做状态标记如开关标志不适合做计数器。如果需要原子操作用 AtomicInteger 或 synchronized。6、死锁的四个必要条件互斥条件资源一次只能被一个线程占用持有并等待线程持有资源的同时还在等待其他资源不可剥夺资源不能被强行抢占循环等待多个线程形成等待环路。要预防死锁可以破坏其中一个条件。破坏持有并等待规定只能一次性获取所有资源破坏不可剥夺长时间无法获取锁就释放手上的锁。破坏循环等待规定所有线程按相同顺序申请资源。7. 线程的六种状态NEW线程对象刚创建还没调用start()。RUNNABLE正在运行或者正在等待CPU调度。BLOCKED被阻塞在等synchronized锁。WAITING无限期等待等另一个线程显式唤醒。比如Object.wait()没超时参数Thread.join()LockSupport.park()。TIMED_WAITING有时限的等待到了时间自动醒。比如Thread.sleep()Object.wait(1000)thread.join(1000)。TERMINATED线程运行结束退出了。这里用一个JavaGuide提供的图来表示线程的生命周期最合适不过