多线程
1. 什么是线程和进程?
进程是程序的一次执行过程,是系统运行程序的基本单位,进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。当启动main函数时,就启动了JVM进程。
线程:线程与进程相似,但线程是一个比进程更小的执行单位。一个进程执行中可以产生多个线程。在jvm中,同类的线程共享堆空间和方法区,每个线程私有PC寄存器、虚拟机栈、本地方法栈
2. 线程与进程的关系,区别及优缺点?
关系:在一个进程执行中可以产生多个线程,线程是进程的最小运行单位。
区别:各进程之间是独立的,同一进程的多个线程可能会互相影响
优缺点:线程运行的开销小,但不利于资源管理和维护。进程反之。
3. 并发与并⾏的区别?
并行:单位时间内,多个任务同时执行
并发:同一时间段,多个任务都在执行;(多个线程操作一个资源)
4. 使⽤多线程可能带来什么问题?
内存泄漏、死锁、上下文切换等。。
5.线程的⽣命周期和状态?
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:
线程在生命周期内并不是固定于某个状态,而会随着代码执行在不同的状态切换。
线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态(RUNNABLE)的线程获得了 cpu 时间片(timeslice)后就处于 RUNNING(运行) 状态。当线程执行 wait()方法之后,线程进入 **WAITING(等待)**状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。
6.上下文切换
指线程执行权的切换。就是当一个线程的时间片用完后会重新进入就绪状态并且让其他线程执行,这个过程就是一次上下文切换。
概括来说就是:当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次 上下文切换。
7.死锁
存在两个线程A和B,线程A抱着自己的资源1同时还想要B的资源2,而线程B抱着自己的资源2同时还想要A的资源1,两个线程被无限期的阻塞,因此导致了死锁。
一般来说,要出现死锁问题需要满足以下条件:
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何避免死锁?
死锁是由四个必要条件导致的,所以一般来说,只要破坏这四个必要条件中的一个条件,死锁情况就应该不会发生。
-
如果想要打破互斥条件,我们需要允许进程同时访问某些资源,这种方法受制于实际场景,不太容易实现条件;
-
打破不可抢占条件,这样需要允许进程强行从占有者那里夺取某些资源,或者简单一点理解,占有资源的进程不能再申请占有其他资源,必须释放手上的资源之后才能发起申请,这个其实也很难找到适用场景;
-
进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态。这个方法看似有点用处,但是它的缺点是可能导致资源利用率和进程并发性降低;
-
避免出现资源申请环路,即对资源事先分类编号,按号分配。这种方式可以有效提高资源的利用率和系统吞吐量,但是增加了系统开销,增大了进程对资源的占用时间。
8.sleep()和wait()方法的比较
sleep释放资源不释放锁,wait释放资源释放锁
wait通常用于线程交互,sleep用于暂停执行
wait方法调用用,线程不会自动苏醒(wait(long time)超时后会自动苏醒),需要其他线程调用同一对象的notify或notifyAl。sleep方法执行完成,线程自动苏醒。
两者都可以用于暂停线程的执行。
9. 调用start()和run()的区别
new一个Thread线程进入了新建状态;调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。start() 会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。而直接执行run()方法,会把run方法当成一个main线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结:调用start方法方可启动线程并使线程进入就绪状态,而run方法只是thread的-一个普通方法调用,还是在主线程里执行。
10.Synchronize关键字
Synchronize关键字是为了解决多个线程之间访问资源的同步性。Synchronize关键字可以保证其所修饰的方法或者代码块在某一时刻只能有一个线程执行。
三种使用方式:
- 修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
- 修饰静态方法:也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static表明这是该类的一个静态资源,不管 new了多少个对象,只有–份)。所以如果-一个线程A调用一个实例对象的非静态 synchronized方法,而线程B需要调用这个实例对象所属类的静态synchronized 方法,是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的锁,而访问非静态synchronized方法占用的锁是当前实例对象锁。
- 修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。
总结: synchronized 关键字加到static静态方法和synchronized(class)代码块上都是给Class类上锁。synchronized关键字加到实例方法上是给对象实例上锁。尽量不要使用synchronized(String a)因为JVM中,字符串常量池具有缓存功能!