协程铺垫知识 —— JAVA 线程
Question:
- 线程和进程的区别?
- cpu 个数、cpu 核心数、cpu 线程数?
- 并发和并行的区别?
- 内核态线程和用户态线程是什么?
- 守护线程和用户线程?
- 线程的状态有哪些?如何转换这些状态?
- 为什么会有线程阻塞?
- 线程池是啥捏?
- 如何获得线程结果?(Future、Callable)
1. 线程和进程的区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。
[图片上传失败...(image-a1770c-1559130882083)]
2. cpu 个数、cpu 核心数、cpu 线程数?
在 linux 终端中查看cpuinfo
下面区别一下cpu、cpu cores、processors
CPU
:独立的中央处理单元,体现在主板上是有多个CPU的槽位。
CPU cores
:在每一个CPU上,都可能有多个核(core),每一个核中都有独立的一套ALU、FPU、Cache等组件,所以这个概念也被称作物理核。
processor
:这个主要得益于超线程技术,可以让一个物理核模拟出多个逻辑核,即processor。
简单来说就是,当有多个计算任务时,可以让其中一个计算任务使用ALU的时候,另一个则去使用FPU。
这样就可以充分利用物理核中的各个部件,使得同一个物理核中,也可以并行处理多个计算任务。
多核处理器:一个cpu有多个核心处理器(通过cpu内部总线通信)
多处理器:多个cpu(通过主板上的总线通信)
理论上来说,对于计算密集型的任务,线程数应该和CPU所能提供的并行数一致。那这里的“并行数”应该采取物理核数还是processor数呢?
对于计算密集型的任务,一般建议将线程数设置为物理核数。具体的,还需要针对不同的程序,做对应压力测试得到合适的参数选择。
3. 并发和并行的区别?
并行:同时执行几个任务
并发:轮流执行分时间执行几个任务(看起来像并行)
核数 > 线程数:并行
核数 < 线程数:并发
4. 内核态线程和用户态线程
5. 守护线程和用户线程
用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当 JVM 检测仅剩一个守护线程,而用户线程都已经退出运行时,JVM就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,JVM就不会退出。
守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。
虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。
6. 线程的状态有哪些?如何转换这些状态?
7. 为什么会有线程阻塞?
当线程执行 Thread.sleep()时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断;
当线程碰到一条 wait()语句时,它会一直阻塞到接到通知 notify()、被中断或经过了指定毫秒时间为止(若制定了超时值的话)。wait、notify需要对对象加同步。
public class ObjectWaitTest {
public static Object waitObject = new Object();
public static void notifyAllThread() {
System.out.println("notifyAllThread");
synchronized (waitObject) {
waitObject.notifyAll();
}
}
public static void notifyThread() {
System.out.println("notifyThread");
synchronized (waitObject) {
waitObject.notify();
}
}
static class MyThread extends Thread {
public Object waitObject = null;
private boolean isStop = false;
public MyThread(Object waitObject) {
this.waitObject = waitObject;
}
public void run() {
while (true) {
synchronized (waitObject) {
if (isStop) {
System.out.println(Thread.currentThread().getId() + " is stop");
try {
waitObject.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId() + " is resume");
System.out.println(Thread.currentThread().getId() + " will exit");
throw new RuntimeException(Thread.currentThread().getId() +" exit");
}
}
}
}
public void suspendThread() {
this.isStop = true;
}
}
}
public class Main {
public static void main(String[] args) {
ObjectWaitTest.MyThread tm1 = new ObjectWaitTest.MyThread(ObjectWaitTest.waitObject);
tm1.setName("tm1");
tm1.start();
ObjectWaitTest.MyThread tm2 = new ObjectWaitTest.MyThread(ObjectWaitTest.waitObject);
tm2.setName("tm2");
tm2.start();
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
tm1.suspendThread();
tm2.suspendThread();
try {
Thread.currentThread().sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ObjectWaitTest.notifyAllThread();
// ObjectWaitTest.notifyThread();
// try {
// Thread.currentThread().sleep(3000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// ObjectWaitTest.notifyThread();
}
}
运行结果
挂起和唤醒线程,suspend() 使线程进入阻塞状态,只有对应的 resume() 被调用的时候,线程才会进入可执行状态。(但这种方式产生死锁的风险很大,因为线程被挂起以后不会释放锁,可能与其他线程、主线程产生死锁,已经被deprecated,不建议使用)
yield() 会使的线程放弃当前分得的cpu时间片,但此时线程任然处于可执行状态,随时可以再次分得cpu时间片。yield() 方法只能使同优先级的线程有执行的机会。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。(暂停当前正在执行的线程,并执行其他线程,且让出的时间不可知)
join()也叫线程加入。是当前线程A调用另一个线程B的join()方法,当前线程转A入阻塞状态,直到线程B运行结束,线程A才由阻塞状态转为可执行状态。
I/O 操作。常见的一种方式是 InputStream 的 read()方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间;I/O 操作是由操作系统完成的,JVM 无法控制。
线程也可以阻塞等待获取某个对象锁的排他性访问权限(即等待获得 synchronized 语句必须的锁时阻塞)。
注意,并非所有的阻塞状态都是可中断的,以上阻塞状态的前两种可以被中断,后两种不会对中断做出反应。
8. 线程池
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 例如,线程数一般取cpu数量+2比较合适,线程数过多会导致额外的线程切换开销。
线程池是需要自定义的。
9. 如何获得线程结果?(Future、Callable)
Future 有 get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
public class CallableDemon implements Callable<Integer> {
private int sum;
public Integer call() throws Exception {
System.out.println("Callable子线程开始计算啦!");
Thread.sleep(2000);
for(int i=0 ;i<5000;i++){
sum=sum+I;
}
System.out.println("Callable子线程计算结束!");
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
//创建线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//创建Callable对象任务
CallableDemon calTask=new CallableDemon();
//提交任务并获取执行结果
Future<Integer> future =es.submit(calTask);
//关闭线程池
es.shutdown();
//创建线程池
// ExecutorService es = Executors.newSingleThreadExecutor();
// //创建Callable对象任务
// CallableDemon calTask=new CallableDemon();
// //创建FutureTask
// FutureTask futureTask=new FutureTask<Integer>(calTask);
// //执行任务
// es.submit(futureTask);
// //关闭线程池
// es.shutdown();
try {
Thread.sleep(2000);
System.out.println("主线程在执行其他任务");
if(future.get()!=null){
//输出获取到的结果
System.out.println("futureTask.get()-->"+future.get());
}else{
//输出获取到的结果
System.out.println("futureTask.get()未获取到结果");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主线程执行完成");
}
}
参考文章:
https://juejin.im/entry/5b77c798f265da43296c3b96
https://blog.csdn.net/pange1991/article/details/53860651