2019-02-19 · Concurrent

多线程的基本概述

在学习并发之前需要搞清楚的是多线程的一些基础,本文简要描述下多线程的基础知识。

什么是多线程

进程(process)和线程(thread)是操作系统的基本概念,它们都是对 CPU 工作时间段的描述,只是描述的的颗粒大小不同而已。这里有一篇来自全民科普大师-阮一峰的文章 进程与线程的一个简单解释 里面进行了比较具象化的描述。

使用多线程

在 Java 种实现多线程编程的方式主要有两种,一种是继承 Thread 类,另一种是实现 Runnable 接口。这两种方式在本质上没有太大的区别,毕竟 Thread 类也是实现的 Runnable 接口的。 Runnable 的出现主要是解决 Java 种只支持单继承的语法限制。
这里我们就使用 Runnable 来实现阮一峰文章中解释的信号量问题。这里我们假设有10把钥匙和10个带有编号得人来取这些钥匙。

public class Test {

    public static void main(String[] args) {
        TestRunnable runnable = new TestRunnable();
        for (int i = 1; i <= 10; i++) {
            new Thread(runnable, "Person" + i).start();
        }
    }

    static class TestRunnable implements Runnable {

        private int count = 10;

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " -> " + count);
            count--;
        }
    }

}

上面代码在我的本地输出结果为

Person1 -> 10
Person6 -> 10
Person3 -> 10
Person9 -> 7
Person5 -> 10
Person7 -> 10
Person4 -> 10
Person2 -> 10
Person8 -> 4
Person10 -> 7

可以看出出现了多个人去取了同一把钥匙的情况,和我们的设想并不符合,那么问题出现在什么地方呢?其实在 JVM 中 i-- 这里的代码并不具有原子性。其操作会被分成如下的3步:

  1. 取出原有i值。
  2. 计算i-1表达式。
  3. i重新进行赋值。
    也就是说在有人取钥匙的时候,有其他的人在进行取钥匙这个动作,那么解决方案就是使用 synchronized 关键字来保证同一只要一个人在进行取钥匙的动作。加锁的代码区域叫做互斥区
@Override
public synchronized void run() {
    System.out.println(Thread.currentThread().getName() + " -> " + count);
    count--;
}

现在就会看到如下的输出

Person1 -> 10
Person5 -> 9
Person6 -> 8
Person3 -> 7
Person4 -> 6
Person2 -> 5
Person8 -> 4
Person7 -> 3
Person9 -> 2
Person10 -> 1

从上面的输出可以看出,每个人去取钥匙的时候,并不是按照人的编号顺序进行取的,如上第二个取钥匙的人是5号。这个就是线程调用的随机性