• Java多线程


    Java多线程

    作者:Grey

    原文地址:

    Github

    语雀

    博客园

    线程和进程的关系

    线程就是轻量级进程,是程序执行的最小单位。

    多进程的方式也可以实现并发,为什么我们要使用多线程?

    1. 共享资源在线程间的通信比较容易。
    2. 线程开销更小。

    进程和线程的区别?

    • 进程是一个独立的运行环境,而线程是在进程中执行的一个任务。他们两个本质的区别是是否单独占有内存地址空间及其它系统资源(比如I/O):
    • 进程单独占有一定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不干扰;而线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。
    • 进程单独占有一定的内存地址空间,一个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性高;一个线程崩溃可能影响整个程序的稳定性,可靠性较低。
    • 进程单独占有一定的内存地址空间,进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收以及页调度,开销较大;线程只需要保存寄存器和栈信息,开销较小。
    • 进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位,即CPU分配时间的单位 。

    创建线程

    有三种方式

    1. 继承Thread类,重写run方法
    2. 实现Runnable接口,实现run方法
    3. 通过线程池创建
    4. 通过Callable/Future创建(需要返回值的时候)

    线程基本操作

    sleep

    当前线程睡一段时间

    yield

    这是一个静态方法,一旦执行,它会使当前线程让出一下CPU。但要注意,让出CPU并不表示当前线程不执行了。当前线程在让出CPU后,还会进行CPU资源的争夺,但是是否能够再次被分配到就不一定了。

    join

    等待另外一个线程的结束,当前线程才会运行

    public class ThreadJoin {
        volatile static int i = 0; 
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() -> {
                for (i = 0; i < 10000; i++) {
                }
            });
            thread.start();
            // join 方法表示主线程愿意等待子线程执行完毕后才继续执行
            // 如果不使用join方法,那么i输出的可能是一个很小的值,因为还没等子线程
            // 执行完毕后,主线程就已经执行了打印i的操作
            thread.join();
            System.out.println(i);
        }
    }
    

    线程状态切换

    CAS

    比较与交换的意思

    举个例子:

    内存有个值是3,如果用Java通过多线程去访问这个数,每个线程都要把这个值+1,之前是需要加锁,即synchronized关键字来控制,但是JUC的包出现后,有了CAS操作,可以不需要加锁来处理,流程是:

    第一个线程:把3拿过来,线程本地区域做计算+1,然后把4写回去,

    第二个线程:也把3这个数拿过来,线程本地区域做计算+1后,在回写回去的时候,会做一次比较,如果原来的值还是3,那么说明这个值之前没有被打扰过,就可以把4写回去,如果这个值变了,假设变为了4,那么说明这个值已经被其他线程修改过了,那么第二个线程需要重新执行一次,即把最新的4拿过来继续计算,回写回去的时候,继续做比较,如果内存中的值依然是4,说明没有其他线程处理过,第二个线程就可以把5回写回去了。

    流程图如下:

    CAS

    ABA问题

    CAS会出现一个ABA的问题,即在一个线程回写值的时候,其他线程其实动过那个原始值,只不过其他线程操作后这个值依然是原始值。

    如何来解决ABA问题呢?

    我们可以通过版本号或者时间戳来控制,比如数据原始的版本是1.0,处理后,我们把这个数据的版本改成变成2.0版本, 时间戳来控制也一样,

    以Java为例,AtomicStampedReference这个类,它内部不仅维护了对象值,还维护了一个时间戳。

    当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新时间戳。

    当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值,写入才会成功。

    因此,即使对象值被反复读写,写回原值,只要时间戳发生变化,就能防止不恰当的写入。

    Note:

    CAS的底层实现

    Unsafe.cpp-->Atom::cmpxchg--Atomic_linux_x86_inline.hpp-->调用了汇编的LOCK_IF_MP方法

    Multiple_processor

    lock cmpxchg

    虽然cmpxchg指令不是原子的,但是加了lock指令后,则cmpxhg被上锁,不允许被打断。在多核CPU中,必须加lock

    使用CAS好处

    jdk早期是重量级别锁 ,通过0x80中断 进行用户态和内核态转换,所以效率比较低,有了CAS操作,大大提升了效率。

    对象的内存布局

    这里说到的对象内存布局和具体的虚拟机实现是有关系的,我们讨论的是Hotspot的实现。

    参考资料

    [多线程与高并发-马士兵]

    实战Java高并发程序设计(第2版)

    深入浅出Java多线程

    待续

  • 相关阅读:
    NFC读写电子便签总结
    对字符串md5加密
    把ArrayList集合中的字符串内容写到文本文件中
    【原创】关于jquery实现格式化时间
    jQuery插件之ajaxFileUpload
    jxl读取excel实现导入excel写入数据库
    jxl写入excel实现数据导出功能
    多个Jar包的合并操作
    基于git的源代码管理模型——git flow
    Google Gson 使用简介
  • 原文地址:https://www.cnblogs.com/greyzeng/p/14176141.html
Copyright © 2020-2023  润新知