Java中的多线程编程2

多线程

是一个独立的执行流,解决并发编程这样的问题(虽然进程也能行,但是不如线程更轻量)

上面说的“就绪”和“阻塞”都是针对系统层面上的线程的状态(PCB)

在java中Thread类,对于线程的状态又进一步细化了

  • NEW :安排了工作,还未开始行动

把Thread对象创建好了,但是还没有调用start

  • TERMINATED:工作完成了

操作系统的线程已经执行完毕,销毁了但是Thread对象还在

以上两个状态,是java内部搞出来的状态。就和操作系统PCB没有任何关系

  • RUNNABLE:可工作的,又可分为正在工作和即将开始工作

就绪状态,处在这个状态的线程,就是在就绪队列中,随时可以调用到PCB上,如果代码中没有sleep,也没有其他的可能导致阻塞的操作,代码大概率处在Runable状态

  • TIMED-WAITING:这几个都表示排队等其他事情

代码中调用了sleep就进入到这个状态,join(超时时间)意思就是当前这个线程在一定时间之内,是阻塞状态

  • BLOCKED:这几个都表示排队等其他事情

当前这个线程在等待锁,导致了阻塞(阻塞状态之一)synchronized

  • WAITING:当前这个线程在等待唤醒,导致了阻塞(阻塞状态之一)

为啥要这么细分?开发过程中经常会遇到一种情况,程序“卡死”,一些关键的线程阻塞了在分析卡死原因的时候,第一步就可以先来看看当前程序里的各种关键线程所处的状态

线程状态转换简图:

image-20220919191228441

线程安全问题

整个多线程中最重要,也是最复杂的问题,如果面试官问了线程相关的内容,就一定会问到线程安全,日常开发者中如果用到多线程编程,也一定会涉及到线程安全问题

为什么会有这样的问题?

操作系统在调度线程的时候是随机的(抢占式执行)正是因为这些随机性,可能导致程序的执行出现了一些Bug,如果认为这样的调度随机性引入了bug就认为代码是线程不安全的,如果是因为这样调度的随机性,也没有带来bug,就认为代码是线程安全的(指的是没有bug)

一个线程不安全的典型案例:使用两个线程对同一个整型变量进行自增操作,每个线程自增5w次看最后的结果

class Count {
    public int count = 1;
    public void increase() {
        this.count ++;
    }
}

public class Main {
    private static Count counter = new Count();
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for(int i = 0; i < 50000; i ++) {
                counter.increase();
            }
        });

        Thread thread1 = new Thread(() -> {
            for(int x = 0; x < 50000; x ++) {
                counter.increase();
            }
        });

        thread.start();
        thread1.start();


        System.out.println(counter.count);
    }
}

count ++ 到底干了什么,站在CPU的角度是三个指令

image-20220919193138371

正因为“抢占式执行”,这就导致了两个线程同时执行这三个指令的时候,顺序上充满了随机性

image-20220919193753700

image-20220919193949366

多线程,线程安全问题,如何解决上述问题,加锁!

java中加锁的方式有很多,最常用的就是synchronized,这样的关键字(当一个线程加锁成功的时候,其他线程就会尝试加锁,就会触发阻塞等待,此时对应的线程就会处在BLOCKED状态),阻塞会一直持续到,占用锁的线程把锁释放为止

啥样的代码会产生这样线程不安全的问题呢?

不是所有的线程都要加锁(如果这样了,多线程的并发能力就形同虚设)

产生线程不安全的原因

1. 线程是抢占式执行,线程之间的调度充满随机性,【线程不安全的万恶之源】

2. 多个线程对同一个变量进行操作的时候(如果多个线程针对不同的变量进行修改没事,如果多个线程针对同一个线程进行读,也没事)

3. 针对变量的操作不是原子的,针对有些操作,比如:读变量的值,只是一条机器指令,此时这样的操作本身就可以视为原子,通过加锁操作也就把好几个指令打包成一个原子的

4. 内存可见性,也会影响到线程安全。一个具体的例子:针对同一个变量,一个线程进行读操作(循环进行很多次)一个线程进行修改操作(合适的时候执行一次)

5. 指令重排序,也会影响到线程安全问题

image-20220919213032160

synchronized的使用方法

  1. 直接修饰普通的方法。(当两个线程同时尝试对同一个对象进行加锁的时候,才有竞争如果是两个线程在针对不同对象加锁,就没有竞争
  2. 修饰一个代码块。需要显示指定针对那个对象加锁(java中的任意对象都可以作为锁对象
  3. 修饰一个正常方法。相当于针对当前类对象加锁

image-20220919224003798
加锁,就没有竞争
2. 修饰一个代码块。需要显示指定针对那个对象加锁(java中的任意对象都可以作为锁对象
3. 修饰一个正常方法。相当于针对当前类对象加锁

[外链图片转存中…(img-H7iJ26pb-1721024434413)]

  • 16
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ζ◇十点半就睡觉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值