线程的概念:
线程是操作系统能进行的运算调度得到最小单位,被包含在进程中,是进程的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程可以并发多个线程,每个线程执行不同额任务
线程与进程的区别:
进程:系统中运行的一个应用程序,程序一旦运行就是进程,资源分配的最小单位
线程:系统分配处理器时间资源的基本单位,程序执行的最小单位
线程的状态:
NEW :创建状态
RUNNABLE: 运行状态
BLOCKED:阻塞状态
WAITING:等待状态
TIMED_WAITING:调用sleep()、join()、wait()方法可能导致线程处于等待状态
TERMINTED:执行完毕,退出
notify()和wait()的作用:
notify()唤醒一个正在等待这个对象的线程监控,如果有任何线程正在等待这个对象,那么它们中的一个被选择被唤醒,选择是任意的,需要在同步代码块或同步方法中调用,即调用前线程必须获得该对象的对象级别锁
wait()导致当前线程等待,直到另一个线程调用notify()或notifyAll()方法,属于Object类的方法,需要在同步代码块或同步方法中调用
wait和sleep的区别:
- sleep不释放同步锁,wait释放同步锁
- sleep可以用时间指定来使它自动醒来,时间不到需要使用interreput()强行打断;wait可以使用notify或者notifyAll直接唤起
- sleep属于Thread类,wait属于Object类
- wait释放同步锁,使其他线程可以使用同步方法或者同步带代码块
sleep和yield的异同:
相同:
sleep和yield都会释放CPU
不同:
sleep使当前线程进入停滞状态,执行sleep的线程在指定时间内不会执行,yield只是使当前线程重新回到可执行状态,执行yield有可能进入到可执行状态后马上又被执行,
死锁的概念:
死锁:指两个或多个进程或者线程在执行过程中因为争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,此时称系统处于死锁状态
死锁产生的四个必要条件(缺一不可)
- 互斥条件:线程对资源的访问是排他的,当该线程释放资源后下一个线程才可以进行占用
- 请求和保持:自己拿着不放手又等待新的资源到手,导致其他等待资源的线程一直等待
- 不可剥夺:在没有使用完资源时,其他线程不能进行剥夺
- 循环等待:一直等待对方线程释放资源
并发与并行的区别:
并发:指在某个时间段内,多个任务交替执行任务,当多个线程在操作时,把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程运行时,其他线程处于挂起状态
并行:同一时刻同时处理多任务的能力,当多个线程在操作时,CPU同时处理这些线程请求的能力
主要区别在于CPU是否能同时处理所有任务,并发不能,并行能
线程安全的要素:
原子性:atomic包、cas算法、synchronized、lock
可见性:synchronized、volatile
有序性:happens-before规则
实现线程安全:
- 互斥同步:synchronized、lock
- 非阻塞同步:cas
- 无需同步:如果不涉及共享资源,就不需要同步保证正确性
保证线程安全的机制:
synchronized关键字、lock锁、cas、volatile、Threadlocl等
创建线程的方法:
- 继承thread类
- 实现runable接口 (推荐)
- 通过callable和future创建线程
线程池创建:
线程池:线程存放的地方,目的是为了较少的系统开销
特点:
- 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁的消耗
- 提高响应速度,不需要等待线程的创建,直接获取空闲线程执行
- 提高线程的可管理性,有效的管理线程的创建,提高资源利用率及系统稳定性
创建方式:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收则新建线程
- newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
- newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行
- newSingleThreadExecutor创建一个单线程化的线程池,只会存在唯一的工作线程来执行任务,保证所有任务的顺序(FIFO\LIFO\优先级)执行
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:线程池的核心线程数,即使没有任务,也会存在线程等候任务执行
- maximumPoolSize:最大线程数,不管提交多少任务,线程池里最多工作线程数
- keepAliveTime:线程的存活时间,当线程池的线程数大于核心线程数是,如果过了线程存活时间还没有任务执行,则线程退出
- Unit:指定线程存活时间的单位 eq:
TimeUnit.SECONDS
- BlockingQueue:一个阻塞队列,提交的任务将会被放到这个队列里
- threadFactory:线程工厂,用来创建线程
- handler:拒绝策略,当线程池里的线程被耗尽且队列已满时调用
BlockingQueue:
阻塞队列,存在先进先出(FIFO)和先进后出(LIFO)两种
常见的有ArrayBlockingQueue\LinkedBlockingQueue
队列一端进入,一端输出,当队列满时阻塞。
核心方法:
- put 放入数据
- take 获取数据
ArrayBlockingQueue:
基于数组实现,内部维护一个定长的数组,以便缓存队列中的数据对象,除了定长的数据,内部还保存着2两个整形的变量,分别标识着队列的头部和尾部在数组中的位置。
LinkedBlockingQueue:
基于链表的阻塞队列,内部也维护了一个数据缓冲队列,如果没有指定其容量大小,他会默认一个无限大小的容量,这样一旦生产者的速度大于消费者速度,也许没有等到队列满阻塞系统内存就可能耗尽
区别:
- ArrayBlockingQueue的初始化必须传入队列大小。LinkedBlockingQueue则可以不传入
- ArrayBlockingQueue用一把锁控制并发(生产者消费者进出都是一把锁),LinkedBlockingQueue两把锁控制并发,锁的细粒度更细(生产者进入一把锁,消费者消费一把锁)
- ArrayBlockingQueue采用数组的方式存取,LinkedBlockingQueue用链表方式存取
handler拒绝策略:
JAVA提供四种丢弃处理方法,我们也可以实现接口RejectedExecutionHandler实现自己的策略
- AbortPolicy:不处理,直接抛出异常
- CallerRunsPolicy:只用调用者所在线程来运行任务,即提交任务的线程
- DiscardOldestPolicy:LRU策略,丢弃队列里最近最久不使用的一个任务,并执行当前任务
- DiscardPolicy:不处理,丢弃掉,不抛出异常
线程池的五种状态:
- running:在这个状态的线程池能判断接受新提交的任务,并且也能处理阻塞队列中的任务
- shutdown:处于关闭的状态,该线程池不能接受新提交的任务,但是可以处理阻塞队列中已经保存的任务,在线程处于runing状态,调用shutdown()方法能切换为该状态
- stop:处于该状态的线程池,既不能接受新提交的任务也不能处理阻塞队列中的任务,并且能中断现在线程中的任务,调用shutdownNow()方法可以使线程池变为该状态
- tidying:在shutdown状态下阻塞队列为空,且线程中的工作线程数量为0 就会进入该状态,当在stop状态下使,只要线程中的工作线程数量为0 就会进入该状态
- terminated:在tidying状态下调用terminated()方法就会进入该状态,可以认为该状态是最终的终止状态
shutdownNow和shutdown的区别:
- shutdown会把线程池状态改为shutdown,而shutdownnow把当前线程池状态改为stop
- shutdown只会中断所有空闲的线程,shutdownNow会中断所有的线程
- shutdown返回方法为空,会将当前队列中额所有任务执行完毕,shutdownNow把任务队列中的所有任务都取出来返回
线程间通信的几种方式:
实现线程间通信的模型有两种:共享内存或者消息传递
题目:有A\B两个线程,A线程向一个集合循环添加字符串“123”,循环10次,当循环到第5次时,希望B线程收到A线程通知,然后开始B的业务
- 使用volatile关键字
public class ThreadMsgTest {
//定义一个 volatile 属性的变量
private static volatile boolean flag = false;
private static void notifyThreadWithVolatile(){
Thread a = new Thread("线程A"){
@SneakyThrows
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 5){
flag = true;
Thread.sleep(500);
break;
}
System.out.println(Thread.currentThread().getName() + "=====" + i);
}
}
};
Thread b = new Thread("线程B"){
@SneakyThrows
@Override
public void run() {
while (true){
while (flag){
System.out.println(Thread.currentThread().getName()+"收到通知");
System.out.println("开始处理业务");
Thread.sleep(1000);
}
}
}
};
a.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
b.start();
}
public static void main(String[] args) {
notifyThreadWithVolatile();
}
}