一、线程和进程的概念
1.什么是进程?什么是线程?
进程是一个应用程序(1个进程是一个软件)。
线程是一个进程中的执行场景/执行单元。线程是cpu执行的最小单位
- 一个进程可以启动多个线程。
2.进程和线程的关系?
进程可以看作是一个公司,而线程可以看作是里面的员工。
例如:阿里巴巴是一个公司,即是一个进程,里面的员工马云则是一个线程。
3.进程和进程之间的内存是否独立不共享?线程与线程之间呢?
-
进程与进程之间的内存独立不共享。
例如:打开王者荣耀是一个进程,打开酷狗音乐也是一个进程,二者不共享资源。 -
线程A和线程B呢?
在java语言中:
线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
例如:假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
4.多线程机制的目的?
每个进程有自己独立的地址空间,多并发请求,为每一个请求创建一个进程导致系统开销、用户请求效率低
java中之所以有多线程机制,目的就是为了提高程序的处理效率
5.什么时候数据在多线程并发的环境下会存在安全问题?
线程安全:如果一个操作序列,不考虑耗时和最远的消耗,在单线程下执行和多线程执行的情况下,最后得到的结果是相同的,这个操作序列就叫做线程安全操作。
三个条件:
1)多线程并发
2)有共享的数据
3)共享数据有修改的行为
满足上述三个条件,就会有线程安全的问题。
6.怎么解决线程安全问题?
当多线程并发,有共享数据,并且数据有被修改的行为,此时就存在线程安全问题,可以用线程同步机制(实际上就是线程不能并发,必须排队处理)
7.同步编程模型和异步编程模型
同步编程模型:线程t1和线程t2,在线程t1执行的时候,必须等待t2线程指向结束,或者说在线程t2执行的时候,必须等待t1线程指向结束,这就是同步线程模型。
效率较低,线程排队进行
异步编程模型:线程t1和线程t2,各自执行各的,t1不管t2,t2不管t1,谁都无需等待,这种编程模型叫做异步编程模型。
效率较高,多线程并发
二、线程的创建
1.编写一个类,直接继承java.lang.Thread,重写run方法
// 定义线程类
public class MyThread extends Thread{
public void run(){
}
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程。
t.start();
2.编写一个类,实现java.lang.Runnable接口,实现run方法
// 定义一个可运行的类
public class MyRunnable implements Runnable {
public void run(){
}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
3.实现Callable接口,实现call方法
class MyCallable implements Callable {
@Override
public Object call() throws Exception {
}
4.匿名内部类
//创建线程对象,采用匿名内部类方法
//通过一个没有名字的类,new一个对象
Thread t=new Thread(new Runnable() {
@Override
public void run() {
}
});
t.start();
第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。
三、线程的生命周期及常用方法解析
1.新建状态
刚new出来的新的线程对象
2.就绪状态
就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权利(CPU时间片就是执行权)当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行,标志着线程进入运行状态
3.运行状态
run方法的开始执行,标志着这个线程进入运行状态,当之前占有的CPU时间片用完之后会重新回到就绪状态,继续抢夺CPU时间片,当再次抢到CPU时间之后,会重新进入run方法,接着上一次的代码,继续往下执行
4.阻塞状态
当一个线程遇到阻塞事件,例如接收用户键盘输入或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片
阻塞分三种情况:
(1)等待阻塞(o.wait->等待队列):运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waiting queue)中;
(2)同步阻塞(lock->锁池):运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中;
(3)其他阻塞(sleep/join):运行的线程执行Thread.sleep(long ms)或thread.join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕时,线程重新转入可运行状态中。
5.死亡状态
run方法结束
6.常用方法
6.1.start():启动线程
启动一个新的线程,
6.2.run():子线程的执行体
run方法时子线程的执行体
6.3.sleep():线程休眠
线程调用后让当前线程进入休眠,进入“阻塞状态”,放弃占有cpu的时间片,让给其他线程使用,会抛出interruptExecption异常。
这行代码出现在线程A中,A线程进入休眠状态
这行代码出现在线程B中,B线程进入休眠状态
6.4.yield():线程让步
线程调用后会暂停当前线程的执行,让步于其他同优先级或更高优先级的线程执行。会使得当前线程由“运行状态”进入到“就绪状态”。
6.5.join():线程合并
线程调用后会暂停当前的线程,等待子线程的执行,join会处于并行状态的线程合并为串行状态。
例如:加入线程A中调用B.join(),则线程A会被暂停执行,并让线程B先执行,知道线程B执行结束,A才会执行。
6.6.interrupt():线程中断
interrupt方法可以中断当前的线程,线程进入到”阻塞状态“或者时线程运行结束。
public void interrupt() {
if (this != Thread.currentThread()) {
checkAccess();
// thread may be blocked in an I/O operation
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // set interrupt status
b.interrupt(this);
return;
}
}
}
该方法的特点:
- 如果线程当前状态是可中断的状态(sleep\join\wait等方法导致线程进入到阻塞状态),在任意的其他线程中调用(线程名).interrupt方法时,那么线程会立即抛出interruptException异常,退出阻塞状态。
- 如果线程当前处于运行状态,吊桶(线程名).interrupt方法,线程会继续执行,知道线程发送sleep\join\wait等方法的调用会进入到阻塞状态,随后会抛出interruptException异常,退出阻塞状态。
6.7.deamon:守护线程
守护线程作用时服务于非守护线程,垃圾回收线程就是一个典型的守护线程。
守护线程的生命周期时依赖于非守护线程的。
6.8.priority:线程优先级
java中的线程分为10个优先级,一般默认优先级为5.
线程优先级并不绝对按着设定的顺序执行,最终决定权在操作系统。
四、相关混淆概念
1.并行与并发:
并发:
指多个线程操作同一个资源(CPU),不是同时执行,而是交替执行。
并发的特性:
原子性:如果一个操作序列是不可被分割的,那就是一个原子操作,也叫做操作具有原子性。
a++;//a++可以被分割为a=a+1;故该操作不具有原子性。
在并发编程中通过同步技术(lock)或者是Concurrent数据集合来保证操作安全性。
可见性:一个变量在多个线程中是共享的,如果一个线程修改了这个变量的值,那么在其他线程中可以立即知道该变量的值被改变,则称这个修改是具有可见性的。
【类似这种临时变量会有主内存和工作内存(各个线程中独有的内存)都会保存一份】
有序性;在一个线程内存观察,所有的操作都是有序的。在线程之间观察,从一个线程来观察其他线程,所有线程都可以交叉并行执行,是正序的。
并行:时真正意义上的同时执行,多核CPU,每一个线程使用一个单独的CPU的资源来运行。
2.临界资源和临界区
临界资源:一般指的是内存资源,CPU资源,一个时刻只允许一个线程访问,一个线程访问临界资源时,其他线程时不能使用的。临界资源是非可剥夺性资源。
临界区:在一个线程中可以访问临界资源的程序代码的片段。(注意:不是CPU,不是内存资源,是代码片段)
使用临界区的使用原则:“空闲让进、忙则等待、有限等待、让权等待”
- 空闲让进:当无进程处于临界区时,允许进程进入临界区,并且只能在临界区运行有限的时间。
- 忙则等待:当有一个进程在临界区是,其他需要进入临界区的进程必须等待,以保证进程互斥地访问临界资源。
- 有限等待:对要求访问临界资源的进程,应保证进程等待有限时间后进入临界区,以免陷入“饥饿”状态。
- 让权等待:当进程不能进入自己的临界区时,应立即释放处理机,以免进程陷入“忙等”状态。 假如所需要的临界资源不是CPU资源时可以放弃对CPU的使用,从而争夺所需要的临界资源。
五、线程简单问题及其回答
1.使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?
是,main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈
2.什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。
3.对于单核的CPU来说,真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没问题的。
即:4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人来的感觉是:多个事情同时在做!!!!!
即:两个线程频繁切换,给人的感觉是同时进行的。
4.多线程和多进程有哪些区别?
1)每个进程拥有自己独有的数据,线程共享数据,线程之间的通信相比于进程之间的通信更有效更容易
2)线程相比于进程创建/销毁开销更小
3)进程是资源分配的最小单位,线程是cpu调度的最小单位
4)多进程程序更加健壮,多线程程序只要有一个线程挂掉,对其共享资源的其他线程也会产生影响
5)如果追求速度,选择线程
如果频繁创建和销毁,选择线程
如果追求系统更加稳定,选择进程
5.start()方法和run()方法的关系
start方法:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后瞬间就结束了
(这段代码只是为了开启一个新的栈空间,只要新的栈空间开辟出来,start()方法就结束了,线程就启动成功了)
启动成功的线程自动调用run方法,并且run方法在分支栈的栈底部(压栈)
run方法在分支栈的底部,main方法在主栈的底部,run和main是平级的
mythread.start();
这行代码不结束,下面不会进行,只是该行代码瞬间结束,这里的代码还是在主栈中进行。
mythread.run();
不会启动线程,不会分配新的分支栈