目录
多线程
进程和线程的区别
进程的定义: 是指一个内存中运行的应用程序(程序的一次运行就产生一个进程),每个进程都有自己独立的一块内存空间,比如在Windows的任务管理器中,一个运行的xx.exe就是一个进程。
线程的定义: 是指进程中的一个执行任务(控制单元),一个进程中可以运行多个线程,多个线程可共享进程的数据,在进程中,线程被创建后使用进程申请的内存空间,不重新申请内存。
什么是多线程?
简单的说,在同一个进程中并发运行的多个子任务就是多线程。
一个进程至少有一个线程,为了提高CPU的效率,可以在一个进程中开启多个控制单元,这就是多线程。
为什么是多线程?
线程出现的原因: 线程的出现为了解决实时性问题。
多线程和Java
在Java程序运行,至少有2个线程启动了,它们分别是:Main线程(主线程)和垃圾回收GC线程,
主线程很特殊,在启动JVM的时候自动启动的(同时启动的还有GC的垃圾回收检测线程)。
19:17:46
老师,教案专门强调主线程是在jvm启动时启动,GC线程是啥时候启动的啊?19:18:08
gc线程在需要gc的时候启动,程序申请内存空间的时候会触发gc算法,计算可用空间,然后根据gc规则决定要不要进行垃圾回收。19:18:14
它前面说的维护垃圾回收线程指的是不是GC线程啊?19:18:22
那是守护线程,gc线程也属于守护线程.19:18:32
gc线程有两部分工作,一部分是检测是否需要垃圾回收,还有一部分是进行垃圾回收19:18:38
检测的线程是jvm启动时就运行的
用户线程和守护线程(破)(了解)
在Java中有两类线程:User Threapod(用户线程)、Daemon Thread(守护线程)
用户线程和守护线程的区别
二者其实基本上是一样的。唯一的区别在于JVM何时离开。
用户线程:当存在任何一个用户线程未离开,JVM是不会离开的。
守护线程:如果只剩下守护线程未离开,JVM是可以离开的。
用户线程定义:平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程。
守护线程定义:是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种,
- 守护线程,比如垃圾回收线程,就是最典型的守护线程。
- 用户线程,就是应用程序里的自定义线程
并行和并发
并发:同一时间多进程轮流使用CPU,多进程形成并发,任意时间只有一个程序在占用CPU。
并行:同一时间多个进程同时使用各自的CPU,需要多个CPU支持,多进程形成并行。
Java中线程的创建和启动
方式
方式一:继承Thread类
- 自定义类继承Thread
- 覆写run方法
- 创建自定义类对象
- 自定义类对象调用start方法
方式二:实现Runnable接口
- 自定义类实现Runnable接口
- 覆写run方法
- 创建自定义类对象
- 把自定类的对象作为Thread类构造器参数,并调用Thread对象start方法
方式一、二的区别:
- 继承Thread类的方式使用起来方便,启动一个线程也方便,很多功能都在Thread类中定义好了。
- 实现Runnable接口的方式启动得依赖于Thread,因为本身Runnable中只有run方法,请看Thread的构造方法。
- 在Thread的子类中,如果想要多个子类共享同一资源必须使用static修饰此资源,也就说明了继承Thread这种方式本身不是很适合共享资源的。Runnable接口的实现类中共享资源无需被静态修饰,即可实现资源共享,因为它是为Thread构造器传入Runnable实现类对象,多个线程使用的是同一个Runnable实现类,而不是直接New一个线程,故而实现了资源共享。
- 由于Java不允许多继承,因此实现类实现了Runnable接口可以再继承其他类,但是Thread明显不可以。
Runnable为什么不可以直接run?
Runnable其实相对于一个Task,并不具有线程的概念,如果你直接去调用Runnable的run,其实就是相当于直接在主线程中执行了一个函数而已,并未开启线程去执行。
线程的生命周期
课本上的引用:
- 新生状态:用 new 关键字建立一个线程后,该线程对象就处于新生状态。
处于新生状态的线程有自己的内存空间,通过调用 start()方法进入就绪状态。- 就绪状态
处于就绪状态线程具备了运行条件,但还没分配到 CPU,处于线程就绪队列,等待系统为其分配
CPU。当系统选定一个等待执行的线程后,它就会从就绪状态进入执行状态,该动作称为“CPU 调
度”。- 运行状态
在运行状态的线程执行自己的run 方法中代码,直到等待某资源而阻塞或完成任务而死亡。如果在
给定的时间片内没有执行结束,就会被系统给换下来回到等待执行状态(就绪)。- 阻塞状态
处于运行状态的线程在某些情况下,如执行了 sleep(睡眠)方法,或等待 I/O
设备等资源,将让出 CPU 并暂时停止自己运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的
I/O 设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续执行。- 死亡状态
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个,一个是正常运行的线程完成
了它的全部工作;二是线程抛出未捕获的Exception或Error,三是线程被强制性地终止,如通过
stop 方法来终止一个线程【易导致死锁,不推荐】
结论:
- 就绪状态:有资源,无资格。
- 运行状态:有资源,有资格。
- 阻塞状态:无资源,无资格。
提示:
线程的执行时间和CPU为其分配的时间片有关,当线程时间片到期,就由运行状态进入就绪状态中,即进入就绪队列,等待CPU的重新分配调度。
操作线程的方法
join()方法
join方法的主要作用就是同步,它可以使得线程之间的并发执行变为串行执行。
比如在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
细节:现有A、B两个线程同时启动,B线程执行一会儿但未执行完毕,此时A线程抢占到了资源,A开始执行,此时A执行了一会儿但未执行结束,此时B线程调用了join方法,此时A线程进入阻塞队列等待,B线程从刚刚被A抢占资源时的进度继续执行知道B线程结束,此时A再继续执行。
sleep(毫秒数)方法
sleep方法让正在执行的线程暂停一段时间,进入阻塞状态,常常用来模拟网络延迟,或者耗时操作等等。
调用sleep()后,在指定时间段之内,该线程不会获得执行的机会
线程的优先级
每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。并不是说优先级高的就一 定先执行,哪个线程的先运行取决于CPU的调度。
Thread对象的setPriority(int x)和getPriority()用来设置和获得优先级。
线程安全性
什么是线程安全?
当多线程并发访问同一个资源对象(共享资源)的时候,可能出现线程不安全的问题。
什么是synchronized?
同步锁,又称之为同步监听对象/同步锁/同步监听器/互斥锁:
synchronized(同步锁){
// 需要同步操作的代码
}
为什么是synchronized?
为了保证每个线程都能正常执行原子操作(避免线程原子性操作被中断),Java引入了线程同步机制。
在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就执行,其他的线程只能在代码块外等待。
如何判断synchronized应该锁谁?
根据需要同步的资源来划分:
- 当需要同步的资源是非静态的基本数据类型时,锁其上一级,例如
User类
中的成员private int money = 100;
是需要同步的资源,此时money是int类型,故而锁User,即锁this:synchronized(this){}
。 - 非静态的由于数据类型时,锁资源本身。
- 静态的基本数据类型或引用数据类型时,锁当前类的Class对象,即 Employee.class、User.class、Student.class此类
拓展:八锁现象。
synchronized锁时的细节:现有A、B、C三个线程(均带有同步锁),一个共享资源Q。此时ABC三个线程启动进入就绪队列,此时A线程被CPU选中先执行,此时A对资源Q进行上锁,A开始使用资源Q完成任务,此时B、C线程前来抢夺资源Q,此时Q被A锁定,B、C无法抢到,此时B、C进入阻塞队列,此时A本次执行结束回到就绪队列,此时B、C位于阻塞队列中处于阻塞状态,只有A位于就绪队列中处于就绪状态,CPU只有选择A线程,A再执行,A线程执行结束了,为资源解锁,A回到就绪队列,此时B、C知道资源解锁,从阻塞状态变为就绪状态,B、C也加入就绪队列,此时A、B、C均在就绪队列中,等待CPU的下一次调度,如此循环往复知道共享资源耗尽。
在被synchronized锁的时候,sleep()的时间也计入线程的生命周期中,不会为资源进行解锁,而如果此时线程调用的是wait()方法,则wait()方法会为共享资源进行解锁。
同步方法
使用synchronized修饰的方法,就叫做同步方法。保证A线程执行该方法的时候,其他线程只能在方法外等待。
此时同步锁是谁?
锁的是调用当前同步方法的对象:
- 对于非static方法,同步锁就是this。
- 对于static方法,同步锁就是当前方法所在类的字节码对象。
synchronized的优劣(掌握)
好处:保证了多线程并发访问时的同步操作,避免线程的安全性问题。
缺点:使用synchronized的方法/代码块的性能要低一些。
建议:尽量减小synchronized的作用域。
面试题:
- StringBuilder和StringBuffer的区别
- 说说ArrayList和Vector的区别
- HashMap和Hashtable的区别
通过源代码会发现,主要就是方法有没有使用synchronized的区别,比如StringBuilder和
StringBuffer。我们知道,StringBuilder的效率高,但不安全;StringBuffer效率低,但安全。
因此得出结论:使用synchronized修饰的方法性能较低,但是安全性较高,反之则反。
异常
重载和方法是否抛出异常无关,也就是说,两个方法如果方法签名相同但一个抛出异常、一个不抛出异常,这样是不满足重载的。
运行时异常(了解)
runtime异常,顾名思义在编译时期不被检测,只有在运行时期才会被检查出来。
运行异常可以不使用try…catch处理,但一旦出现异常就将由JVM处理(打印堆栈信息)。
RuntimeException(运行时异常)通常是指因设计或实现方式不当而导致的问题。程序员小心谨慎是
可以避免的异常。如:事先判断对象是否为null就可以避免NullPointerException异常,事先检查除数
不为0就可以避免ArithmeticException异常。运行时异常特点:
在编译阶段,Java编译器检查不出来。一般的,程序可以不用使用try-catch和throws处理运行异
常。
编译时异常(了解)
编译被检查异常,顾名思义就是在编译时期就会被检测到的异常。除了RuntimeException以及子
类以外,其他的Exception及其子类都是编译异常,有时候也称之为检查时异常。特点:
在编译阶段,Java编译器会检查出异常,也就说程序中一旦出现这类异常,要么使用try-catch语句
捕获,要么使用throws语句声明抛出它,否则编译就不会通过。简而言之:程序要求必须处理编译异常,使用try-catch或throws处理。
编译时异常(检查时异常) : 程序必须做出处理,一般继承于Exception
异常处理
抛出异常
自定义异常
常见异常有哪些
Exception和Error的区别