1.进程和线程的概念
1>什么是进程:站在操作系统角度,进程是程序运行资源分配(以内存为主)的最小单位。
2>什么是线程:线程是cpu调度的最小单位。
3>二者的关系:进程包含多个线程,可以认为是父与子的关系,父亲可以没有儿子(C中可以没有
如 NGINX 只有多进程,Java中一定有 因为JVM本身就是一个进程),但是儿子肯定有父亲。
2.进程之间的通信有几种方式
1>管道:分为匿名管道(pipe)及命名管道(named pipe):匿名管道可用 于具有亲缘关系的父子进程间的通信,命名管道除了具有管道所具有的功能外, 它还允许无亲缘关系进程间的通信
2>信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较 复杂的通信方式,用于通知进程有某事件发生
3>消息队列
4>共享内存
5>信号量
6>套接字(socket)
3.CPU的核心数和线程数之间的关系
线程是CPU调度的最小单位,同一时刻一个CPU核心只能运行一个线程,即八核CPU可以同时执行8个线程(这就是为什么要用多线程的原因,单线程太浪费多核服务器了不是么?),但是iIntel 引入了超线程技术后,产生了逻辑处理器的概念,使核心数和线程数形成了1:2 的关系 , 我们可以用
Runtime.getRuntime().availableProcessors()
获取到当前的逻辑处理器数目,如下图所示
4.上下文切换(context Switch :不同线程执行程序的位置和对应数据不同,所以需要切换)
5.并发和并行的区别
并行是同时进行的,并发是交替进行的
6.Java中的线程
1>Java程序天生就是多线程的?
当我们启动一个main方法时,他会有几个线程?上代码
package com.concurrent;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
public class Onee {
public static void main(String[] args) {
//当一个程序只有一个main方法时 它有几个线程?
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();//jvm线程管理接口
//不需要获取同步的monitor和synchronizer的信息 只获取线程和线程的堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(threadInfo.getThreadId() + ":" + threadInfo.getThreadName());
}
}
}
结果如下图所示:
[6] Monitor Ctrl-Break //监控 Ctrl-Break 中断信号的
[5] Attach Listener //内存 dump,线程 dump,类信息统计,获取系统属性等
由此可见Java程序天生就是多线程的!
2>线程的启动 与 终止
启动方式:
1. X extends Thread 然后 通过 X.start()
2. X implements Runnable , 然后交给Thread 运行
上代码:
package com.concurrent;
/**
* 类说明:新启线程的方式
*/
public class CreateThread {
private static class Thread_1 extends Thread {
@Override
public void run() {
//do my work
System.out.println("extends Thread");
}
}
private static class Thread_2 implements Runnable {
@Override
public void run() {
//do my work
System.out.println("implements Runnable");
}
}
public static void main(String[] args) {
Thread_1 thread_1 = new Thread_1();
thread_1.start();
Thread_2 thread_2 = new Thread_2();
new Thread(thread_2).start();
System.out.println("main end!");
}
}
Thread 和 Runnable 的区别 Thread 才是 Java 里对线程的唯一抽象,Runnable 只是对任务(业
务逻辑) 的抽象。Thread 可以接受任意一个 Runnable 的实例并执行。
3> Callable 、Future、 FutureTask
因为 Future 只是一个接口,所以是无法直接用来创建对象使用的,因此就 有了FutureTask
FutureTask 类实现了 RunnableFuture 接口,RunnableFuture 继承了 Runnable 接口和 Future 接
口,而 FutureTask 实现了 RunnableFuture 接口。所以它既可以 作为 Runnable 被线程执行,又
可以作为 Future 得到 Callable 的返回值。
package com.concurrent;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class UseCallable {
private static class UsCallable implements Callable<Integer> {
private int sum = 0 ;
@Override
public Integer call() throws Exception {
for (int i = 0; i < 501; i++) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Callable 子线程执行任务中断!");
return null;
}
sum = sum+i;
System.out.println("sum = " + sum);
}
System.out.println("子线程计算结束!结果为" + sum);
return sum;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
UsCallable usCallable = new UsCallable();
FutureTask<Integer> futureTask = new FutureTask<Integer>(usCallable);
new Thread(futureTask).start();
//未来的某一时刻 获取到运行结果
Thread.sleep(1);
Random random = new Random();
if (random.nextInt(100) > 25) {
System.out.println(futureTask.get());
}
}
}
问:创建线程的方式有几种?
4> 中止
状态,这样容易引发死锁问题 ;stop 这个方法 在终结一个线程时不会保证线程的资源正常释放,
通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下,比如写
文件时会让文件损坏
通过gpt 翻译官网(Java Thread Primitive Deprecation)的原话 如下
2 interrupt()的使用
安全的中止则是其他线程通过调用某个线程 A 的 interrupt() 方法对其进行中断操作,中断好比其他线程对该线程 A 打了个招呼:“A,你要中断了”,不代表线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。
线程通过检查自身的中断标志位是否被置为 true 来进行响应,线程可以使用 isInterrupted() 方法来判断是否被中断,也可以调用静态方法 Thread.interrupted() 来判断当前线程是否被中断,不过 Thread.interrupted() 会同时将中断标识位改写为 false。
如果一个线程处于了阻塞状态(如线程调用了 thread.sleep、thread.join、thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为 false。
不建议自定义一个取消标志位来中止线程的运行。因为 run 方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,
一、一般的阻塞方法,如 sleep 等本身就支持中断的检查,
二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。
注意:处于死锁状态的线程无法被中断。
上代码:
package com.concurrent;
/**
* @Author coder huang
* @Date 2023 08 06 17 56
**/
public class EndThread {
private static class UseRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()
+ " interrupt flag is " + Thread.currentThread().isInterrupted());
while (!Thread.currentThread().isInterrupted()) {
//资源释放的业务代码
System.out.println(Thread.currentThread().getName()
+ " I am implements Runnable.");
}
System.out.println(Thread.currentThread().getName()
+ " interrupt flag is " + Thread.currentThread().isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
UseRunnable useRunnable = new UseRunnable();
Thread endThread = new Thread(useRunnable, "endThread");
endThread.start();
Thread.sleep(20);
endThread.interrupt();
}
}
深入理解 run() 和 start()方法
Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()
其实只是创建了一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()
方法后,才实现了真正意义上的启动线程。
从 Thread 的源码可以看到,Thread 的start()
方法中调用了start0()
方法,而start0()
是个 native 方法,这就说明Thread#start
一定和操作系统是密切相关的。start()
方法让一个线程进入就绪队列等待分配CPU,分到CPU后才调用实现的run()
方法。start()
方法不能重复调用,如果重复调用会抛出异常(注意,此处可能有面试题:多次调用一个线程的start
方法会怎么样?)。
而run
方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。