一文看尽Java-多线程概念

简介: ava-多线程概念

一、前言


   主要讲解一下多线程中的一些概念,本文之后就开始针对JUC包的设计开始解读;


二、概念


   线程安全

   1.存在共享数据(临界资源);2.多个线程同时操作共享数据;只有同时出现这两种情况的时候才会造成线程安全问题;

   解决线程安全

   同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据以后在对共享数据进行操作;

   多线程特性

   原子性

   现在的操作系统主要是通过时间分片的形式来管理线程或者进程,Java编程语言一句语言需要多条CPU指令来完成,Java在多线程切换的时候由于不满足原子性的特征,导致共享变量产生意料之外的结果;典型的count+=1,如下图,共享变量的count的指最终结果是1而不是2;

  1005447-20190908210105040-550255983.png

   可见性

   在多处理器(CPU)系统中,每个处理器都有自己的高速缓存,而它们又共享同一主内存,整体结构如下图:

   1005447-20190908211732045-765161641.png

   在单核CPU的情况下,CPU缓存与内存数据一致性问题容易处理,因为所有线程的操作都是针对同一个CPU的缓存,一个线程对缓存的写对于另外的线程一定是可见的,整体的执行情况如下图:

   1005447-20190908212216876-1922987092.png

   在多CPU的情况下,每个CPU都有自己的缓存,这个时候每个共享变量在CPU中的缓存都是不可见的,这个时候就产生了CPU缓存与内存数据一致性的问题,整体执行的情况如下图,由于count变量分别在不同的CPU上执行,相互看不到对方的操作,这个时候变量count就会不一致,产生意料之外的结果,针对这种我也写了一个demo;

  1005447-20190908213504419-739530906.png

   有序性

   编译器为了优化性能,有的时候会改变程序中语句的执行顺序,在Java经典的双重检查创建单例模式,就是其中的一个体现,代码如下图:

/**
 * @author wtz
 * <p>
 * 双重锁定单例模式 指令重排
 */
public class Singleton {
    private static Singleton singleton;
    private Singleton() {
    }
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

   代码整体上看起来无任何瑕疵,但是实际这个方法并不完美,问题出在new的操作上,正常情况下new Singleton()执行的操作如下步骤:

   1.在内存中分配一块空间;

   2.在内存上初始化Singleton对象;

   3.将内存地址赋值给singleton变量;

   经过编译器优化以后可能是这个样子:  

   1.在内存中分配一块空间;

   2.将内存地址赋值给singleton变量;

   3.在内存上初始化Singleton对象;

   优化以后当CPU时间片切换时间刚好是线程B判断为空的时候,这个时候singleton此时不为空,不需要进入锁中,这个时候就返回为初始化的singleton,整体性执行过程如下图:

  1005447-20190908215816413-644391312.png

   

   竞态条件&临界区

   当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区;

   互斥锁

   互斥锁解决了并发程序中的原子性问题,保证同一时刻只有一个线程执行,保证了一个或多个操作在CPU执行的过程中不被中断,Java原生语言主要是通过synchronized实现互斥锁;

   Java内存模型

   Java内存模型主要是为了解决内存可见性的问题,使用内存模型约束了CPU缓存和编译优化;Java内存模型(JMM)从不同的角度来说都可以说很多的东西,比如从线程角度来说,JMM规范不同线程之间线程通信的问题,从操作系统的角度来说,JMM规范了工作线程与内存之间访问的问题;我们主要从程序员角度来看这个问题的话我认为可以从三方面说起:

   1.volatile、synchronized和final语义;

   2.JUC并发包;

   3.happens-before;

   我们主要说第3点,第1,2点以后在补充,我们先要明白一些概念,才能更好的理解后面的一些内容;happens-before主要有8个原则,我们通俗的话来讲讲:

   1.程序的顺序性,单线程的每个前面的操作优先于后面的操作;

   2.volatile,对于volatile修饰的变量,写的操作一定优先于读的操作,也就是说对变量写操作对于后续的读操作都是可见的;

   3.锁,解锁的操作优先于加锁的操作,在Java锁指的就是synchronized,变量在解锁之前的操作,在重新加锁之后一定可以看到;

   4.传递性,A优先于B,B优先于C,则A优先于C;

   5.线程开始原则,主线程A启动子线程B,则子线程B能够看到主线程A在启动B子线程之前的操作;

   6.线程终止原则,主线程A等待子线程B完成,当子线程B完成以后,主线程A能够看到子线程的B的操作;

   7.线程中断原则,对线程interrupt()方法优先于发生被中断线程检测到中断事件的发生;

   8.对象终结规则,构造函数的执行一定优先于它的finalize方法;

   等待-通知机制

   等待-通知机制主要是为了处理循环等待造成的CPU消耗问题,主要有以下两个步骤:

   1.线程首先获取互斥锁,当线程要求的条件不满足时,释放互斥锁,进入等待状态;

   2.当要求的条件满足时,通知等待的线程,重新获取互斥锁;

   Java原生语言主要是通过synchronized + wait + notify/notifyAll实现;

   活跃性

   死锁

   死锁的定义一组相互竞争资源的线程因相互等待,导致永久阻塞的现象,发生死锁必备的四个条件:

   1.互斥,共享资源同时只有占用一个线程;

   2.占有且等待,线程A获取共享资源X,在等待共享资源Y的时候,不是释放共享资源Y;

   3.不可抢占,其他线程不能抢占线程A获取的共享资源;

   4.循环等待,线程A等待线程B获取的共享资源,线程B等待线程A获取的共享资源;

   只要破坏其中任意一个条件就可以跑坏死锁;

   活锁

   线程之间互相谦让,导致线程无法执行下去,解决方案通过给线程随机等待一个时间;

   饥饿

   线程不能正常的访问共享资源,并且无法执行下去,解决线程饥饿的办法:

   1.保证资源的公平性,也就线程的优先级一样;

   2.保证资源充足;

   3.避免线程长时间占用锁执行;

目录
打赏
0
0
0
0
28
分享
相关文章
|
2天前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
88 60
【Java并发】【线程池】带你从0-1入门线程池
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
72 14
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
48 13
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
Java字面量详解:概念、分类与使用实例
本文介绍了Java字面量的概念、分类及应用。
54 11
|
1月前
|
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
117 17
|
2月前
|
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
Java 多线程 面试题
Java 多线程 相关基础面试题
Java的基础概念(二)
本文介绍了Java编程语言中的运算符和表达式,涵盖算术运算符、赋值运算符、关系运算符、逻辑运算符、三元运算符等。重点讲解了算术运算符的使用,如加减乘除取余,并强调了整数除法和取余的特殊性。同时,详细说明了隐式转换与强制转换的概念及应用场景,以及字符串和字符的拼接规则。通过多个案例演示了不同运算符的实际应用,包括数值拆分、自增自减、三元表达式的使用等。最后简要提及了运算符的优先级,指出小括号具有最高优先级。

热门文章

最新文章