最近在准备实习面试的事情,因此每天都在复习一些基础知识,昨天面试的时候关于多线程的问题回答得不是很好,因此今天决定把多线程基础的知识给复习一下,顺便整理整理。
线程与进程
进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序就是一个进程从创建、运行和消亡的过程。
在Java中,当我们启动了main函数时其实就是启动了一个JVM的进程,而main函数所在的线程就是这个进程中的一个线程,也称为主线程。
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。
由于线程可以共享一个公共资源,因此在各个线程间做切换工作时,负担要比进程小得多,也正因如此,线程被称为轻量级进程。
线程是进程划分的更小的执行单位,线程和进程最大的不同在于基本上各个进程间是独立的,而各线程则不一定,因为同一进程中的线程极有可能会互相影响。线程执行开销小,但不利于资源的管理和保护,而进程正相反。
守护线程和非守护线程
非守护线程包括常规的用户线程或用于处理GUI事件的事件调度线程,JVM在它所有非守护线程已经离开后会自动离开。守护线程是用来服务非守护线程的,比如说GC线程。
线程安全
线程安全是一个实例或一个方法在多线程环境下使用而不会出现问题,并且在不必考虑在运行时环境下的调度和交替运行,也不需要进行额外的同步,就可以获得正确的结果。
线程不安全的根本原因应该是多个线程对共享资源的访问与更改造成程序结果的错误。所以线程不安全的解决策略就是保护共享资源。
线程的状态
在Java中,线程的状态主要有五种:
新建(New):创建后尚未启动。
运行(Runnable):Runnable包括了操作系统中线程状态的Running和Ready,也就是处于这状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间。
阻塞(Blocked):线程被阻塞了,“阻塞”与“等待”的区别是:“阻塞”在等待着获取到一个排他锁,这个事件将在另一个线程放弃这个锁的时候发生;而“等待”则是在等待一段时间,或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
无限期等待(Waiting):处于这种状态的线程不会被分配CPU执行时间,他们要等待被其他线程显式地唤醒。以下几种情况会让线程陷入无限期的等待状态:没有设置Timeout参数的Object.wait()和Thread.join()方法、LockSupport的park()方法。
限期等待(Timed-Waiting):处于这种状态也不会被分配CPU执行时间,不过无须等待被其他线程显式地唤醒,在一定时间后他们会由系统自动唤醒。以下方法会让线程进入限期等待状态:Thread.sleep()方法、设置了Timeout参数的Object.wait()和Thread.join()方法、LockSupport.parkNanos()方法和LockSupport.parkUntil()方法。
结束(Terminated):已终止线程的线程状态,线程已经结束执行。
创建线程的方式
1.继承Thread类并重写run方法,创建后的子类调用start()方法即可执行线程方法。通过继承Thread实现的线程类,多个线程间无法共享线程类的实例变量(由于不是同一个Thread对象)
2.通过实现Runnable接口来实现,定义一个类实现该接口并且重写run方法,然后创建该对象来作为Thread类的对象的参数,创建出来的Thread对象才是线程对象。通过此方法,各线程类之间是互相共享资源的。
3.使用Callable类和Future创建线程,继承Thread类和Runnable接口可以看出,这两种方法都不能有返回值,且不能声明抛出异常。而Callable接口则实现了这两点,他相当于升级版的Runnable接口,其中的call方法作为线程的执行体,同时允许有返回值。但是Callable对象不能直接作为Thread类的target,所以引入Future接口,此接口可以接收call()的返回值。并且Callable是函数式接口,可以使用Lambda表达式。
什么是线程的上下文切换
多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。
并行与并发
并行(Parallel):同一时刻,多个任务同时进行
并发(Concurrency):同一时间段内,多个任务都在执行(但是同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行)
JVM下控制并发
在《深入Java虚拟机2》的第五部分——高效并发里,有对这部分做一个比较详细的介绍,包括Java内存模型、线程与线程安全以及锁优化几部分。这里上一篇整理的很完整的博客——深入理解Java虚拟机——高效并发
什么是死锁
死锁是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都无法继续运行。
如何解决死锁问题
预防死锁、避免死锁、检测死锁和解除死锁
具体可看这篇文章,写的很好。关于死锁
ThreadLocal类
- ThreadLocal类可以为线程提供局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但是在这个过程中,不会和其他线程的局部变量发生冲突,实现了线程的数据隔离。
- 关于ThreadLocal的应用:可以用来管理Connection,实现当前线程的操作都是使用同一个Connection,保证了事务;也可以避免一些参数的传递。
- Thread、ThreadLocal和ThreadLocalMap的关系
Thread维护一个ThreadLocalMap的引用,而ThreadLocalMap是ThreadLocal类的内部类。ThreadLocal类的set方法,其实是往Map中设置值,Key为ThreadLocal的弱引用,Value为当前传递进来的对象。而get方法,其实就是在Map中获取值。 - 关于内存泄漏问题:
由于ThreadLocalMap和生命周期和Thread一样长,如果没有手动删除对应Key就会导致内存泄露,而不是因为弱引用。防止内存泄露的方法就是手动remove() - 应用场景:
假如有一个Http请求,会经过4层Service,每层都要拿到当前发出请求的用户User,我们就可以把User对象保存在ThreadLocal之中,要使用的时候直接get即可,当然,在最后一处记得remove()!
参考文章:一篇文章搞懂Java ThreadLocal
转载:https://blog.csdn.net/fucccck_ly/article/details/105351883