多线程
你好!
线程和多线程是什么
进程:进程是系统资源分配的独立实体,进程间的资源是独立且不能相互访问的,如果需要访问则需要使用进程间通信,如管道,文件,套接字等。一个进程可拥有多个线程。进程例子:JVM
线程:操作系统能够进行运算调度的最小单位。是进程中的一个独立控制单元。一个进程中至少有一个线程。
多线程:一个进程中有多个线程在执行任务。
线程生命周期
- 新建:从新建一个线程对象到程序start()这个线程之间的状态,都是新建状态;
- 就绪:线程对象调用start()方法后,就处于就绪状态,等到JVM里面的线程调度器的调度;
- 运行:就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞以及死亡状态。
- 等待/阻塞/睡眠:在一个线程执行了sleep(睡眠)、suspend(挂起)等方法后会失去所占有的资源,从而进入阻塞状态,在睡眠结束后可重新进入就绪状态。
- 终止:run()方法完成后或发生其他终止条件时就会切换到终止状态。
创建线程的方法
- 继承Thread类:
步骤: 1.定义类继承Thread;
2.复写Thread类中的run方法;
目的:将自定义代码存储在run方法,让线程运行
3.调用线程的start方法:
该方法有两步:启动线程,调用run方法。 - 实现Runnable接口:接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run的无参方法。
实现步骤: 1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法,将线程要运行的代码放在该run方法中。
3.通过Thread类建立线程对象。
4将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。自定义的run方法
线程池的创建和参数详解
-
Executors类创建线程池:
Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待;
Executors.newCachedThreadPool:创建一个可缓存的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程;
Executors.newSingleThreadExecutor:创建单个线程数量的线程池,它可以保证先进先出的执行顺序;
Executors.newSingleThreadScheduleExecutor:创建一个单线程的可以执行延迟任务的线程池;
Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定)(jdk1.8添加) -
ThreadPoolExecutor类原始创建线程池的方式以及7个可配置参数:
参数1:corePoolSize
核心线程数,线程池中始终存活的线程数。
参数2:maximumPoolSize
最大线程数,线程池中允许的最大线程数,当线程池的任务队列满了之后可以创建的最大线程数。
参数3:keepAliveTime
最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
参数4:unit
单位,时参数3 keepAliveTime的单位,用于设定线程的存活时间,该时间单位有以下7种单位:- TimeUnit.DAYS:天
- TimeUnit.HOURS:小时
- TimeUnit.MINUTES:分
- TimeUnit.SECONDS:秒
- TimeUnit.MILLISECONDS:毫秒
- TimeUnit.MICROSECONDS:微妙
- TimeUnit.NANOSECONDS:纳秒
参数5:workQueue
一个阻塞队列,用于存储线程池等待执行的任务,均为线程安全,它包含以下7种类型:- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程,不保持线程。
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
比较经常使用到的是
LinkedBlockingQueue
和SynchronousQueue
,线程池的排队策略与BlockingQueue
有关。参数6:threadFactory
线程工厂,主要用来创建线程的,默认为正常优先级、非守护线程。
参数7:handler
拒绝策略,拒绝处理任务时的策略(在队列满了、以及线程池里面的线程数达到最大线程数时,就会触发拒绝策略用于拒绝线程),系统提供了4种拒绝策略:- AbortPolicy:拒绝并抛出异常。
- CallerRunsPolicy:使用当前调用的线程来执行此任务。
- DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
- DiscardPolicy:忽略并抛弃当前任务。
默认策略为
AbortPolicy
。 -
线程的执行流程
ThreadPoolExecutor关键接电脑的执行流程如下:- 当线程数小于核心线程数时,创建线程。
- 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
- 当线程数大于等于核心线程数,且任务队列已满:若线程数小于最大线程数,创建线程;若线程数等于最大线程数,触发拒绝策略,拒绝任务或抛出异常。
多线程常见面试题
- Synchronized和Lock的区别
Synchronized关键字主要解决多线程通信时候共享数据同步问题。
Synchronized底层使用指令
Synchronized是关键字,内置语言实现,Lock是接口。
Synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。
ThreadLocal与Synchronized区别ThreadLocal和Synchonized都用于解决多线程并发访问,他们两者的区别:
synchronized是利用锁的机制,使变量或代码块在某一时刻只能被一个线程访问,而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享,而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
总结:Synchronized是为了让多线程进行数据共享,而ThreadLocal为了让多线程进行数据隔离。
- 同步方法和同步块,哪个更好?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。同步块更符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁。