-
什么是进程和线程?
进程:
进程是操作系统进行资源分配的最小单位,一个进程中可以存在一个或者多个线程,同一进程中的线程可以共享该进程中的系统资源,但是进程和进程之间是相互独立的,不同进程间传递消息需要使用跨进程通讯。
线程:
线程是CPU调度的最小单位,必须依赖进程而存在,同一进程中的多个线程共享进程数据。
举例:
进程就像是火车,而线程就是这个火车上的车厢,每个火车最少有一个车厢,同一个火车上的车厢可以共享这个火车上的资源。
-
什么是并发和并行?
并发:
并发指应用能交替执行不同的任务,就像是单CPU核心执行多线程任务,并不是同时执行,而是以肉眼无法观察到的速度交替执行。
并行:
并行指应用能够同时执行不同的任务,就像是多CPU核心执行多线程任务,两个核心可以同时执行不同的任务。
举例:
并发是两个队列使用同一个咖啡机,只能交替使用,不能同时。
并行是两个队列使用两个咖啡机,可以同时使用,互不干扰。
-
进入正题
前面说了这么多都是为了后面学习多线程的铺垫,而且也是面试很容易问到的点,因为很多人总是搞不清楚区别,下面正式进入多线程的学习。
-
线程创建的三种方式
-
1. 继承Thread类
public class ThreadDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行了");
}
}
}
复制代码
-
2. 实现Runnable接口
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("线程执行了");
}
}
}
复制代码
-
3. 实现Callable接口
public class ThreadDemo {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
//接收线程返回的值
try {
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
static class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
return "线程执行了,我是返回的值";
}
}
}
复制代码
-
线程怎么停止?
正常情况下,如果线程执行完成会自动销毁,但是如果我们线程有一个很耗时的操作,导致线程一直无法销毁,我们想终止应该怎么办?
-
方法一(stop方法)
从图中可以看到,stop()方法已经被官方废弃了,因为stop方法会强行终止一个线程,无法保证线程的资源被释放,所以我们不建议这种方式。
-
方法二(自定义标识符)
因为线程正常执行完就会停止,所以我们可以自定义一个标识符来判断,如果标识符状态改变了就停止线程中的任务,线程就可以正常销毁了,大多数人使用的都是这种方式。
public class ThreadDemo {
private static boolean isRunning = true; //线程是否运行
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//2s后停止线程
Thread.sleep(2000);
isRunning = false;
}
static class MyThread extends Thread {
@Override
public void run() {
while (isRunning) {
System.out.println("线程正在执行");
}
}
}
}
复制代码
-
方法三(interrupt方法) 推荐
interrupt与方法二非常类似,方法二需要我们自己定义一个标识符,其实官方已经给我们定义一个标识符了,我们直接使用就行。注意: 调用interrupt方法线程并不会停止,需要我们自己去获取interrupt的状态来决定线程是否继续执行。
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//2s后停止线程
Thread.sleep(2000);
myThread.interrupt();
}
static class MyThread extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程正在执行");
}
}
}
}
复制代码
-
线程生命周期
注意:线程调用start()方法并不会立即执行,而是进入就绪状态等待系统的调度。
-
守护线程
线程分为用户线程和守护线程,守护线程会守护所有用户线程(老舔狗了),只要还有用户线程没有结束,守护线程就不会结束,当所有用户线程都结束了,守护线程就会自动结束。
public class ThreadDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//将线程设置为守护线程
myThread.setDaemon(true);
myThread.start();
}
static class MyThread extends Thread {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程正在执行");
}
}
}
}
复制代码
-
sleep()
sleep()会使当前调用的线程睡眠一段时间,也就是暂停执行一段时间,此时会让出CPU执行权进入阻塞状态,等待阻塞完毕会进入就绪状态,等待系统调度。
-
join()
join()方法会暂停除了调用线程以外的线程,保证调用线程执行完毕后再执行其他线程,如果我们现在创建三个线程,并执行:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2();
MyThread3 myThread3 = new MyThread3();
myThread1.start();
myThread2.start();
myThread3.start();
}
static class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("线程1执行了-> "+i);
}
}
}
static class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("线程2执行了-> "+i);
}
}
}
static class MyThread3 extends Thread {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("线程3执行了-> "+i);
}
}
}
}
复制代码
上面代码是没有加入join()代码的,所以执行结果会是随机的:
线程1执行了-> 0
线程3执行了-> 0
线程3执行了-> 1
线程2执行了-> 0
线程3执行了-> 2
线程1执行了-> 1
线程1执行了-> 2
线程2执行了-> 1
线程2执行了-> 2
复制代码
如果我们给thread1设置join(),执行的时候就会先把thread1执行完毕,然后thread2和thread3再继续抢夺资源:
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2();
MyThread3 myThread3 = new MyThread3();
myThread1.start();
myThread1.join();
myThread2.start();
myThread3.start();
执行结果:
线程1执行了-> 0
线程1执行了-> 1
线程1执行了-> 2
线程2执行了-> 0
线程3执行了-> 0
线程2执行了-> 1
线程3执行了-> 1
线程2执行了-> 2
线程3执行了-> 2
复制代码
-
yield()
yield()方法会使当前线程由运行状态变为就绪状态,此时CPU会随机调度就绪状态的线程,有可能再次调度到此线程,也可能会调度到别的线程,所以yield()方法有可能让出资源后会立即再次抢夺到资源。
-
wait()和notify()、notifyAll()
这三个方法都是Object类的方法,所以所有对象都可以调用这三个方法,由于这三个方法跟锁机制有关联,本篇没有讲到线程锁的概念,所以下篇会和锁机制一起详解。