Java基础——多线程与并发

编程

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

两者关系

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

程序计数器:用于记录线程当前要执行的指令地址信息的区域;

栈:用于存储线程私有的局部变量和线程调用栈祯的区域;

堆:进程中最大的一块内存区域,存放各种变量信息,为线程共享;

方法区:用于存放JVM加载的类、常量及静态变量等信息的区域,为线程共享。

两者区别

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

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

并行与并发

基本概念

并行:当系统有一个以上CPU时,每个CPU单独执行一个线程,这些线程之间互不抢占其它线程的CPU资源,这样的运作方式即为 并行。

并发:在一个CPU上,运行着多个线程。由于CPU在只能执行一个线程,所以把CPU运行时间划分为若干时间段,再把时间段分配给每个线程,在每个时间段内只执行一个线程,其余线程处于挂起状,这样的运作方式即为 并发。

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

并发过程中常见问题

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

以一个双核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();

以上是 Java基础——多线程与并发 的全部内容, 来源链接: utcz.com/z/513957.html

回到顶部