说明:这篇博客是我读《JAVA并发编程之美》后的一个总结。如有侵权请联系删除。同时说明以下内容仅代表个人观点,如果有不清除或是有错误的地方,欢迎大家批评指正!!!
目录
1.进程与线程的基础概念
-
什么是进程?什么是线程?
答:进程是操作系统分配和调度资源的最小单位,而对于CPU的资源的分配是以线程来划分的。线程是进程中的一个实体,线程属于进程,一个进程可以有多个线程。举个简单的例子:进程可以理解为一个工厂,而线程就是工厂中的一个个的流水线。
-
进程和线程在JAVA中的表现
当我们运行main方法是就启动了一个JVM(java虚拟机)进程,main方法就是该进程中的一个线程,也称主线程。
其大致表现如下图:
该图中绿色部分代表各个线程共享的部分,红色的部分则代表各个线程独有的部分。
堆:主要存放的是new操作创建的对象实例。
方法区:存放JVM加载的类,常量及静态变量等信息。
程序计数器:用于记录下一条指令的地址(或者理解为代码执行的位置)。
栈:用于存储该线程的局部变量(线程私有),栈帧。
2.JAVA中如何创建线程?他们的优缺点是什么?
JAVA中一共有3种创建线程的方式:
public class CreateThread1 {
// 1.继承Thread类的方式
private static class InheritThread extends Thread {
@Override
public void run() {
System.out.println("InheritThread!");
}
}
// 2.实现Runnable接口
private static class ImplementRunnable implements Runnable {
@Override
public void run() {
System.out.println("ImplementRunnable!");
}
}
// 3.使用FutureTask
private static class ImplementCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Hello world!";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.继承Thread类的方式
new InheritThread().start();
// 2.实现Runnable接口的方式
new Thread(new ImplementRunnable()).start();
// 3.使用FutureTask的方式
FutureTask<String> stringFutureTask = new FutureTask<>(new ImplementCallable());
new Thread(stringFutureTask).start();
// 输出FutureTask的方式的返回值
System.out.println(stringFutureTask.get());
}
}
小结:使用继承方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,但是Futuretask方式可以。
3.JAVA中的线程有哪些状态?
共有6中状态:
NEW:新建
RUNNABLE:运行
WAITING:等待
TIMED_WAITING:有时限的等待,超过指定时间后自行返回
BLOCKED:阻塞状态
TERMINATED:终止
-
他们之间是怎么转换的?
-
转换涉及方法详解:
(1)唤醒与等待
等待:wait()、wait(long timeout)、wait(long timeout, int nanos)。这三个方法是Object类提供的方法,即所有对象都能使用。其中核心方法为红色标注的方法。该过程会释放掉该共享变量的锁,若没有获取到共享变量的锁而调用其wait方法会报IllegalMonitorStateException异常,若在等待的过程中被打断会抛出InterruptedException。
wait():内部调用的是wait(0)代表一直等待。
wait(long timeout):代表超过timeout毫秒后会自动返回,该方法为native方法。
wait(long timeout, int nanos):其内部也是调用的wait(long timeout),加了对与nanos的判断(0-999999)在这个范围内是timeout + 1,否则抛出异常。
唤醒:notify()、notifyAll()。这两个方法是Object类提供的方法,用于唤醒等待的线程。
notify():会随机唤醒一个因争夺当前共享变量失败而阻塞的线程。
notifyAll():会唤醒所有因争夺当前共享变量失败(在调用该方法前)而阻塞的线程。
(2)等待线程执行终止的方法join
join()、join(long millis)、join(long millis, int nanos)。这三个方法是Thread类提供的方法。用于等待线程执行终止。其中核心方法为红色标注的方法,其内部调用的是wait方法。若在等待的过程中被打断会抛出InterruptedException,就不再展开赘述了。
(3)让线程睡眠的方法
sleep(long millis)、sleep(long millis, int nanos)。这两个方法是Tread类提供的静态方法。用于当前线程进入睡眠。其中核心方法为红色标注的方法,该方法为native方法。睡眠过程中不会释放当前线程获取到锁,若在睡眠的过程中被打断会抛出InterruptedException。
(4)让出CPU执行权的方法yield
yield(),这个方法是Thread类提供的方法,其为native方法,会交出当前CPU的使用权,但是会立刻进入到下一轮的争抢中,也就是说下次争抢到的CPU使用权的线程可能还是该线程。
(5)线程的中断
interrupt()、isInterrupted()、interrupted()、isInterrupted(boolean ClearInterrupted)。这四个方法都是跟中断线程相关的方法。其中红色标注的方法是核心方法。
interrupt():该方法中用于给调用该方法的线程设置中断标记。但是如果线程正在运行,并不真正的打断线程的运行,这里只是设置了标记而已。需要和其他方法配合使用。
isInterrupted(boolean ClearInterrupted):该方法是一个私有的native方法,用于判断该线程的中断标记为true,同时对标记进行清除(当ClearInterrupted变量为true时)。
interrupted():该方法是Thread类的静态方法,其内部代码如下,这里尤其要注意的是这里是对当前线程的中断标记进行判断,并清除该标记。
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
isInterrupted():该方法是Thread类的一个方法,用于判断线程的中断标记是否为true。这里也是调用的isInterrupted(boolean ClearInterrupted),但是参数为false,所以不对标记进行清除。
关于图中使用LockSupprot的方法不在本次分享中进行详细说明。后续会有专门针对与它的讲解。
如果有不清楚或是错误的地方,欢迎大家下方留言一起讨论!!!同时该系列会持续分享,如果希望看到后续的内容也可以关注一下😁!!!