多线程-线程安全问题及解决方法

多线程安全问题 在多线程编程中,线程安全问题是比较难,非常重要的问题。不管是在面试过程中还是在实际的工作中,也都是经常出错的地方。因此本篇文章着重介绍一下线程的安全问题。 阅读详情

一.线程安全的概念​:

可以这么认为:如果多线程环境下代码运行的结果是符合我们预期的,就跟在单线程环境应该的结果一样,则说这个程序是线程安全的。

 来看这么一串代码:

public class Demo {

    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        //一个线程自增5w次 按理来说一共10w次
        System.out.println(count);
    }
}

正常来说两个线程都对着个count变量各自增5w次 最后输出的结果应该是10w次

但真正的结果↓

可以看到每一次运行的结果都是随机的都比10w小

这很明显就是因为线程安全问题引起的bug

二.线程不安全的原因:

1.线程调度是随机的

这个是线程安全造成的罪魁祸首,随机调度使一个程序在多线程环境下,执行顺序存在很多的变数,必须保证 在任意执行顺序下 , 代码都能正常工作.

2.修改共享数据

多个线程修改同一个变量

3.修改操作不是原子的

一条 java 语句不一定是原子的,也不一定只是一条指令​
比如刚才我们看到的 count++,其实是由三步操作机器指令组成的:​
1. 从内存把数据读到 CPU​
2. 进行数据更新
3. 把数据写回到 CPU​

以上count++涉及三步机器指令 而不是一条 所以不是原子的 原子指的是一条 不是原子的就会导致一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。 

这里列举两种情况

 情况二👇

 两个线程之间和操作系统 以及JVM的线程都要进行全排列 情况分产多 这里只是简化只有两个线程

 线程安全问题还包括内存可见性问题和指令重排序问题 这里就不先介绍 后面文章会说到 上述代码只涉及前三点

三.Java 内存模型 (JMM):

Java虚拟机规范中定义了Java内存模型. ​

线程之间的共享变量存在 主内存 (Main Memory). ​
每一个线程都有自己的 "工作内存" (Working Memory) . ​
当线程要读取一个共享变量的时候, 会先把变量从主内存拷贝到工作内存, 再从工作内存读取数据. ​
当线程要修改一个共享变量的时候, 也会先修改工作内存中的副本, 再同步回主内存. ​

问题:

1)为啥要整这么多内存? ​

实际并没有这么多 "内存". 这只是 Java 规范中的一个术语, 是属于 "抽象" 的叫法. ​
所谓的 "主内存" 才是真正硬件角度的 "内存". 而所谓的 "工作内存", 则是指 CPU 的寄存器和高速缓存.

2)为啥要这么麻烦的拷来拷去?​

 因为 CPU 访问自身寄存器的速度以及高速缓存的速度, 远远超过访问内存的速度(快了 3 - 4 个数量级, 也就是几千倍, 上万倍). ​

比如某个代码中要连续 10 次读取某个变量的值, 如果 10 次都从内存读, 速度是很慢的. 但是如果只是第一次从内存读, 读到的结果缓存到 CPU 的某个寄存器中, 那么后 9 次读数据就不必直接访问内存了. 效率就大大提高了.

那为啥寄存器都这么快了 还要内存干嘛?

一个字:贵 而且运行速度是相对的内存就已经很快了

 

 通过JMM模型来看上述的情况二数据变化

从图中可以看到执行了两次add 但最后的结果确是1 这就是因为线程安全的导致的bug 

 像这种bug情况由于随机排序 还有很多 

四.解决线程安全的问题

对于1线程的随机调度是操作系统在操控 我们无法修修改 

对于2同时修改一个变量   可以调整代码结构 规避一些不安全的代码 但是这种方案不够通用 有些情况下就是需要多个线程同时修改同一个变量

所以我们只能对3 原子性进行修改解决

 

如何解决?

我们就需要用到synchronized 关键字 - 监视器锁 monitor lock ​

synchronized 的特性​
1) 互斥​
synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待. ​
• 进入 synchronized 修饰的代码块, 相当于 加锁​
• 退出 synchronized 修饰的代码块, 相当于 解锁​

synchronized用的锁是存在Java对象头里的。​ 

对于上述的count++操作我们就可以将其上锁(将三步指令操作)包装起来成为意义上的原子性操作(当一个线程获得锁时,其他线程再获取这个锁时就会阻塞等待 无法继续执行count++ 自然不会打扰到 第一个线程count++的操作)

改正后的代码👇

public class Demo {

    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();

        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                //这里的括号可以是任何对象引用
                //synchronized关键字{}中的代码块就是要在机器语言中锁住执行的机器指令(将多个指令变成打包成一个)具有原子性
                synchronized (locker) {//进入代码块,相当于加锁
                    count++;//执行一些要保护的逻辑
                }//出了代码块,就相当于解锁
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                //两个线程针对同一个对象(locker)加锁才能产生互斥效果 线程安全得到保证
                synchronized (locker) {
                    count++;
                }
            }
        });
        /**
         * 如果t2是后获取锁的
         * t1就已经lock完成了
         * t2的lock就会阻塞t2线程的进行 等到t1执行完unlock之后 t2才会继续执行
         */
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("count: " + count);

    }
}

若让两个线程产生互斥(阻塞)的前提 锁对象必须一样 这里的锁对象可以时java中任意对象(类的实例) 注意事项可以看上述注释

加锁后解决线程安全问题的一种情况 如下

阻塞:

针对每一把锁, 操作系统内部都维护了一个等待队列. 当这个锁被某个线程占有的时候, 其他线程尝试进行加锁, 就加不上了, 就会阻塞等待, 一直等到之前的线程解锁之后, 由操作系统唤醒一个新的线程, 再来获取到这个锁. ​
注意: ​
 1)上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 "唤醒". 这也就是操作系统线程调度的一部分工作. ​
2)假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B 和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则. ​

其他加锁的情况

五.引入锁后也随之带来了一些问题:死锁

1.第一种情况 看下面代码

class Counter {
    private int count = 0;

    public void add() {
        count++;
    }

    public int get() {
        return count;
    }
}
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

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

        thread.start();
        thread.join();

        System.out.println("count= " + counter.get());
    }
}

上述代码中

一个线程没有释放锁, 然后又尝试再次加锁. ​
 第一次加锁, 加锁成功​
第二次加锁, 锁已经被占用, 阻塞等待. ​
按照之前对于锁的设定, 第二次加锁的时候, 就会阻塞等待. 直到第一次的锁被释放, 才能获取到第二个锁. 但是释放第一个锁也是由该线程来完成, 结果这个线程已经躺平了, 啥都不想干了, 也就无法进行解锁操作. 这时候就会 死锁. ​

虽然上述情况会出现死锁 但是java中引入了可重入的概念

当某个线程针对一个锁 加锁成功后,后续该线程再次针对这个锁进行加锁,不会触发阻塞,而是直接往下走 这个锁时当前线程持有,如果其他线程加锁时就会正常阻塞, 可重入锁只是针对 一个线程一把锁 连续加锁多次的情况

可重入锁的实现原理:

关键在于让锁对象内部保存,当前时哪个线程拥有这一把锁,后续有线程针对这个锁加锁的时候对比一下是不是锁持有者的线程是否和当前加锁的线程是同一个,同一个就不会阻塞,不同则会阻塞

对于如何知道在哪一个大括号的时候解锁的

JVM的Monitor先引入一个变量 计数器 每次触发{的时候计数器++ 触发}的时候 计数器-- 当计数器--为0的时候就是真正解锁的时候

那么还有一个问题{ }那么多 计数器怎么判断到底哪一个才是synchronized的{}

其实在JVM视角中看到的是编译后的字节码文件javac ,java代码中看到的是{ } 字节码看到的是不同的指令 { 涉及加锁指令 } 涉及解锁指令 而其他的{ }不会被编译成加锁解锁的指令

第二种情况(真正会构成死锁阻塞的情况)

public class Demo20 {
    public static void main1(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread thread1 = new Thread(() -> {
            //该线程拿到第一把锁 再拿第二把锁的时候,因为第二把锁也因同样的情况不能释放,所以就会产生死锁(不能释放第一把锁)
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);//加上sleep确保thread1拿到locker1
                    //不加sleep很可能thread1一口气就把locker1和locker2都拿到了 这时候t2还没动 自然无法构成死锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("thread1 获取到两个锁");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);//加上sleep确保thread2拿到locker2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker1) {
                    System.out.println("thread2 获取到两个锁");
                }
            }
        });

        thread1.start();
        thread2.start();
    }
}

上述是线程一拿到锁1之后不释放锁1的前提下获取锁2 这时锁2被线程二拿到 线程1被阻塞线程二 也要获取锁1 也会被阻塞 进而导致两个线程都会被阻塞 这就是请求和保持

请求和保持概念:

主要时单个线程的资源请求和占有行为,线程因为新资源请求被阻塞,而自身持有的资源有阻碍了其他线程获取资源,强调的时单个线程对资源的不合理占有和请求

解决方法就是不要去嵌套锁

public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread thread1 = new Thread(() -> {
            //取消嵌套
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);//加上sleep确保thread1拿到locker1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (locker2) {
                System.out.println("thread1 获取到两个锁");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    Thread.sleep(1000);//加上sleep确保thread2拿到locker2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (locker1) {
                System.out.println("thread2 获取到两个锁");
            }
        });

        thread1.start();
        thread2.start();
    }

像这个中不嵌套 让一个线程拿到一个锁之后 就释放再获取另一个锁 这个就能解决请求和保持问题

但是不够通用 有些嵌套不可避免 但是大部分的请求和保持还能用循环等待的解决方法来解决

循环等待问题:

还是看到上面有线程安全问题的代码 多个线程,多把锁之间的等待过程构成了循环 比如:A等待B ,B也在等待A释放锁 或者A等待B , B等待 C, C等待A释放锁 这就构成了循环等待的问题

***解决方法就是约定好加锁的顺序

例如每个线程加锁的时候先获取序号小的锁再获取大的锁
public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread thread1 = new Thread(() -> {
            //都先获取小锁
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);//加上sleep确保thread1拿到locker1
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("thread1 获取到两个锁");
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    Thread.sleep(1000);//加上sleep确保thread2拿到locker2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (locker2) {
                    System.out.println("thread2 获取到两个锁");
                }
            }
        });

        thread1.start();
        thread2.start();
    }

这样也能解决

请求和保持强调的是单个线程在资源获取上的不合理状态

循环等待强调的是多个线程之间的循环依赖的关系

六.死锁的总结

1.构成死锁的场景:

a)一个线程一把锁 -- 可重入

b)两个线程两把锁 

b)M个线程N把锁

2.构成死锁的必要条件

a)锁的互斥(无法改变)

b)锁的不可剥夺(无法改变)

c)请求和保持

d)循环等待

3.如何避免死锁

避免死锁只能打破c 和d

c是把嵌套的锁改为并列的锁

d是约定好加锁的顺序

 

多线程-线程安全 什么是线程安全? 无论遇到怎么样的多个线程访问某对象或某方法的情况,不需要额外做任何的处理。程序也可以正常的运行,不会有因为多线程而出错,因此称为线程安全。 线程不安全:get同时set、额外同步。 全都线程安全? 运行速度、设计成本、tradeoff 什么时候会出现线程安全问题 ... 阅读详情

相关推荐

麻了,代码改成多线程,竟有9大问题

很多时候,我们为了提升接口的性能,会把之前单线程同步执行的代码,改成多线程异步执行。比如:查询用户信息接口,需要返回用户基本信息、积分信息、成长值信息,而用户、积分和成长值,需要调用不同的接口获取数据。如果查询用户信息接口,同步调用三个接口获取数据,会非常耗时。这就非常有必要把三个接口调用,改成异步调用,最后汇总结果。再比如:注册用户接口,该接口主要包含:写用户表,分配权限,配置用户导航页,发通知消息等功能。该用户注册接口包含的业务逻辑比较多,如果在接口中同步执行这些代码,该接口响应时间会非常慢。...

m0_73257876的博客 715

多线程的线程安全

wait notify是 用来控制多线程直接的执行先后顺序的wait 和 notify 都要先进行上锁(synchronized)必须是同一个对象调用wait 和 notify锁对象也要和 调用wait / notify 的对象一致就算没有wait , 直接notify 也是没有副作用的。

m0_60354608的博客 2498

Foxglove Studio实战:5步搞定自动驾驶数据可视化(附避坑指南)

本文详细介绍了如何使用Foxglove Studio高效搭建自动驾驶数据可视化工作流。通过五个核心步骤:环境准备、数据导入、面板搭建、交互分析与自动化集成,帮助开发者快速掌握这一专为机器人系统设计的开源平台。文章重点解析了其与ROS/ROS 2、MCAP等数据源的无缝对接,以及3D点云、多传感器融合、团队标注等核心功能,并附有实用的性能优化与避坑指南,旨在提升算法调试与团队协作效率。

d3e4f的博客 335

多线程——保证线程安全

目录多线程——保证线程安全含义如何保证线程安全具体方法volatile关键字保证可见性禁止重排序synchronized关键字保证原子性防止死锁原因后果检查死锁解决方法 不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵! 多线程——保证线程安全 含义 线程安全:指在多线程对一个共享资源同时进行操作的时候,所得到的结果每次都是一样的。 如何保证线程安全 方法:要保证线程安全,就必须保证线程同步。保证线程的可见性,有序性和原子性。 线程同步 线程同步的含义和字面意义相反。同步

小叮当不懒的博客 9581

Vivado ChipScope Debugging: Overcoming Common DRC Errors in FPGA Design

本文深入解析Vivado ChipScope调试中常见的DRC错误,特别是AVAL-244和AVAL-245等时钟相关问题,提供实用的Tcl命令解决方案和最佳实践。通过将DRC错误降级为警告的技巧,帮助FPGA设计者高效完成调试流程,同时强调何时应保留DRC检查以确保设计可靠性。

weixin_30335575的博客 416

移植 lwip,在 windows 内核构建一个 TCPIP 协议栈

移植 lwip,在 windows 内核构建一个 TCPIP 协议栈 亲,当你点开看这篇文章,就说明你知道我想要干什么事情,或者说,你知道在 Windows 的内核种绕开它的网络协议,再独立构建一个网络协议栈的意义。如果你要问我有 windows 系统的网络协议栈不用,移植 lwip 有什么意义,那么你可以关闭链接走人了,因为这篇文章不适合你,它不是讲 lwip 移植原理的。 经常关注我的博客的朋...

oRbIt 的专栏 3576

【多线程】线程安全问题及解决方法

而load操作时间开销很大,远远高于cmp ,此时CPU有个大胆的操作,将load操作优化了,就导致只有第一次读取flag的值的时候t1线程会从主存中进行读取,之后则在本地内存进行读取,这就会导致当t2线程修改flag的值并同步到主存的时候,t1线程并没有读取到更新的值,这和我们上述提到过的Java内存模型对应,当我们给flag变量加上volatile关键字的时候问题就解决了,这是由于加了volatile关键字的变量,会指示JVM每次读取这个变量的时候都需要从主存中进行读取。

weixin_62848751的博客 1829

Vast.ai GPU服务器租用与SSH密钥连接实战指南

本文详细介绍了如何在Vast.ai平台租用GPU服务器并通过SSH密钥安全连接的全流程。从注册、选配GPU型号到SSH密钥生成与配置,再到实战中的端口转发和文件传输技巧,帮助用户高效利用云端GPU资源进行深度学习等计算密集型任务。

weixin_29574585的博客 526

【多线程】线程安全问题出现的原因以及解决方法

线程安全问题出现的原因以及解决方法

m0_74105656的博客 902

人形机器人硬件开发:骨架系统设计全攻略

本文以人形机器人骨架系统为核心研究对象,系统解析其从理论建模到工程实现的全流程设计方法。通过融合刚体力学、材料科学与机器人控制理论,构建"概念基础-理论框架-架构设计-实现机制-实际应用-高级考量-综合拓展"的七层分析体系。重点覆盖运动学/动力学建模、轻量化结构设计、关节模块集成、多物理场耦合优化等关键技术,结合波士顿动力Atlas、本田ASIMO等经典案例,提供从原型设计到量产验证的工程实践指南。

AI天才研究院 1874

100天精通Python(爬虫篇)——第122天:基于selenium接管已启动的浏览器(反反爬策略)

100天精通Python(爬虫篇)——第122天:基于selenium接管已启动的浏览器(反反爬策略)

努力让自己发光,对的人才能迎着光而来 13万+

多线程之线程安全(synchronized篇):死锁问题及解决方法

线程安全是指多个线程同时访问共享资源时,不会因为线程之间的调度问题导致数据不一致或程序行为异常。为保证线程安全,常常需要同步访问共享资源,以确保同一时间只有一个线程可以操作这些资源。死锁是指两个或多个线程互相等待对方持有的锁,导致所有线程都无法继续执行的情况。简单来说,线程A持有锁1,等待锁2;而线程B持有锁2,等待锁1。此时,这两个线程都无法继续运行,程序进入了僵局。

小蜗牛的珍贵百宝箱 1667

包成功安装tiny-cuda-nn,记录安装过程中的问题解决,附带pytorch3d安装【踩坑指南】

该包可以显著提高NeRF训练速度,是和NeRFstudio等框架中,必须使用的。本文提供tiny-cuda-nn可以安装的完整包的下载链接,可以直接上传到项目中安装。附带笔者遇到的问题的解决方案。

zik的博客 7029

【Java多线程】使用同步方法解决线程安全问题

模仿以上方法套用到使用同步方法处理继承Thread类的方法中的线程安全问题中,在继承Thread类创建多线程的代码中将操作共享数据的代码完整的声明在一个方法。描述:如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法同步的。同步监视器:t1,t2,t3。在show方法中有一个默认的同步监视器“this”在show方法中有一个默认的同步监视器“this”我们可以看到还是出现的错票,这是因为上文提到的。我们将操作共享数据的代码完整的声明在一个方法。我们可以把show方法改成静态的。

qq_64976935的博客 513

cesium 实现经纬线绘制和经纬度标注

废话不对数直接上线效果图及代码 1.效果图 2.实现代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=

一碗老面的博客 2569

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

多线程使用同步方法解决线程安全问题

仍然还是两个线程窗口卖票 当一次任务是在一个方法中完成的,那么我们可以将这个方法封装,即锁这个方法 如下是同步方法的代码; /** * */ package com.yan; /** * @author 雷雨 * */ public class TestThread { /** * @param args */ public static void main(Strin...

小白的程序猿 229

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

多线程创建使用,生命周期,线程安全问题解决,定时器实现,wait,notify方法

线程的创建与启动Thread类有两个常用构造方法Thread()/Thread(Runnable) 继承Thread类,重写run方法 实现Runnable接口,重写run方法----适用于此类有父类的情况下 多线程常用方法 Thread.currentThread()//获取当前线程的线程信息 Thread.currentThread().getName()//获取当前线程的线程名称 myThread.setName("mmmmmmmmmm");//设置此线程名称 this.getId()//获取此

punk的博客 244

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

23、java 多线程之二:同步代码块解决线程安全(方式一)、同步方法解决线程安全(方式二)、两种方式与Thread 类及Runnable 接口混搭使用的利弊和问题(经典案例)

【代码】23、java 多线程之二:同步代码块解决线程安全(方式一)、同步方法解决线程安全(方式二)、两种方式与Thread 类及Runnable 接口混搭使用的利弊和问题(经典案例)

狮子座的男孩 1081

java之多线程(二)——线程安全问题以及解决(同步代码块、同步方法)

线程的生命周期 JDK中Thread.State类定义了线程的几种状态 要想实现多线程,必须在主线程中创建新的线程对象。java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历一下五种状态。 1、新建:当一个Thread类或其子类的对象被声明并创建时,新生的程序对象处于新建状态。 2、就绪:处于新建状态的线程被start()后,将进入线程队列的等待CPU时间片...

weixin_42902126的博客 406

进程、线程,java如何实现多线程任务,Thread里面常用的方法,如何解决线程安全问题,死锁,线程状态,线程池

进程、线程,java如何实现多线程任务,Thread里面常用的方法,如何解决线程安全问题,死锁,线程状态,线程池

weixin_51635918的博客 1166

线程安全问题原因及解决方法(多线程)

线程安全问题原因及解决方法

m0_62464062的博客 884

Java多线程(三):线程安全问题与解决方法

1. 什么是线程安全问题 如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。线程不安全指的是程序在多线程的执行结果不符合预期。 2. 导致线程不安全的因素 2.1 抢占式执行 2.2 多个线程同时修改了同一个变量 public class ThreadDemo16 { static class Counter { // 变量 private int number = 0; ...

澄白易的博客 2293

23 API-多线程(多线程概述,多线程实现方案,线程控制常见方法,线程安全问题及解决)

API-多线程(多线程概述,多线程实现方案,线程控制常见方法,线程安全问题及解决)

飛白的学习笔记 1842

多线程-*同步方法*解决两种线程创建方式的线程安全问题及练习

使用同步方法解决两种线程创建方式的线程安全问题及练习

qq_45761228的博客 160

java语言基础(91)——多线程(同步方法解决线程安全问题)

同步方法解决线程安全问题: package ThreadDemo; public class SellTicket implements Runnable{ private int ticket = 100; public synchronized void run(){ while(true){ if(ticket>0){ try { T

Don't lost way 428

二、多线程学习笔记(实现多线程的四种方式和解决线程安全问题的三种方法)

一、理解程序、进程和线程的含义        1.程序:为完成某种特定任务而用某种语言编写的一组指令的集合,是一段静态的代码。        2.进程:是程序的一次执行过程,或是正在运行的一个程序,有自身的产生、存在和消亡的过程,即存在生命周期。        3.线程:进程进一步细化为线程,是一个程

陈大大的博客 648
上一篇: 数据库--事务
下一篇: 多线程--内存可见性问题以及volatie关键字
刘 大 望
博客等级 码龄2年 896粉丝 61原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值