1、什么是进程与线程
(1)【进程】是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
(2)【线程】与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
(3)两者关系:
下图是JVM角度两者关系
从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。
总结: 线程 是 进程 划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反
2、为什么需要Java 多线程编程
Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
3、 并发与并行的区别
(1)并发: 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
(2)并行: 单位时间内,多个任务同时执行。
4、一个线程的生命周期
(1)新建/初始/init状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
(2)就绪/可运行/runnable状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
(3)运行/running状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
(4)阻塞/blocked状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
【1】等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
【2】同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
【3】其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
(5)死亡/结束/end/dead状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态
5、线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
6、 如何创建一个线程
Java 提供了四种创建线程的方法:
(1)通过实现 Runnable 接口;
(2)通过继承 Thread 类本身;
(3)通过 Callable 和 Future 创建线程;
(4)通过线程池创建。
7、Thread类的重要方法和静态方法
》》重要方法:
序号 | 方法 | 描述 |
---|---|---|
1 | public void start() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 | public void run() | 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
3 | public final void setName(String name) | 改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) | 更改线程的优先级。 |
5 | public final void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec) | 等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt() | 中断线程 |
8 | public final boolean isAlive() | 测试线程是否处于活动状态。 |
》》静态方法:
序号 | 方法 | 描述 |
---|---|---|
1 | public static void yield() | 暂停当前正在执行的线程对象,并执行其他线程。 |
2 | public static void sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
3 | public static boolean holdsLock(Object x) | 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 |
4 | public static Thread currentThread() | 返回对当前正在执行的线程对象的引用。 |
5 | public static void dumpStack() | 将当前线程的堆栈跟踪打印至标准错误流。 |
8、yield方法使用
》》运行状态 - 可运行状态 -运行状态
/**
* 主动放弃CPU占用给其他线程执行:yield() 方法
* yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。
* 调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。
*
* 结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,
* 但有可能没有效果,因为调度器可能又直接随机到该线程。
*/
public class YieldTest extends Thread {
public YieldTest(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println("" + this.getName() + "-----" + i);
// 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i == 20) {//实际上不一定让出去 因为可能自己 又抢到
this.yield();
}
}
}
public static void main(String[] args) {
YieldTest yt1 = new YieldTest("张三");
YieldTest yt2 = new YieldTest("李四");
yt1.start();
yt2.start();
}
}
9、中断方法stop与interrupt
【1】stop :不推荐!
- stop来终止线程后,可以立马终止线程,数据同步会出现了错误。
- 也可以发现线程被强行stop后,自动释放了持有的锁。
public class StopTest extends Thread {
private MyThread myThread;
public StopTest(MyThread myThread) {
this.myThread = myThread;
}
public void run() {
myThread.print_p_u("b", "bb");
}
static class MyThread {
private String username = "a";
private String password = "aa";
public synchronized String getUsername() {
return username;
}
public synchronized String getPassword() {
return password;
}
synchronized public void print_p_u(String password, String username) {
try {
this.username = username;
Thread.sleep(100000);
this.password = password;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
try {
MyThread myThread = new MyThread();
StopTest t = new StopTest(myThread);
t.start();
Thread.sleep(500);
t.stop();
System.out.println(myThread.getUsername() + " "
+ myThread.getPassword());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
【2】interrupt:中断状态标记
- interrupt中断机制中有如下方法:
- -Thread.interrupt(),设置当前中断标记为true(类似属性的set方法)
- -Thread.isInterrupted(),检测当前的中断标记(类似属性的get方法)
- -Thread.interrupted(),检测当前的中断标记,然后重置(中断标记为false类似属性的get方法+set方法)
- 因此interrupt中断机制并不是真正的将当前线程中断,而是一个中断标记的变化。
public class InterruptTest {
//这里用来打印消耗的时间
private static long time = 0;
private static void resetTime() {
time = System.currentTimeMillis();
}
private static void printContent(String content) {
System.out.println(content + " 时间:" + (System.currentTimeMillis() - time));//执行时间
}
public static void main(String[] args) {
test1();
}
private static void test1() {
Thread1 thread1 = new Thread1();
thread1.start();
//延时3秒后interrupt中断
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt(); //就会让执行的线程中断
printContent("执行中断");
}
private static class Thread1 extends Thread {
@Override
public void run() {
resetTime();//获取当前时间
int num = 0;
while (true) {
if (isInterrupted()) {
printContent("当前线程 isInterrupted");
break;
}
num++;
if (num % 100 == 0) {
printContent("num : " + num);
}
}
}
}
}