JavaEE初阶Day 6:多线程(4)

这篇具有很好参考价值的文章主要介绍了JavaEE初阶Day 6:多线程(4)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Day 6:多线程(4)

前序:针对Day 5结尾的count++

多线程的执行,是随机调度抢占式的执行模式,某个线程执行指令过程中,当它执行到任何一个指令的时候,都有可能被其他线程把它的CPU抢占走

实际并发执行,由于上述原因以及count++本质是CPU的三个指令,两个线程执行指令的相对顺序就可能会存在多种可能,不同的执行顺序,得到的结果就可能会存在差异

1. 线程不安全的原因

(1)线程在系统中是随即调度的抢占式执行的,这是线程不安全的罪魁祸首万恶之源

(2)当前代码中,多个线程同时修改同一个变量

(3)线程针对变量的修改操作,不是“原子”的,count++这种操作不是原子的,是包含了三个指令

(4)内存可见性问题(后续介绍)

(5)指令重排序(后续介绍)

针对上述原因进行问题解决

  • 原因(1)无法干预,属于内核设计,无法改变

  • 原因(2)是一个切入点,但是在Java中,并不普适,针对特定场景可以使用,例如String是不可变对象

    • 一个线程修改同一个变量(ok)
    • 多个线程读取同一个变量(ok)
    • 多个线程修改不同的变量(ok)

    String为不可变对象:很好的保证线程安全;有稳定的哈希值;方便在常量池中缓存

  • 原因(3)是解决线程安全问题最普适的方案,可以通过一些操作,把“非原子”操作,打包成一个“原子”操作,例如:加锁

    如果某个代码操作,对应到一个CPU指令,就是原子的,对应到多个就不是原子的,每个代码最终变成哪些指令,需要对芯片手册(CPU指令集)要有比较深入的理解

2. 锁

:本质上是操作系统提供的功能,内核提供的功能,同过api给应用程序了,Java(JVM)对于这样的系统api又进行了封装(其他的语言,同样也可以封装/调用这样的系统api来完成加锁操作)

锁的操作主要是两个方面

  • 加锁:t1加锁之后,t2也尝试加锁,就会阻塞等待(系统内核控制的),在Java中就能看到BLOCKED状态
  • 解锁:直到t1解锁之后,t2才有可能拿到锁(加锁成功),体现了锁的互斥

锁的主要特性:互斥,一个线程获取到锁之后,另一个线程也尝试加这个锁,就会阻塞等待,也叫做锁竞争/锁冲突

代码中可以创建多个锁,只有多个线程竞争同一把锁,才会产生互斥,针对不同的锁,则不会

3. synchronized

synchronized (locker){
	.......
}
  • synchronizedJava中的关键字,指的是同步的,此处谈到的同步,指的是互斥/独占,反义词可以理解为共享
  • synchronized (locker),()里面就是写的“锁对象”
    • 锁对象的用途,有且只有一个,就是用来区分两个线程是否是针对同一个对象加锁,如果是,就会出现锁竞争/锁冲突/锁互斥,就会引起阻塞等待
    • 和对象具体是什么类型,有什么属性或者方法,没有任何关系
  • {}进入到代码块,就是给上述()锁对象进行了加锁操作,当出了代码块,就是给上述()锁对象进行了解锁操作
package thread;

public class Demo20 {
    private static int count = 0;
    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();
        System.out.println("count = " + count);
    }
}

这两个线程中,每次进行count++是存在锁竞争的,会变成串行执行,但是执行for循环中的条件以及i++,仍然是并发执行的

package thread;


class Counter {
    private int count = 0;
    
    //synchronized修饰普通方法,就相当于针对this加锁了
    public void add() {
        synchronized (this){
            count++;
        }

    }
    //上述方法也可以写成如下形式
    synchronized public void add() {
        count++;

    }


    public int get(){
        return count;
    }
	
    
    //synchronized修饰static方法,相当于针对该类的类对象加锁
    public static void func() {
        synchronized(Counter.class){
            //.....
        }
    }
    
    //上述方法也可以写成如下形式
    synchronized public static void func(){
        //......
    }

}
public class Demo20 {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                counter.add();

                //counter.func();
            }
        });

        Thread t2 = new Thread(() ->{
            for (int i = 0; i < 50000; i++) {
                counter.add();
                //counter.func();
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("count = " + counter.get());
    }
}

synchronized(Counter.class)中的Counter.class反射,即程序运行时,能够拿到类一些属性信息,包括不限于

  • 类的名字,继承自哪个类,实现了哪些interface
  • 类提供了哪些方法,每个方法叫什么,每个方法有什么参数,参数是什么类型
  • 类提供了哪些属性,每个属性叫什么,每个属性是什么类型(public/private…)

上述信息,最初都是程序员自己写的.java源代码中提供的

  • java编译之后,.java形成了.class字节码,上述信息转化为二进制
  • java运行.class字节码,就会读取这里的内容, 加载到内存中,给后续使用这个类,提供基础
  • 所以JVM中在内存里保存上述信息的对象,就是类对象,后续想创建这个类的实例,就需要依照上述信息
  • 在Java中可以通过类名.class来拿到这个类对象,一个java进程中,某个类,只能有唯一一个类对象

所以,一旦多个线程调用func,则这些线程都会触发锁竞争文章来源地址https://www.toymoban.com/news/detail-850513.html

到了这里,关于JavaEE初阶Day 6:多线程(4)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 【JavaEE初阶】 线程安全

    【JavaEE初阶】 线程安全

    线程安全是多线程编程是的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且准确的执行,不会出现数据污染等意外情况。上述是百度百科给出的一个概念解释。换言之,线程安全就是某

    2024年02月08日
    浏览(15)
  • JavaEE初阶:多线程 - 编程

    JavaEE初阶:多线程 - 编程

    我们在之前认识了什么是多进程,今天我们来了解线程。 一个线程就是一个 \\\"执行流\\\". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 \\\"同时\\\" 执行 着多份代码. 引入 进程 这个概念,主要是为了解决并发编程这样的问题。因为cpu进入了多核心的时代,要想进一步

    2024年02月12日
    浏览(12)
  • javaee初阶———多线程(三)

    javaee初阶———多线程(三)

    T04BF 👋专栏: 算法|JAVA|MySQL|C语言 🫵 小比特 大梦想 此篇文章与大家分享多线程专题第三篇,关于 线程安全 方面的内容 如果有不足的或者错误的请您指出! 我们在前面说过,线程之间是抢占式执行的,这样产生的随机性,使得程序的执行顺序变得不一致,就会使得程序产生不同的结

    2024年04月16日
    浏览(15)
  • 【JavaEE初阶】线程的概念与创建

    【JavaEE初阶】线程的概念与创建

    本节目标 认识多线程 创建多线程 Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 一个线程就是一个 “执行流” . 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之

    2024年02月07日
    浏览(14)
  • 【JavaEE初阶】 线程池详解与实现

    【JavaEE初阶】 线程池详解与实现

    线程池,是一种线程的使用模式,它为了降低线程使用中频繁的创建和销毁所带来的资源消耗与代价。 通过创建一定数量的线程,让他们时刻准备就绪等待新任务的到达,而任务执行结束之后再重新回来继续待命。 想象这么一个场景: 在学校附近新开了一家快递店,老板很

    2024年02月06日
    浏览(11)
  • 多线程(JavaEE初阶系列4)

    多线程(JavaEE初阶系列4)

    目录 前言: 1.单例模式 1.1饿汉模式 1.2懒汉模式 1.3结合线程安全下的单例模式 1.4单例模式总结 2.阻塞式队列 2.1什么是阻塞队列 2.2生产者消费者模型 2.2.1 上下游模块之间进行“解耦合” 2.2.2削峰填谷 2.3阻塞队列的实现 结束语: 在上节中小编主要与大家分享了多线程中遇到

    2024年02月15日
    浏览(33)
  • 多线程(JavaEE初阶系列2)

    多线程(JavaEE初阶系列2)

    目录 前言: 1.什么是线程 2.为什么要有线程 3.进程与线程的区别与联系 4.Java的线程和操作系统线程的关系 5.多线程编程示例 6.创建线程 6.1继承Thread类  6.2实现Runnable接口 6.3继承Thread,使用匿名内部类 6.4实现Runnable接口,使用匿名内部类 6.5lambda表达式创建Runnable子类对象 7.

    2024年02月15日
    浏览(12)
  • 【JavaEE初阶】 线程安全的集合类

    【JavaEE初阶】 线程安全的集合类

    原来的集合类, 大部分都不是线程安全的. Vector, Stack, HashTable, 是线程安全的(不建议用), 其他的集合类不是线程安全的. 为什么不建议使用呢? 因为我们在使用的时候,这些类就会自动的加锁,虽然编译器会自动优化为没有锁竞争的线程进行锁消除的优化,但是呢万一编译器没

    2024年02月08日
    浏览(37)
  • 多线程(JavaEE初阶系列7)

    多线程(JavaEE初阶系列7)

    目录 前言: 1.常见的锁策略 1.1乐观锁和悲观锁 1.2轻量级锁和重量级锁 1.3自旋锁和挂起等待锁 1.4互斥锁与读写锁 1.5可重入锁与不可重入锁 1.6公平锁与非公平锁 2.CAS 2.1什么是CAS 2.2自旋锁的实现 2.3原子类 3.synchronized 3.1synchronized的原理以及基本特点 3.2偏向锁 3.3轻量级锁 3.4重

    2024年02月14日
    浏览(29)
  • 【JavaEE初阶】多线程(四)阻塞队列 定时器 线程池

    【JavaEE初阶】多线程(四)阻塞队列 定时器 线程池

    概念 阻塞队列是一种特殊的队列. 也遵守 “ 先进先出 ” 的原则. 阻塞队列能是一种线程安全的数据结构, 并且具有以下特性: 当队列满的时候, 继续入队列就会阻塞, 直到有其他线程从队列中取走元素. 当队列空的时候, 继续出队列也会阻塞,直到有其他线程往队列中插入元素

    2023年04月26日
    浏览(13)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包