线程总结

1.线程
什么是线程
线程是指程序在执行过程中,能够执行程序代码的一个执行单元。

2.线程和进程的区别是什么
进程是指一段正在执行的程序。线程又叫做轻量级进程。一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段、数据段和堆空间)及一些进程级的资源,但是各个线程拥有自己的栈空间。
每个进程中通常都有多个线程互不影响的并发执行。
线程与进程的区别归纳:
a. 地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b. 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c. 调度和切换:线程上下文切换比进程上下文切换要快得多。
d. 在多线程OS中,进程不是一个可执行的实体。
3.创建线程有哪几种方式,哪种最好
有四种方式:
1、继承Thread类,重写run()方法;
2、实现Runable接口,并实现该接口的run()方法;
3、应用程序可以使用Executor框架来创建线程池;
4、还有一种方式是实现Callable接口,重写call()方法;
Runnable最好:(1)避免了单继承,在java中不能多继承,如果继承了Thread则不能继承了其他类,而如果实现了Runnable还可以继承其他类。(2)增强了程序的扩展性,降低了耦合性,将创建线程和启动线程分离开来。
Runnable和Callable的区别:(1)Callable有返回值,而Runnable没有返回值。(2)Callable的call()方法可以抛出异常,而Runnable的run()方法不能。(3)运行Callable可以获得一个Future对象,该对象可以监听call()方法的运行情况,当利用Future对象的get()方法获取结果时,当前线程阻塞,直到call()方法运行结束,返回结果。
4.start() 跟 run() 方法的区别和联系
系统通过调用线程类的start()方法来启动一个线程,此时该线程处于就绪状态,而非运行状态,也就意味着这个线程可以被JVM来调度执行。在调度过程中,JVM通过调用线程类的run()方法来完成实际的操作,当run()方法结束后,此线程就会终止。
如果直接调用线程类的run()方法,这会被当做一个普通的函数调用,程序中仍然只有主线程这一个线程,也就是说,start()方法能够异步的调用run()方法,但是直接调用run()方法却是同步的,因此也就无法达到多线程的目的。
run()方法:是在主线程中执行方法,和调用普通方法一样;(按顺序执行,同步执行)
start()方法:是创建了新的线程,在新的线程中执行;(异步执行)。
4.线程的几种可用状态

  • 新建( new ):新创建了一个线程对象。
  • 可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。
  • 运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。
  • 阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。阻塞的情况分三种:
    (一). 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。
    (二). 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
    (三). 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
  • 死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。
    在这里插入图片描述
    同步
    1、为什么要线程同步?
    因为当我们有多个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作,一个取100块,一个存钱100块。假设账户原本有0块,如果取钱线程和存钱线程同时发生,会出现什么结果呢?取钱不成功,账户余额是100.取钱成功了,账户余额是0.那到底是哪个呢?很难说清楚。因此多线程同步就是要解决这个问题。
    2.线程同步的实现方式?三种方式的区别
    (1)Synchronized同步方法
    锁对象:普通方法锁对象是this,静态方法的锁对象是本类的字节码,.class。
    作用范围是加入Synchronized的方法。
    锁对象是隐式的,不用自己写锁对象
    (2)Synchronized同步代码块
    锁对象可以是任意对象。
    作用范围是被Synchronized(锁对象){}包起来的部分。
    需要自己写锁对象。
    (3)Lock接口及其实现类ReentrantLock。
    1).lock():以阻塞的方式获取锁,如果获取到锁则立刻返回,如果获取不到锁,则等待,直到获取锁后返回。
    2).tryLock():以非阻塞的方式获取锁,如果获取到锁则返回true,获取不到锁则返回false。
    3).tryLock(long timeout,TimeUnit unit ):如果获取到了锁则立即返回,如果获取不到锁则等待设定的时间,如果超过设定时间还未获取到锁则返回false。
    4).lockInterruptibly():如果获取到锁,则立即返回,如果没有获取到锁则等待,直到获取到锁或者当前线程被其他线程议程中断(IntreeuptedException).与lock不同的是lock()方法会忽略interrupt().
    3.Synchronized和lock的区别?
    (1)Synchronized是自动释放锁,不会因为没有释放锁而导致死锁。而lock需要手动释放锁unlock().
    (2)Lock提供了异步获取锁的方法tryLock(),它不会一直等待,如果锁被其他线程占用了则立即返回false.
    (3)在资源竞争不是很激烈的情况下,Synchronized同步的性能要优于ReentrantLock.而竞争很激烈的情况下ReentrantLock的性能要优于Synchronized.
    (4)Synchronized是托管个jvm实现的,而Lock()的锁定是通过代码实现的,Synchronized加在方法上,或者同步代码块中,而Lock需要显示的指定起始位置和终止位置。
    4.Synchronized的作用范围?
    (1)若一个线程进入该对象的非静态同步方法(Synchronized()),则其他线程可以进入该对象的非同步方法,静态同步方法(因为静态同步方法的锁对象是该类的字节码,而非静态同步方法的锁对象是this.)
    5.Synchrinized代码块中为什么要使用wait()和notify()方法?
    使用wait()和notify()可以进行线程间的通信,使线程间有序的对一个资源进行访问,当调用wait()方法时当前线程释放锁,并等待,并且可以通过notify()唤醒一个线程(当前等待队列的第一个线程),并让该线程获得锁。notifyAll()方法可以唤醒所有线程,但并不是让所有线程都获得锁而是让他们去竞争。
    5监视器(Monitor)
    在 java 虚拟机中, 每个对象( Object 和 class )通过某种逻辑关联监视器,每个监视器和一个对象引用相关联, 为了实现监视器的互斥功能, 每个对象都关联着一把锁.
    一旦方法或者代码块被 synchronized 修饰, 那么这个部分就放入了监视器的监视区域, 确保一次只能有一个线程执行该部分的代码, 线程在获取锁之前不允许执行该部分的代码
    另外 java 还提供了显式监视器( Lock )和隐式监视器( synchronized )两种锁方案。
    6.锁升级的原理
    在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。在Java中,锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。
    在这里插入图片描述
    6.Condition
    https://www.cnblogs.com/gemine/p/9039012.html
    阻塞:
    1.sleep(),wait(),yield()方法的区别?
    sleep和wait()区别:
    (1)原理不同:sleep()方法是Thread类的静态方法,它是让当前线程暂停执行一段时间,而把执行机会让给其他线程,等到计时时间一到,该线程会自动苏醒。而wait()方法是Object类中的方法,它是将当前线程释放锁等待,而同时可以利用notify或notifyAll()方法唤醒其他线程。
    (2)对锁的处理机制不同:sleep()不会释放锁,而wait()会释放锁。
    (3)wait()方法只能在同步代码快或同步方法中使用,而sleep可以在任何地方使用。
    sleep()和yield()礼让线程的区别:
    sleep()和yield()方法都是Thread类中的静态方法。
    (1)sleep()方法是让当前线程处于阻塞状态,直到超出指定时间后才进入可运行状态,而yield()方法是让该线程进入可运行状态。
    (2)sleep()方法给其他线程运行机会时不考虑线程的优先级,而yield()方法则考虑优先级,优先级高的先运行。
    (4)sleep()方法声明抛出InterruptedException异常,而yield()方法不抛出任何异常。

死锁:
1.什么是死锁
所谓死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
2.产生死锁的原因
可归结为如下两点:
a. 竞争资源
系统中的资源可以分为两类:
可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
b. 进程间推进顺序非法
若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁

3.产生死锁的条件
死锁产生的4个必要条件:
1、互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
2、不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
3、请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
4、循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。
4.如何确保N个线程可以访问N个资源同时又不导致死锁
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
5.项目中遇到过死锁吗?怎么解决的

6.预防死锁
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

7.死锁检测
1、Jstack命令
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息。 Jstack工具可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
2、JConsole工具
Jconsole是JDK自带的监控工具,在JDK/bin目录下可以找到。它用于连接正在运行的本地或者远程的JVM,对运行在Java应用程序的资源消耗和性能进行监控,并画出大量的图表,提供强大的可视化界面。而且本身占用的服务器内存很小,甚至可以说几乎不消耗。

线程池
1.什么是线程池,为什么要使用线程池
所谓线程池,就是将多个线程放在一个池子里面(所谓池化技术),然后需要线程的时候不是创建一个线程,而是从线程池里面获取一个可用的线程,然后执行我们的任务。线程池的关键在于它为我们管理了多个线程,我们不需要关心如何创建线程,我们只需要关系我们的核心业务,然后需要线程来执行任务的时候从线程池中获取线程。任务执行完之后线程不会被销毁,而是会被重新放到池子里面,等待机会去执行任务。
我们为什么需要线程池呢?首先一点是线程池为我们提高了一种简易的多线程编程方案,我们不需要投入太多的精力去管理多个线程,线程池会自动帮我们管理好,它知道什么时候该做什么事情,我们只要在需要的时候去获取就可以了。其次,我们使用线程池很大程度上归咎于创建和销毁线程的代价是非常昂贵的,甚至我们创建和销毁线程的资源要比我们实际执行的任务所花费的时间还要长,这显然是不科学也是不合理的,而且如果没有一个合理的管理者,可能会出现创建了过多的线程的情况,也就是在JVM中存活的线程过多,而存活着的线程也是需要销毁资源的,另外一点,过多的线程可能会造成线程过度切换的尴尬境地。

2.创建线程池的几种方式?

(1)newFixedThreadPool(int nThreads)
创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化,当线程发生未预期的错误而结束时,线程池会补充一个新的线程。如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

(2)newCachedThreadPool()
创建一个可缓存的线程池,如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程,线程池的规模不存在任何限制。
这种类型的线程池特点是:
1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
3).在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

(3)newSingleThreadExecutor()
这是一个单线程的Executor,它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行

(4)newScheduledThreadPool(int corePoolSize)
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
在这里插入图片描述
在这里插入图片描述
3.线程池的的5种状态
线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。
线程池各个状态切换框架图:
在这里插入图片描述
1、RUNNING
(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(02) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
2、 SHUTDOWN
(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
3、STOP
(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
4、TIDYING
(1) 状态说明:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
5、 TERMINATED
(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。
4.submit()和execute()方法的区别?
execute和submit都属于线程池的方法。
(1)execute只能提交Runnable类型的任务,而submit既能提交Runnable类型任务也能提交Callable类型任务。
(2)execute会直接抛出任务执行时的异常,submit会吃掉异常,可通过Future的get方法将任务执行时的异常重新抛出。
(3)execute所属顶层接口是Executor,submit所属顶层接口是ExecutorService,实现类ThreadPoolExecutor重写了execute方法,抽象类AbstractExecutorService重写了submit方法。
(4)submit()有返回值,execute()没有返回值.

5.线程池的参数有哪些?
(1)corePoolSize:线程池的大小
(2)maximumPoolSize:线程池中创建的最大线程数;
(3)keepAliveTime:空闲的线程多久时间后被销毁。
(4)unit:TimeUnit枚举类型的值,代表keepAliveTime时间单位.
(5)workQueue:阻塞队列,用来存储等待执行的任务,决定了线程池的排队策略.
(6)handler:线程拒绝策略。当创建的线程超出maximumPoolSize,且缓冲队列已满时,新任务会拒绝.

守护线程
在Java中有两种线程,一种是用户线程,一种是守护线程。守护线程是一种特殊的线程,它具有陪伴的特性,当线程中不存在非守护线程了,守护线程也就自动销毁了。

ThreadLocal
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值