Java 并发编程系列之带你了解多线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/codejas/article/details/82761928

早期的计算机不包含操作系统,它们从头到尾执行一个程序,这个程序可以访问计算机中的所有资源。在这种情况下,每次都只能运行一个程序,对于昂贵的计算机资源来说是一种严重的浪费。

操作系统出现后,计算机可以运行多个程序,不同的程序在单独的进程中运行。操作系统负责为各个独立的进程分配各种资源。并且不同的进程间可以通过一些通信机制来交换数据,比如:套接字、信号处理器、共享内存、信号量等。

一、了解多线程

1.1 进程与线程

想必大家都听说过这两个名词,它们之间有什么联系与不同呢?

记得当时上操作系统课时,书上有这么一句话:进程是独立拥有 cpu 资源的最 小单位,线程是接受 cpu 调度的最小单位。网上看了那么多的解释,还是觉得书上说的最简单明了。

进程中可以同时存在多个程序控制流程的线程,线程会共享进程范围内的资源,因此一个进程可以有多个线程。打个比方,进程就像一个大工厂,而线程就是工厂的工人,多个工人负责协同完成工厂的任务。

1.2 多线程的优势

  • 发挥多处理器的强大能力
  • 简化建模过程
  • 简化异步事件处理机制
  • 响应更灵敏的用户界面

1.3 多线程安全性问题

多线程是一把双刃剑,使用多线程时意味着对开发人员有一定的技术要求。可见学会了多线程技术,就能写出更优雅的代码了,哈哈~

多个线程同时访问意味着“共享”变量,这些“共享”变量往往在其生命周期内是可变的,这样以来就极易出现多线程安全性问题。

什么是线程安全,这里给一个比较准确的定义:当多个线程访问某各类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。

二、Java 内存模型

为什么要在多线程专题里涉及到 Java 内存模型呢?我觉得它可以帮助我们更好的理解多线程的工作机制。

比如很多人都听说过或了解过 volatile 关键字,都知道它能保证内存可见性问题,但是理解起来总是太过抽象。如果你了解了 Java 内存模型,它可以很好的帮助你理解这些问题。
在这里插入图片描述
Java 内存模型规定了所有的变量都存储在主内存中(Main Memory)中(可以类比为物理硬件的主内存)。每条线程都有自己的工作内存(Working Memory),线程的工作内存中保存了主内存中的变量的拷贝,线程对变量的所有操作(读取、赋值)都必须在自己的工作内存中进行,而不能直接读写主内存中的变量。

不同的线程之间无法访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

如果你对 Java 内存区域了解的话,很容易就会想工作内存、主内存与 Java 内存区域中的堆、栈、方法区是否有一定关系呢?

如果在实例变量的角度来看,主内存对应于 Java 堆中的对象实例数据部分,而工作内存则对应于虚拟机栈中的部分区域。从更低层次来看,主内存直接对应于物理硬件的内存。

三、解决线程安全性问题

使用 Java 创建线程的几种方式这里就不再赘述了,我们来了解几种处理多线程安全性问题的方法。

3.1 synchronized 关键字

Java 提供了一种内置的锁机制(synchronized 关键字)来保证原子性与内存可见性。关于原子性与内存可见性问题会在讲述 volatile 关键字时详细的介绍,感兴趣的可以关注后面的文章。

Java 的内置锁是一种互斥锁,意味着最多只有一个线程能持有这种锁。当线程 A 和线程 B 同时访问临界资源,如果线程 A 获取锁,线程 B 就必须等待或阻塞,A 不释放 B 就只能等待下去。

由于每次只有一个线程执行内置锁内的代码,因此被 synchronized 关键字保护的临界资源会以原子(一组不可分割的单元)的方式执行,多个线程间执行时不会受到干扰,原子性与数据库事务有相同的含义。

synchronized 关键字可以用来修饰方法和代码块,如果修饰非静态方法和同步代码块,使用的锁是当前对象,如果修饰静态方法和静态代码块,使用的是当前类的 Class 对象作为锁。

使用方式如下

public class SyncTest {
    private Integer num = 1;
    
    public int numAutoIncrement() {
        synchronized (this) {
            return num ++;
        }
    }

    public static void main(String[] args) {
        SyncTest syncTest = new SyncTest();

        // 开启 10 个线程
        for (int i = 0; i < 10; i++) {
            new Thread(() -> 
                    System.out.println(Thread.currentThread().getName() 
                            + ":" + syncTest.numAutoIncrement())
            ).start();
        }
    }
}

3.2 使用原子类

jdk1.5 之后 java.util.concurrent.atomic 包下引入了诸如 AtomicIntegerAtomicLong 等特殊的原子性变量类。

这些变量类底层使用 CAS 算法与 volatile 关键字确保对所有的计数器操作都能保证原子性与可见性,也就解决了多线程安全问题。

使用方式如下

public class SyncTest {
    private AtomicInteger atomicInteger = new AtomicInteger(1);
    
    public int numAutoIncrement() {
        return atomicInteger.getAndIncrement();
    }

    public static void main(String[] args) {
        SyncTest syncTest = new SyncTest();

        for (int i = 0; i < 10; i++) {
            new Thread(() -> 
                    System.out.println(Thread.currentThread().getName() 
                            + ":" + syncTest.numAutoIncrement())
            ).start();
        }
    }
}

3.3 使用显示 Lock 锁

jdk1.5 之前,解决多线程共享对象访问的机制只有 synchronizedvolatile。jdk1.5 新增了一种新的机制:ReentrantLockReentrantLock 并不是为替代内置锁而生的,当内置锁机制不适用时,可以考虑使用这种更灵活的加锁机制。

使用方式如下

public class SyncTest {
    private Lock lock = new ReentrantLock();
    private Integer num = 1;
    
    public int numAutoIncrement() {
        lock.lock();
        try {
            return num++;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        SyncTest syncTest = new SyncTest();

        for (int i = 0; i < 10; i++) {
            new Thread(() -> 
                    System.out.println(Thread.currentThread().getName() 
                            + ":" + syncTest.numAutoIncrement())
            ).start();
        }
    }
}

从上面的代码可以看出来,使用显示锁机制相对内置锁要麻烦一些,使用时要注意必须在 finally 语句块中释放锁,否则当出现异常时,获取到的锁永远不会被释放,在代码中存在这种情况无疑是一种定时炸弹。

显示锁相较于内置锁还提供了等待可中断、公平与非公平等功能,当然还有一个优点是显示锁更加灵活。

PS:
这篇博文中很多知识点拿出来都可以单独写一篇文章,这里只是总结了一部分重要的知识点,先让大家对多线程有一个感性的认识。后面会陆续的补充并发编程系列的文章。如果大家觉得写得还行的话,请持续关注后面的文章。

参考资料

《Java 并发编程实战》
《深入理解 Java 虚拟机》

猜你喜欢

转载自blog.csdn.net/codejas/article/details/82761928