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