|
一.多线程
1.实现多线程的方法:
.继承Thread类
.实现Runnable接口-重写run方法
.实现Callable接口-重写call方法
2.callable返回值使用什么接收:
.Future - 接口
.FutureTask - 类,可以new出来,传进去的callable的call方法只会执行一次。
3.简单线程用到的方法:

①sleep(long millis): 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
②join():指等待t线程终止。
③yield():暂停当前正在执行的线程对象,并执行其他线程。
④setPriority(): 更改线程的优先级。
.MIN_PRIORITY = 1
.NORM_PRIORITY = 5
.MAX_PRIORITY = 10
⑤interrupt():不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时) 能抛出异常,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!
⑥wait() :让当前线程进入等待状态,同时,wait()也会让当前线程释放他所持有的锁。直到其他线程调用此对象的notify或者notifyAll方法,当前线程被唤醒。wait(long timeout):指定时间量。
⑦notify():唤醒单个线程。
⑧notifyall():唤醒所有线程。
4.线程的生命周期
.新建,就绪,运行,阻塞,死亡。
.新建:new 就绪:start 运行:run 阻塞:sleep,阻赛io,等待通知notify
5.并发特征
.
.
.
.
二.线程池
1.使用工具类创建线程池-Executors
//创建固定大小的线程池
//核心线程数:参数
//最大线程数:参数
//线程存活时间:0L(TimeUnit.MILLISECONDS毫秒)
//队列选择:LinkedBlockingQueue
ExecutorService pool = Executors.newFixedThreadPool(5);
//创建缓存线程池
//核心线程数:0
//最大线程数:2147483647(Integer.MAX_VALUE)
//线程存活时间:60L(TimeUnit.SECONDS秒)
//队列选择:SynchronousQueue
ExecutorService pool = Executors.newCachedThreadPool();
//创建单个线程的线程池
//核心线程数:1
//最大线程数:1
//线程存活时间:0L(TimeUnit.MILLISECONDS毫秒)
//队列选择:LinkedBlockingQueue
ExecutorService pool = Executors.newSingleThreadExecutor();
//创建延迟或定时线程的线程池
//核心线程数:参数
//最大线程数:2147483647(Integer.MAX_VALUE)
//线程存活时间:0L(TimeUnit.NANOSECONDS纳秒)
//队列选择:DelayedWorkQueue
ExecutorService pool = Executors.newScheduledThreadPool(5);
//创建单个延迟或定时线程的线程池
//核心线程数:1
//最大线程数:2147483647(Integer.MAX_VALUE)
//线程存活时间:0L(TimeUnit.NANOSECONDS纳秒)
//队列选择:DelayedWorkQueue
ExecutorService pool = Executors.newSingleThreadScheduledExecutor();
//JDK1.8
//创建线程很耗时的线程池
//核心线程数:10
//最大线程数:0x7FFFFFFF = 2147483647(Integer.MAX_VALUE)最大整型数int
//线程存活时间:未发现
//队列选择:未发现
ExecutorService pool = Executors.newWorkStealingPool(10);2.使用ThreadPoolExecutor创建线程池--推荐
//创建单个延迟或定时线程的线程池
//核心线程数:2
//最大线程数:5
//线程存活时间:60L(TimeUnit.SECONDS秒)
//队列选择:ArrayBlockingQueue
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,5,60L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));ThreadPoolExecutor参数说明
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
.corePoolSize
核心线程数。在创建线程池之后,默认情况下线程池中并没有任何的线程,而是等待任务到来才创建线程去执行任务,当线程池中的线程数目达到 corePoolSize后,新来的任务将会被添加到缓存队列中,也就是那个workQueue,除非调用 ThreadPoolExecutor#prestartAllCoreThreads() 方法或者是 ThreadPoolExecutor # prestartCoreThread() 方法(从这两个方法的名字就可以看出是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或一个线程)。
PS:很多人不知道这个数该填多少合适,其实也不必特别纠结,根据实际情况填写就好,实在不知道,就按照阿里工程师的写法取下列值就好了:
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
.maximumPoolSize
线程池中的最大线程数。表示线程池中最多可以创建多少个线程,很多人以为它的作用是这样的:”当线程池中的任务数超过 corePoolSize 后,线程池会继续创建线程,直到线程池中的线程数小于maximumPoolSize“,其实这种理解是完全错误的。它真正的作用是:当线程池中的线程数等于 corePoolSize 并且 workQueue 已满,这时就要看当前线程数是否大于 maximumPoolSize,如果小于maximumPoolSize 定义的值,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来拒绝这个任务。另外超过 corePoolSize的线程被称做&#34;Idle Thread&#34;, 这部分线程会有一个最大空闲存活时间(keepAliveTime),如果超过这个空闲存活时间还没有任务被分配,则会将这部分线程进行回收。
.keepAliveTime
控制&#34;idle Thread&#34;的空闲存活时间。这个idle Thread就是上面提到的超过 corePoolSize 后新创建的那些线程,默认情况下,只有当线程池中的线程数大于corePoolSize,且这些&#34;idle Thread&#34;并没有被分配任务时,这个参数才会起作用。另外,如果调用了 ThreadPoolExecutor#allowCoreThreadTimeOut(boolean) 的方法,在线程池中的线程数不大于corePoolSize,且这些core Thread 也没有被分配任务时,keepAliveTime 参数也会起作用。
.unit
参数keepAliveTime的时间单位,共7种取值,在TimeUtil中定义:
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
.workQueue
阻塞队列。如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到该队列当中,注意只要超过了 corePoolSize 就会把任务添加到该缓存队列,添加可能成功也可能不成功,如果成功的话就会等待空闲线程去执行该任务,若添加失败(一般是队列已满),就会根据当前线程池的状态决定如何处理该任务(若线程数 < maximumPoolSize 则新建线程;若线程数 >= maximumPoolSize,则会根据拒绝策略做具体处理)。
常用的阻塞队列有:
1)ArrayBlockingQueue //基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue //基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue //这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。
.threadFactory
线程工厂。用来为线程池创建线程,当我们不指定线程工厂时,线程池内部会调用Executors.defaultThreadFactory()创建默认的线程工厂,其后续创建的线程优先级都是Thread.NORM_PRIORITY。如果我们指定线程工厂,我们可以对产生的线程进行一定的操作。
.handler
拒绝执行策略。当线程池的缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:
ThreadPoolExecutor.AbortPolicy: // 丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy: // 也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy: // 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy: // 由调用线程处理该任务
3.Executor 和 ExecutorService 的区别
Executor | ExecutorService | Executor 是 java 线程池的核心接口,用来并发执行提交的任务 | ExecutorService 继随 Executor,也是接口,扩展了方法,提供了异步执行和关闭线程池的方法 | 只有 execute(Runnable command) 提交任务 | 增加了 submit(Callable task) 、 submit(Runnable task, T result)方法提交任务 | execute() 方法 无返回值 | 两个 submit() 返回 Future 对象,可用来获取任务执行结果 | 不能取消任务 | 可以通过 Future.cancel() 取消 pending 中的任务 | 没有提供和关闭线程有关的方法 | 提供的关闭线程的 shutdown() 方法 | 三.锁
1. synchronized关键字和Lock的区别
①.synchronized关键字是在JVM层面上,Lock是一个接口,ReentranLock是它的实现。
②.synchronized要么等到拿到该锁的线程执行完毕释放掉该锁,要么线程报错释放掉锁。Lock锁苏姚手动上锁和释放锁,并且必须释放掉锁,不然会造成死锁。
③.synchronized无法判断锁的状态,Lock可以判断锁的状态。
④.synchronized只能等待拿到锁的线程释放掉锁。Lock可以尝试获取到锁。
⑤.synchronized和Lock都是可冲入锁,synchronized是非公平锁,Lock默认是非公平锁,可以修改为公平锁。
⑥.synchronized适用少量代码,Lock适用于大量代码。
2.synchronized详解
① .在JDK1.6之前,synchronized的实现直接调用ObjectMonitor的enter和exit,这种锁被称之为重量级锁。从JDK6开始,HotSpot虚拟机开发团队对Java中的锁进行优化,如增加了适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁等优化策略,提升了synchronized的性能。
② .偏向锁:在无竞争的情况下,只是在Mark Word里存储当前线程指针,CAS操作都不做。
.轻量级锁:在没有多线程竞争时,相对重量级锁,减少操作系统互斥量带来的性能消耗。但是,如果存在锁竞争,除了互斥量本身开销,还额外有CAS操作的开销。
.自旋锁:减少不必要的CPU上下文切换。在轻量级锁升级为重量级锁时,就使用了自旋加锁的方式
.锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
.锁消除:虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除。
3.synchronized的使用
①.修饰实例方法:作用于当前实例加锁
②.修饰静态方法:作用于当前类对象加锁
③.修饰代码块:指定加锁对象,对给定对象加锁
4.synchronized锁情况
修饰普通方法或静态代码块(this):锁当前对象
修饰static方法或静态代码块(类.class):锁类的所有对象
每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码 |
|