Java基础 —— 多线程与并发

进程与线程的关系

基本概念

进程:是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是操作系统进行资源分配和调度的基本单位。

线程:是操作系统能够进行运算调度的最小单位,是操作系统独立调度和分派的基本单位。被包含于 进程 中,是 进程 的实际运作单位。

两者关系

一个 进程 可以有多个 线程,多个 线程 共享进程的堆和方法区资源。但每个线程有各自的程序计数器和栈区域。

> 程序计数器:用于记录线程当前要执行的指令地址信息的区域; 栈:用于存储线程私有的局部变量和线程调用栈祯的区域; 堆:进程中最大的一块内存区域,存放各种变量信息,为线程共享; 方法区:用于存放JVM加载的类、常量及静态变量等信息的区域,为线程共享。

两者区别

  • 进程 具有独立的地址空间,一个进程崩溃后不会对其它进程产生影响;线程 是一个进程中的某个执行路径,由于线程没有独立的地址空间,其死掉可能会影响到整个进程;
  • 线程 的开销相对于 进程 而言,要更“轻量级”;
  • 一个 进程 内的多个 线程,由于共享进程资源的原因,通信更快速、有效,极大提高了程序的运行效率;
  • 线程 不能单独执行,必须依赖存在于进程中。

> PS:抽象的讲,进程和线程的关系就好比工厂和工人的关系,进程是工厂而线程是工厂里的工人。

并行与并发

基本概念

并行:当系统有一个以上CPU时,每个CPU单独执行一个线程,这些线程之间互不抢占其它线程的CPU资源,这样的运作方式即为 并行。 并发:在一个CPU上,运行着多个线程。由于CPU在只能执行一个线程,所以把CPU运行时间划分为若干时间段,再把时间段分配给每个线程,在每个时间段内只执行一个线程,其余线程处于挂起状,这样的运作方式即为 并发。

> PS:抽象的讲,并行就好比是工厂的多条忙绿的流水线,而并发就好比流水线上忙碌的工人。

并发过程中常见问题

Java内存模型规定,所有的变量都存放在主内存中,当线程使用变量时,会把主内存里面的变量复制到自己的工作空间,因此线程读写变量是操作自己工作内存中的变量,如图所示:

![Java内存模型]()

以一个双核CPU系统来说,实际工作情况如下图所示:

![双核CPU模型]()

每个核都有自己的控制器、运算器和一级缓存,部分架构中还具备有所有CPU都共享的二级缓存。Java内存模型中线程的工作空间就是这里的一级缓存、二级缓存或者寄存器。当线程A首次获取共享变量X的值,由于两级缓存都没命中,所以会加载主内存中X的值,假如为0。然后把X=0的值缓存到两级缓存中,线程A修改X的值为1并写入两级缓存,同时刷新到主内存。此时,线程A所在CPU的两级缓存和主内存里的X的值都是1。接着,线程B获取X的值,首先一级缓存没命中,然后看二级缓存,二级缓存命中了,所以返回X=1。接着线程B修改X的值为2,并将其存放到线程2所在的一级缓存和共享二级缓存中,并更新主内存中X的值为2。线程A又需要修改X的值,获取时一级缓存命中,此时一级缓存中X的值为1,而非主内存中的2,这就是并发中常见的问题——线程安全问题。

线程创建

Java中创建线程主要有三种方式:

  • 继承 Thread 类
  • 实现 Runable 接口
  • 实现 Callable 接口

> PS:继承方式相对于接口而言,更容易传递和设置参数;继承方式不支持继承其他类,而接口没有这个限制。

线程的状态

![线程状态]()

线程有如下几个状态:

  • NEW:初识状态,线程刚被创建但未start()时;
  • RUNNABLE:运行状态(实际上在系统调度情况下可以分为RUNNING和READY状态);
  • BLOCKED:阻塞状态,线程阻塞于锁;
  • WAITING:等待转态,等待其他线程通知或中断;
  • TIMED_WAITING:超时等待,当超时等待时间到达后,线程会切换到Runable的状态;
  • TERMINATED:终止状态,线程执行完毕。

线程的基本操作

start

线程的启动方法,代码如下:

public synchronized void start() {
    if (threadStatus != 0) // 线程不能重复start
        throw new IllegalThreadStateException();

    group.add(this);
    boolean started = false;
    try {
        start0(); // 本地方法
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}

private native void start0(); // 本地方法
stop

线程暴力停止方法,已被废弃。

join

当前线程只有等加入的线程执行完之后(或到达给定的时间)才能继续执行,否则一直阻塞状态。代码实现如下:

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

public final synchronized void join(long millis, int nanos) throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException("nanosecond timeout value out of range");
    }
    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    join(millis);
}

public final void join() throws InterruptedException {
    join(0);
}
sleep

休眠,Thread的静态方法,让出CPU时间片,但不会释放锁,可以在任意地方调用。休眠时间结束后,线程进入线程池等待下一次获取CPU资源。

public static native void sleep(long millis) throws InterruptedException; // 静态方法

public static void sleep(long millis, int nanos) throws InterruptedException {
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException("nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }
    sleep(millis);
}
wait

等待,Object的实例方法,让出CPU时间片,同时释放锁,只能在同步代码中调用。等待其他线程notify()/notifyAll()或到达指定时间后离开等待池,再次获取CPU后继续执行。

public final native void wait(long timeout) throws InterruptedException; // 本地方法

public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException("nanosecond timeout value out of range");
    }

    if (nanos > 0) {
        timeout++;
    }
    wait(timeout);
}

public final void wait() throws InterruptedException {
    wait(0);
}
yield

静态方法,暂时让出CPU资源,让出的时间片只会配给线程优先级(priority)相同的线程竞争(资源不紧张时,调度器会忽略这个提示)。

public static native void yield();
notify/notifyAll

notify:通知可能等待该对象的对象锁的其他线程,JVM随机唤醒处于wait状态的一个线程,与优先级无关,同步代码块中执行。

notifyAll:使所有正在等待池中等待同一共享资源的全部线程从等待状态退出,进入可运行状态,同步代码块中执行。

public final native void notify();

public final native void notifyAll();
interrupt/isInterrupted/interrupted

interrupt:中断当前线程对象,如果当前线程调用了 wait/sleep/join 方法时会抛出 InterruptedException,并清除标志位(可以理解为一个标志位,表示一个运行中的线程是否被其他线程中断)。

isInterrupted:判断当前线程是否被中断,不会清除标志位。

interrupted:判断当前线程是否被中断,会清除标志位。

Daemon守护线程

通过在 start() 方法前调用 setDaemon(true),将线程设置为守护线程。守护线程会在被守护线程结束后自动结束,但并不会执行 finally 代码块。例如:

Thread thread = new Thread(() -> {
    // do something
});
thread.setDaemon(true);
thread.start();

来源:35分类目录

声明:该文章系转载,转载该文章的目的在于更广泛的传递信息,并不代表本网站赞同其观点,文章内容仅供参考。

本站是一个个人学习和交流平台,网站上部分文章为网站管理员和网友从相关媒体转载而来,并不用于任何商业目的,内容为作者个人观点, 并不代表本网站赞同其观点和对其真实性负责。

我们已经尽可能的对作者和来源进行了通告,但是可能由于能力有限或疏忽,导致作者和来源有误,亦可能您并不期望您的作品在我们的网站上发布。我们为这些问题向您致歉,如果您在我站上发现此类问题,请及时联系我们,我们将根据您的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。