Java多线程第二篇
Java多线程第二篇
1.线程终止
1.1通过成员对线程进行终止
代码语言:javascript代码运行次数:0运行复制变量创建需要以static修饰并且变量成员需要以final修饰或者非final修饰(不变的常量)才能进入到run方法中,但是如果你想终止的话,你一定要修改它的值,所以我们必须要定义一个外部成员。
public class Demo7 {
//这里的成员默认false
private static boolean isQuit;
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
//不为true进入循环
while(!isQuit){
println("Thread is Running");
try {
//thread线程睡眠1000
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//这里跳出循环后打印
println("Thread closing");
});
thread.start();
//这里进入start后线程和主线程同时运行,程序继续往下走
//这里sleep睡眠5000ms为条件,达成条件后继续往下执行
Thread.sleep(5000);
isQuit=true;
//这里睡眠2000在执行下面代码,这时候打印为TERMIATED
Thread.sleep(2000);
println(thread.getState());
}
}
1.2通过Thread本地方法终止
代码语言:javascript代码运行次数:0运行复制通过上述我们明白如果我们创建,可以看到缺点。 1.需要手动创建变量。 2.当线程内部在sleep的时候,主线程修改为ture,这时候结束了循环,但是新的线程内部无法及时的做出响应。 这时候我们就要通过Java中的本地方法来进行线程的中断。
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
//这里的currentThread方法可以获得当前线程的实例,但是无法直接写成thread,这时候我们的thread还没有构造完成。
//isInterrupted是标志位置,判断线程是否结束。
//isInterrupted 默认为false
while(!().isInterrupted()){
println("Thread is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
println("结束线程");
});
thread.start();
Thread.sleep(000);
//这里修改为true,循环会结束?
thread.interrupt();
Thread.sleep(2000);
println("Thread exit");
}
}
这里我们可以看到线程还是在执行,为什么呢? 正常来说sleep会进入休眠,此处给到interrupt改为true之后就可以使用sleep内部触发一个异常,提前被唤醒,清除该实例的标志位。 这里清除该实例标志位后如果不使用break或者throw出异常则会继续执行,可以将选择权限交给开发者进行选择,我们可以通过break来跳出或者直接抛出。
- 以下修改的部分
while (!().isInterrupted()) {
println("Thread is working");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException();
//或者直接通过上述代码的打印异常增加一个break,break前可以增加一些想要添加的代码。
//break;
}
}
});
2.线程的等待join
join 是实现线程等待的效果,在主线程调用thread.join(),此时是主线程等待thread线程先结束。 让一个线程,等待另一个线程执行结束,在继续执行,本质上就是控制线程结束的顺序的方式。
2.1不增加参数
代码语言:javascript代码运行次数:0运行复制public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread thread =new Thread(()->{
while(!().isInterrupted()){
println("Thread is running:1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
});
thread.start();
Thread.sleep(000);
thread.interrupt();
//这里的join就是阻断,让其执行完成后在继续执行其他线程
thread.join();
for(int i=0;i<;i++){
println("Thread is running:2");
Thread.sleep(1000);
}
}
}
2.2增加参数
代码语言:javascript代码运行次数:0运行复制当增加参数后我们会限定join的等待时长,如果超过该等待时长则继续执行
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i=0;i<10;i++) {
println("Thread is running:1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
});
thread.start();
// 这里等待thread执行完成则主线程开始执行
// 如果join带参数后,则是需要等待的时间,如果等待时间过长我们就需要将主线程继续进行
thread.join(000);
for(int i=0;i<;i++){
println("Thread is running:2");
}
}
}
2.线程的休眠时间
代码语言:javascript代码运行次数:0运行复制线程的sleep是有误差的,因为线程的调度开销也是需要时间,所以可能不是很准确,比较随机。
public static void main(String[] args) throws InterruptedException {
long start = ();
Thread.sleep(1000);
long end = ();
println("开始和结束的时差(以ms为单位):"+(end-start));
}
线程状态 | 说明 |
---|---|
EW | THREAD的对象已经存在,start方法还没调用,没有创建新的线程 |
TERMIATED | THREAD的对象仍然存在,内核中的线程已经销毁 |
RUABLE | 就绪状态(线程已经在cpu上执行了/线程正在排队等待在cpu执行) |
TIMED_WAITIG | 阻塞状态,由于sleep这种固定时间的方式产生的阻塞 |
WAITIG | 阻塞状态,由于wait这种不固定时间的方式产生的阻塞 |
BLOCKED | 阻塞状态,由于锁竞争导致的阻塞 |
1.EW状态
public static void main(String[] args)throws InterruptedException {
Thread thread=new Thread(()->{
});
//在调用start之前获取的状态,此时是EW状态
println(thread.getState());
}
2.TERMIATED状态
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
});
thread.start();
thread.join();
//这里start开始创建运行线程,当线程结束后(因为join对其阻塞)这时候线程销毁状态为TERMIATED
println(thread.getState());
}
. RUABLE状态
public static void main(String[] args) {
Thread thread=new Thread(()->{
});
thread.start();
//这里start已经创建好线程正在就绪,是RUABLE状态
println(thread.getState());
}
4.TIMED_WAITIG状态
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while (true){
try {
Thread.sleep(800);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
while(true){
//通过thread线程的进行sleep阻塞的状态sleep是TIMED_WAITIG状态
Thread.sleep(1000);
println(thread.getState());
}
}
线程安全是什么?
线程安全是当一段代码在单个线程中跑的时候,不会出现问题。 但是如果放到了多个线程中则出现问题(bug),我们把这种叫做“线程的安全问题”或者是“线程不安全”。
.1多线程执行
代码语言:javascript代码运行次数:0运行复制假设定义一个成员变量count默认为0,通过两个线程将count存储至十万数值。
private static int count;
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
for(int i=0;i<50000;i++){
count++;
}
});
Thread t2=new Thread(()->{
for(int i=0;i<50000;i++){
count++;
}
});
t1.start();
//如果t1和t2start同时执行,没有join的约束则会出现bug
t1.join();
//线程两个如果并发开始进行则出现bug,我们的实际结果输出十万
t2.start();
t2.join();
println(count);
}
这里的count每次自增的操作本质是分成三步进行(站在cpu的角度通过三个指令进行实现)⬇️
1.load:
将数据从内存中,读到cpu寄存器中。2.add:
将寄存器的数据进行+1自增。.save:
把寄存器中的数据,保存到内存中。
如果多个线程执行上述的代码,因为线程的调度顺序是“随机”的,因在count++中会执行次而且三个指令的cpu指令顺序不同,就会导致在有些调度顺序下,并发程度高,上述逻辑就会出现问题。
通过上述可以意识到,在多线程中,在随机调度的时候,多个线程之间存在诸多可能的先后顺序,我们必须要在保证所有可能的情况下,代码是正确的。
.2线程加锁
代码语言:javascript代码运行次数:0运行复制使用线程加锁来避免解决线程的冲突。 synchronized 在使用的时候,要搭配一个代码块{} 进入{就会加锁 出了}就会解锁,这里synchronized()中需要表示一个用来加锁的对象,这个对象是谁不重要,重要的是通过这个对象来区分两个线程是否在竞争同一块锁。 在已经加锁的状态中,另一个线程尝试同样的加锁,就会产生“锁冲突/锁竞争”,后一个线程就会阻塞等待,一直到前一个线程解锁为止。
public class Test {
private static int count;
public static void main(String[] args) throws InterruptedException {
//这里是上锁,前提是线程公用一个锁!!
Object locker =new Object();
Thread t1=new Thread(()->{
for(int i=0;i<50000;i++){
synchronized (locker){
count++;
}
}
});
Thread t2=new Thread(()->{
for(int i=0;i<50000;i++){
synchronized(locker){
count++;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
println(count);
}
}
synchronized重要的特性,在Java中针对一个对象加多个锁是可重入的。 所谓的可重入锁,指的是一个线程,一把锁,加锁两次,会出现死锁,就是“不可重入“,反之,就是”可重入“。
.2.1死锁
在多个线程中,如果是有把锁(嵌套锁),无论是否可重入,都会进入死锁状态。
代码语言:javascript代码运行次数:0运行复制public class Test {
private static final Object locker1=new Object();
private static final Object locker2=new Object();
public static void main(String[] args) {
Thread t1=new Thread(()->{
synchronized(locker1){
println("hello t1:1");
synchronized (locker2){
println("hello t1:2");
}
}
});
Thread t2=new Thread(()->{
synchronized(locker2){
println("hello t2:2");
synchronized (locker1){
println("hello t2:1");
}
}
});
t1.start();
t2.start();
}
}
.2.2哲学家进餐
这里的锁可以以下列来举例,哲学家进餐。 注意:这里的叉子相当于筷子。 这里每个哲学家都有一份面并且左右两边都有一副叉子,每一个哲学家必须使用两副叉子才能进行吃饭,叉子在同一时间只能被同一个人使用 ,如果哲学家没有拿到两副叉子,则无法吃面。 这里哲学家的状态只有两种:一种是吃面,一种是思考(等待)。
.2.并行算法
这里如果哲学家在吃饭和思考的状态下切换,这里如果哲学家吃饭和思考都是任意时间段,如果每个哲学家饿的时间和思考的时间不固定,则即可交替执行。
这里如果一个哲学家想要拿这个叉子,而另一个哲学家也想拿,这时候则为死锁,两个哲学家都吃不到面。
.2.4死锁的达成条件
1.互斥使用:(锁的基本特性)当一个线程中持有着一把锁,另一个线程也想要获取锁,这时候就要阻塞等待。 2.不可抢占:(锁的基本特性)当锁已经被线程1拿到之后,线程2只能等线程1主动释放掉,不能强占。 .请求保持:(代码的结构)一个线程中如果获取多把锁(当获取第一把锁之后在获取第二把锁,在获取的过程中,锁1不会被释放。) 4.循环等待/环路等待:等待的依赖关系,形成循环。 如果有两把锁设a、b,线程1获取到锁a,然后线程2也获取到锁b,这时候线程1嵌套获取b锁,线程2嵌套获取a锁,则循环等待锁释放,进不去出不来。 例如:你此时在车库,打不开车库门,这时候车钥匙在家,你这时候给你你老婆打电话,让他回家帮你拿钥匙,而你老婆这时候说家的钥匙我落到车库啦~ 。这时候就陷入一种嵌套循环,形成死锁。 ⬆️以上为死锁的四个条件,尽量避免锁嵌套结构!
.volatile关键字
- 保证内存的可见性。
- 禁止指令重排序。
代码语言:javascript代码运行次数:0运行复制计算机在运行过程中需要访问数据,这些数据会放在内存中(定义一个变量时,变量就是在内存中)。 cpu在使用这个变量时,就会把这些内存中的数据读取出来,放到cpu的寄存器中参与运算(load读取内存值放到寄存器中)。 寄存器读取速度>内存读取速度>硬盘读取运算。 为了解决上述的问题,这时候编译器可能对代码进行优化,把一些本来要读取内存的操作,优化成读取寄存器,减少读取内存的次数来提高代码的整体效率
public class Test {
private static int isQuit=0;
public static void main(String[] args) {
Thread thread1=new Thread(()->{
//循环没有内容,但是也会循环许多次
while(isQuit==0){
}
println("isQuit");
});
thread1.start();
Thread thread2=new Thread(()->{
//用户通过第二个线程输入值不为0,则循环会结束?
Scanner scanner=new Scanner(System.in);
isQuit=();
});
thread2.start();
}
}
这时候一个线程读和一个线程写,仍然在执行,此时出现bug(引起线程安全问题),此时就是因为内存可见性引起。 1.load读取内存中的isQuit值放入寄存器中。 2.通过cmp指令比较寄存器中的值是否为0,绝对是否要继续循环。 此时会发生大量的循环,大量load和cmp操作,这时候编译器/JVM发现load出来的结果isQuit都是相同的,并且load读取内存到寄存器中很花费时间(1次load的时间相当于上万次的cmp)。 此时编译器则为了更方便的操作,只有第一次循环时读取内存器,后续不在读取内存,而是直接从寄存器中取isQuit的值,此时内存中无法读取数据编译器却没有进行读取,而是一直在寄存器中执行,此时就出现了bug。
- 方案1
代码语言:javascript代码运行次数:0运行复制上述方案则因在寄存器中读取快,但是算的不准。 而volatile关键词来解决此方案,而volatile则是让JVM继续读取内存中的数据然后通过寄存器在逐个比较,解决此方案。
public class Test {
private volatile static int isQuit=0;
public static void main(String[] args) {
Thread thread1=new Thread(()->{
while(isQuit==0){
//循环没有内容,但是也会循环许多次
}
println("isQuit");
});
thread1.start();
Thread thread2=new Thread(()->{
//用户通过第二个线程输入值不为0,则循环会结束?
Scanner scanner=new Scanner(System.in);
isQuit=();
});
thread2.start();
}
}
- 方案2
代码语言:javascript代码运行次数:0运行复制我们可以减少读取的次数,通过sleep睡眠来限定load的读取次数,此时就不会出发内存可见性问题,但是什么时候代码会被JVM优化,我们人是不清楚的,只有机器清除,所以我们尽量使用volatile更保险一些,此方法知道即可。
public class Test {
private static int isQuit=0;
public static void main(String[] args) {
Thread thread1=new Thread(()->{
while(isQuit==0){
//循环没有内容,但是也会循环许多次
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
println("isQuit");
});
thread1.start();
Thread thread2=new Thread(()->{
//用户通过第二个线程输入值不为0,则循环会结束?
Scanner scanner=new Scanner(System.in);
isQuit=();
});
thread2.start();
}
.4 wait和notify
代码语言:javascript代码运行次数:0运行复制wait和notify是用来协调多个线程中的执行顺序,本身多个线程的执行顺序是随机的,很多的时候,需要通过一些手段来对线程进行干预,前面说过join也是一种干预,但只是影响了线程的结束的先后顺序,很多时候,我们希望线程不要结束,也要进行干预形成先后顺序。
wait(不带参数): 让指定的线程进入阻塞状态,且无notify则处于一直阻塞状态,代码无法向下执行。 wait(带参数):指定线程的时间,超出时间则向下继续执行代码。 notify: 唤醒对应阻塞状态的线程。 notifyAll:唤醒所有处于阻塞状态的线程。
public class Test {
public static void main(String[] args) {
Object obj=new Object();
Thread t1=new Thread(()->{
synchronized(obj){
try {
println("wait等待1");
obj.wait();
println("wait1结束");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread t=new Thread(()->{
synchronized(obj){
try {
println("wait等待2");
obj.wait();
println("wait2结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2=new Thread(()->{
try {
Thread.sleep(000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (obj){
();
println("通知");
}
});
t1.start();
t2.start();
t.start();
}
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。 原始发表:2024-12-2,如有侵权请联系 cloudcommunity@tencent 删除多线程内存线程线程安全java #感谢您对电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格的认可,转载请说明来源于"电脑配置推荐网 - 最新i3 i5 i7组装电脑配置单推荐报价格
上一篇:Java多线程第三篇
下一篇:Java多线程第一篇
推荐阅读
留言与评论(共有 15 条评论) |
本站网友 吞金是什么意思 | 27分钟前 发表 |
形成死锁 | |
本站网友 叶舒 | 2分钟前 发表 |
另一个线程也想要获取锁 | |
本站网友 杭州动漫学校 | 28分钟前 发表 |
让他回家帮你拿钥匙 | |
本站网友 金鱼精 | 26分钟前 发表 |
我们希望线程不要结束 | |
本站网友 光刻工艺 | 9分钟前 发表 |
让他回家帮你拿钥匙 | |
本站网友 连江租房网 | 12分钟前 发表 |
但是如果你想终止的话 | |
本站网友 安阳电影院 | 27分钟前 发表 |
需要通过一些手段来对线程进行干预 | |
本站网友 嘉佑 | 24分钟前 发表 |
需要通过一些手段来对线程进行干预 | |
本站网友 360桌面壁纸 | 27分钟前 发表 |
很多的时候 | |
本站网友 photoshop视频教程下载 | 3分钟前 发表 |
也要进行干预形成先后顺序 | |
本站网友 杭州不孕不育 | 14分钟前 发表 |
只有第一次循环时读取内存器 | |
本站网友 陶瓷洁具品牌 | 15分钟前 发表 |
为了解决上述的问题 | |
本站网友 冒险岛sf发布 | 17分钟前 发表 |
wait(带参数):指定线程的时间 | |
本站网友 北京今日空气质量 | 26分钟前 发表 |
需要通过一些手段来对线程进行干预 |