并发编程
并发和并行
并行:不同程序交替执行,只有多核CPU才可以达到并行。
并发:不同程序同时执行,通过CPU快速切换,感觉上是同时在运行。
进程、线程
进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是对于程序的抽象。是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
线程(Thread):是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程中可以有多个线程并发执行,每条线程可以并行执行不同的任务。
进程和线程的区别:
- 根本区别:进程是操作系统进行资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
- 一个进程由一个或多个线程组成。进程之间是独立的,但线程之间会共享内存,文件等等。
线程安全
当对个线程同时读写同一对象时,会使得结果并非预期。
运行结束后,CNT 并非预期为 0 ,此时是线程不安全。
public class Main {
private static int CNT = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
CNT++;
}).start();
}
for (int i = 0; i < 10; i++) {
new Thread(() -> {
CNT--;
}).start();
}
System.out.println(CNT);
}
}
临界区与竞争条件
临界区:一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区,此代码需要考虑线程安全问题。
竞态条件:多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件。
局部变量的线程安全性
局部变量保存在各自线程中,是独有的,不存在线程安全问题。
局部变量引用的对象保存在堆中,对于所有线程都是可见的,因此需要考虑线程安全问题。
线程活跃性
死锁
进程互相等待对方所占的资源,于是两者都不能执行而处于永远等待状态,此现象称为死锁。
活锁
线程互相改变对方线程退出的条件,导致退出条件永远不能得到满足,导致死锁。
饥饿
线程无法获得足够的资源运行,比如无法获得时间片,因此发生饥饿现象。
线程的启动方式
可以通过继承Thread类或者实现Runable接口两种方法启动线程。
注意项:
-
run()相当于直接调用对象的run()方法,并不会新启线程。start()会新启动一个线程间接调用run()方法。 -
线程的
start()方法的含义:当前线程告诉 JVM ,只要线程规划器空闲,立刻启动调用start()方法的线程。
继承Thread类
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class Main {
public static void main(String[] args) {
new Thread(new MyThread()).start();
new Thread(new MyThread()).start();
}
}
实现Runable接口
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class Main {
public static void main(String[] args) {
new Thread(new MyThread()).start();
// lambda since jdk1.8
new Thread(() -> {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}).start();
}
}
线程的状态
线程共有6种状态。
New(新创建)
当使用new新建一个Thread实例,并且此时未调用start()方法的时候,意味着该线程未开始执行,该线程处于New状态,在运行之前仍需一些工作需要完成。
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
});
}
}
Runnable(运行状态)
当调用start()方法后,线程就处于Runnable状态,此时并不会立即执行,需要等待操作系统的调度。
线程转为Runnable状态
- 新线程启动。
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
});
thread.start();
}
}
Blocked(阻塞)
当线程等待资源或者锁时会进入阻塞状态,需要等待资源释放。
Waiting(等待)
表明线程进入等待状态,表示当前线程需要等待其他线程的一些特定动作(通知或中断)。
Timed Waiting(超时等待)
在等待的基础上增加了超时限制,超时时间到达后会返回到运行状态。
Terminated(被终止)
随着run()方法的执行完毕,线程也随之终止,表明代码已经执行完毕。