线程
java有哪几种方式来创建线程执行任务
- 继承Thread类
- 实现Runnable接口,重写run()方法
- 实现Callable接口,重写call()方法
- 利用线程池来创建线程
以上几种方式底层都是基于Runnable
run()和start()区别
- start()用于启动线程,run()用于执行线程运行时的代码。
- run()可以重复调用,而start()只能调用一次。
- run()是在本线程里的,只是线程里的一个函数,而不是多线程,如果直接调用run(),其实就相当于调用了一个普通函数而已;调用start()方法,会启动一个线程并使线程进入就绪状态,当分配到时间片后就可以开始运行,start()会执行线程的相应准备工作,然后自动执行run()方法;
sleep和wait区别
- 所属类不同:对于sleep方法,我们首先要知道该方法属于Thread类中的,而wait方法则是属于Object类中的;
- 语法使用不同:wait方法必须配合synchronized一起使用,不然在运行时就会抛出IllegalMonitorStateException异常,sleep可以单独使用;
- 释放锁资源不同:sleep方法导致程序暂停执行指定的时间,让出cpu的使用权,线程不会释放对象锁,当指定时间到了又会自动恢复运行状态;而当调用wait方法的时候,线程会放弃对象锁,主动释放锁;
- 唤醒方式不同:sleep方法必须要传递一个时间参数,调用之后线程进入TIMED_WAITING有时限等待状态,时间到了之后,线程会自动唤醒;而wait方法如果不指定时间的话,调用之后线程会进入WAITING无时限等待状态,直到另一个线程调用了notify或者notifyAll之后,休眠的线程才会被唤醒;
线程有哪些中断方式?都有什么区别?
- 正常退出:当线程中run()或者call()按照逻辑流程正常的执行结束,线程自然也就停止了;
- stop暴力停止:直接在程序中使用Thread.stop(),线程会马上停止,但是可能导致数据不同步,或者资源得不到回收的问题,而且stop已经被标注为废弃方法;
- interrupt()异常法:只是改变中断状态,不会中断一个正在运行的线程,需要线程处于阻塞状态,或调用interrupt()后使线程处于阻塞状态才会抛出一个InterruptException异常
线程状态
- 新建状态(NEW):当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值;
- 就绪状态(RUNNABLE):当线程对象调用了start()方法之后,该线程就处于就绪状态,java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行;
- 运行状态(RUNNING):如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程就处于运行状态;
- 阻塞状态(BLOCKED):阻塞状态是指线程因为某种原因放弃了CPU的使用权,也就是让出了cpu timeslice,暂时停止运行,直到线程进入可运行状态(Runnable),才有机会再次获得CPU timeslice转到运行状态。
- 线程死亡(DEAD):run()或call()方法执行完成,线程正常结束;线程抛出一个未捕获的Exception或Error;调用stop()方法。死亡的线程不可再次复生。
阻塞状态
- 等待阻塞(o.wait->等待队列):运行状态(RUNNING)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中;
- 同步阻塞(lock->锁池):运行状态(RUNNING)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM就会把该线程放入锁池(lock pool)中;
- 其他阻塞(sleep/join):运行状态(RUNNING)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态(RUNNABLE);
怎么理解线程安全
在多个线程去访问某个方法或者对象的时候,不管通过任何的方式调用或者线程如何去交替执行,在程序中不做任何同步干预的情况下,这个方法或者对象的执行或者修改都能够按照预期的结果来反馈,那么这个类我们认为它是线程安全的。
线程安全的表现有原子性、有序性和可见性。
线程池
线程池的理解
线程池本质上是一种池化技术,而池化技术是一种资源复用的一个设计思想,线程池里边复用的是线程资源。线程池首先可以减少线程的频繁创建和销毁带来的性能开销,因为线程创建会涉及到cpu上下文切换、内存分配等工作,再者线程池本身会有参数来控制线程创建的数量,这样就可以避免无休止的创建线程带来的资源利用率过高的问题,起到了资源保护的作用。
线程池核心属性
- corePoolSize(核心线程数):线程池中会维护一个最小的线程数量,即使这些线程处于空闲状态,也不会被销毁,除非设置了allowCoreThreadTimeOut为true。
- workQueue(队列):用于保留任务并移交给工作线程的阻塞队列
- maximumPoolSize(最大线程数):线程池允许开启的最大线程数
- handler(拒绝策略):线程池对拒绝任务(无线程可用)的处理策略:AbortPolicy:直接抛出RejectedExecutionException异常,调用者可以捕获这个异常;CallerRunsPolicy:主线程直接执行该任务,执行完之后尝试添加下一个任务到线程池中,可以有效降低向线程池内添加任务的速度;DiscardOldestPolicy:抛弃旧的任务、暂不支持;会导致被丢弃的任务无法再次被执行;DiscardPolicy:抛弃当前任务、暂不支持;会导致被丢弃的任务无法再次被执行
- keepAliveTime(保持存活时间):如果线程池当前线程数超过corePoolSize,则多余的线程空闲时间超过keepAliveTime时会被终止
- unit(空闲线程存活时间单位):keepAliveTime的计量单位
- threadFactory(线程工厂):用于创建工作线程的工厂
线程池的执行流程
线程池为什么用阻塞队列
线程池创建线程需要获取mainlock这个全局锁,影响并发效率,阻塞队列可以很多好的缓冲。如果新任务的到达速率超过了线程池的处理速率,那么新到来的请求将累加起来,这样的话可能会耗尽资源。
线程池有哪几种转态?每种状态分别表示什么?
- RUNNING:表示线程正常运行,既能接收新任务,也会正常处理队列中的任务;
- SHUTDOWN:当调用线程池的shutdown()方法时,线程池就会进入SHUTDOWN状态,表示线程池处于正在关闭状态,此状态下线程池不会接收新任务,但是会继续把队列中的任务处理完;
- STOP:当调用线程池的shutdownnow() 方法时,线程池就会进入STOP状态,表示线程池处于正在停止转态,此转态下线程池不会接收新任务,也不会继续处理队列中的任务,并且正在运行的线程也会被中断;
- TIDYING:线程池中没有线程在运行后,线程池的状态就会自动变为TIDYING,并且会调用terminated(),该方法是空方法,留给程序员进行扩展;
- TERMINATED:terminated()方法执行完之后,线程池状态就会变为TERMINATED
submit()和execute()的区别
submit()和execute()都是用来执行线程池的,只不过使用execute()执行线程池不能有返回方法,而使用submit()可以使用Future接收线程池执行的返回值
Semaphore 信号量
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信息会被阻塞。Semaphore可以用来构键一些线程池、资源池之类的额,比如数据库连接池。
public static void main(String[] args) {
//创建一个计数阈值为5的信号量对象,只能5个线程同时访问
Semaphore semp = new Semaphore(5);
for (int i = 0; i < 10; i++) {
try {
//申请许可
System.out.println("申请许可");
semp.acquire();
System.out.println("申请成功");
//执行业务逻辑
testBiz(semp);
} catch (InterruptedException e) {
System.out.println("申请失败");
}
}
}
private static void testBiz(Semaphore semp) {
Runnable runnable = () -> {
try {
Thread.sleep(10000);
System.out.println("aaaa");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放许可
System.out.println("释放许可");
semp.release();
}
};
Thread thread = new Thread(runnable);
thread.start();
}