2020最新"Java多线程"知识点全面总结(期末,面试必备).


1.什么是进程?什么是线程?

进程是一个应用程序(1个进程是一个软件)。
线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程。


现在的java程序中至少两个线程****并发:
先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。


2.进程与线程的例子?

火车站:一个进程
火车站中的每一个售票窗口:一个线程
我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
所以多线程并发可以提高效率


在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内存独立一个线程一个栈
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。


使用了多线程机制之后,main方法结束,程序可能也不会结束。
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。


3.实现线程的两种方式?

1.编写一个类,直接继承java.lang.Thread,重写run方法。
通过start()方法执行
根据结果可以证明是并发执行

在这里插入图片描述


2.编写一个类实现java.lang.Runnable接,如图方法创建线程对象,调用start方法。

在这里插入图片描述


第二种方法更常用,因为它更灵活,可以继承,还可以实现多个接口,方便使用。


匿名内部类写法:
在这里插入图片描述


4.获取当前对象和名字(获取和修改)?

1.获取线程当前对象: Thread t = Thread.currentThread();返回值t就是当前线程。
2.获取线程对象的名字 : String name = 线程对象.getName();
3.修改线程对象的名字: 线程对象.setName(“线程名字”);


获取修改名字,获取当前对象:在这里插入图片描述

运行结果:在这里插入图片描述


5.sleep()休眠方法?

static void sleep(long millis):Thread.sleep(毫秒数);
让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片让给其它线程使用
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
隔特定的时间,去执行一段特定的代码,每隔多久执行一次。


休眠方法:在这里插入图片描述

运行结果:
等待5秒,出现main,在等待5秒,输出for循环打印信息 在这里插入图片描述


一个关于sleep()的面试题:
问题:图中代码会让线程t进入休眠状态吗?
在这里插入图片描述

分析:sleep是static修饰的,静态方法,t.sleep()执行时还是Thread.sleep(),出现在main方法中,让main线程进入休眠,与t无关。

运行结果:
打印出打印信息,等待五秒,输出hello World!

在这里插入图片描述


6.中止休眠方法?

sleep睡眠太久了,如果希望中途醒来,如何叫醒一个正在睡眠的线程??
注意:这个不是终断线程的执行,是中止线程的睡眠
方法:interrupt();


中止休眠:在这里插入图片描述运行结果:
先输出begin,等待5秒,输出end。
分析:
本应该输出begin后,等待10秒,输出end。主线程中也有sleep方法,他们是并发进行的,5秒之后,来到了interrupt方法,休眠中止,输出end。
在这里插入图片描述


7.终止线程执行?

怎么强行终止一个线程的执行?
stop();// 已过时(不建议使用。)


终止执行:在这里插入图片描述
**结果:**输出t——>0,1,2,3,4中止了
**分析:**每隔一秒,输出一行语句,但是主线程中的sleep方法,模拟了5秒,他们是并发的。
所以5秒之后执行了stop方法,终止了线程的执行。
在这里插入图片描述

注意:这种方式存在很大的缺点:容易丢失数据。因为这种方式是直接将线程杀死了,
线程没有保存的数据将会丢失。不建议使用。


合理的终止一个线程的执行方法:
做一个boolean标记,配合if使用,在你想结束的时候定义为false,if为true就执行,false就终止。
在这里插入图片描述
运行结果:
每隔一秒输出一行,5秒后中止进行进程。
在这里插入图片描述


8.(重点)线程安全问题?

在多线程中,我们必须保证数据的安全,否则,多线程效率在高都毫无意义
比如一个场景:银行中对同一个银行账户同时在柜台和ATM取款,相当于两个线程同时对银行账户操作,但是我们不可能利用银行的延迟问题取出比银行账户多的钱,就是因为必须实现多线程安全问题,否则将会有不堪设想的问题。


什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。
足以上3个条件之后,就会存在线程安全问题。


三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。


如果使用局部变量的话:
建议使用:StringBuilder。
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。


怎么解决线程安全问题呢?
排队执行解决线程安全问题。
这种机制被称为:线程同步机制。(线程不能并发了,线程必须排队执行。)
(异步编程模型:线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁)


实现线程同步的方法:
1.synchronized(线程共享对象){
同步代码块;
}

()中存放的是线程的共享对象,每个对象都有自己的一把锁。这就相当于让线程排队,有次序的执行。t1获得了锁,可以实现同步代码块,这个时候别的线程就不能干涉,因为锁起来了,执行完毕后,将锁打开,t2再获得实现的权力,这样排队执行,就实现了线程安全。

**形象的例子:**一个洗手间,A先进去了,把门锁起来了,这个时候没有人可以进入。A上完厕所,把锁打开出来了。B再进入洗手间,把门锁上了,别人也无法进去,这就是排队机制。


代码实现不同线程对同一个银行账户对象取款:
定义了一个银行账户,有账户名,余额,取款方法,利用synchronized实现线程安全。
t1实现取款方法时,就锁起来了,t2无法操作,直到执行完同步代码块,锁打开,t2进行操作,保证了线程安全。
(this)代表t1和t2共享的对象,他们俩共享一个银行账户对象,而取款方法是通过银行账户账户调用的,所以用this。

在这里插入图片描述

定义线程类,run方法中是取款5000并输出取款信息。
在这里插入图片描述
测试类,定义账户对象act,定义线程t1,t2。让t1和t2对账户对象act进行取款操作:

在这里插入图片描述

运行结果:实现了线程安全

在这里插入图片描述


假设有t1,t2,t3,t4,t5,我们想让t1,t2,t3,排队就设置他们仨的共享对象,t4,t5就不需要排队。
当我们设置synchronized(“abc”)也是可以的,因为"abc"在字符常量池中只有一个,但是这样设置就变成了所有线程对象都需要排队。


当创建了新的账号对象,并创建了新的t3和t4线程。

在这里插入图片描述

结果:
在这里插入图片描述


2.在实例方法上使用synchronized
表示共享对象一定是this(局限性)
并且同步代码块是整个方法体。
缺点:可能无故导致扩大范围,导致效率降低。不常用。

替换代码:

public synchronized void withdraw(double money){
.....
}

3.在静态方法上使用synchronized表示找类锁。
类锁永远只有1把。就算创建了100个对象,那类锁也只有一把。

 public synchronized static void doSome(){
 ....
 }

9.(了解)线程的调度?

常见的线程调度模型有哪些?
抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
java采用的就是抢占式调度模型。
均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。


java中提供了哪些方法是和线程调度有关系的呢?
实例方法:
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程优先级
void join() :合并线程:当某个线程使用join方法加入到另一个线程中,另一个线程会等待这个线程执行完毕后再继续执行。
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。
静态方法:
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。


10.线程的生命周期?

1.新建状态:
用new关键字建立一个线程后,该线程对象就处于新建状态。处于新生状态的线程有自己的内存空间,通过调用start()方法进入就绪状态。
2.就绪状态:  
处于就绪状态线程具备了运行条件,但还没分配到CPU,处于线程就绪队列,等待系统为其分配CPU。当系统选定一个等待执行的线程后,它就会从就绪状态进入运行状态,该动作称为“CPU调度”。
3.运行状态:  
在运行状态的线程执行自己的run方法中代码,直到等待某资源而阻塞或完成任何而死亡。如果在给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。
4.阻塞状态:
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续执行。
5.死亡状态:
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有三个,一个是正常运行的线程完成了它的全部工作;另一个是线程被强制性地终止,如通过stop方法来终止一个线程【不推荐使用】;三是线程抛出未捕获的异常。


11.守护线程?

java语言中线程分为两大类:
一类是:用户线程(主线程main方法是一个用户线程。)
一类是:守护线程(后台线程)  
其中具有代表性的就是:垃圾回收线程(守护线程)。


守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。


守护线程用在什么地方呢?
系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程。
一直在那里看着,没到00:00的时候就备份一次。所有的用户线程
如果结束了,守护线程自动退出,没有必要进行数据备份了。

在这里插入图片描述
实现方法:t.setDaemon(true);
当用户线程结束后,守护线程自动终止(虽然是死循环)。
结果:
在这里插入图片描述


12.定时器?

定时器的作用:间隔特定的时间,执行特定的程序。
如;每天要进行数据的备份操作。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的:
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。

在这里插入图片描述
结果:
从指定的第一次时间开始,每间隔30秒就更新一次输出记录。
在这里插入图片描述


13.可以获取到线程的执行结果的实现线程方式?

实现Callable接口。
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

在这里插入图片描述
结果:在这里插入图片描述


14.开发中应该怎么解决线程安全问题?

synchronized会让程序的执行效率降低,用户体验不好。系统的用户吞吐量降低。用户体验差。在不得已的情况下再选线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。
(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。


15.Object类中的wait和notify方法。

wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。
wait方法和notify方法不是通过线程对象调用。
在这里插入图片描述


wait()方法作用?
Object o = new Object();
o.wait();
让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止
o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。


notify()方法作用?
Object o = new Object();
o.notify();
唤醒正在o对象上等待的线程。
还有一个notifyAll()方法:这个方法是唤醒o对象上处于等待的所有线程


16.死锁?

互相等待,没有结果。
在这里插入图片描述
在这里插入图片描述


如果对你有帮助,可以点赞收藏呦~

发布了19 篇原创文章 · 获赞 28 · 访问量 1100

猜你喜欢

转载自blog.csdn.net/weixin_44307737/article/details/105725081
今日推荐