基础概念
(1) CPU 核心数和线程数的关系
核心数:线程数 = 1:1 使用了超线程技术后: 1:2
多核心:也指单芯片多处理器( Chip Multiprocessors,简称CMP),CMP是由美国斯坦福大学提出的,其思想是将大规模并行处理器中的SMP(对称多处理器)集成到同一芯片内,各个处理器并行执行不同的进程。这种依靠多个CPU同时并行地运行程序是实现超高速计算的一个重要方向,称为并行处理
多线程: Simultaneous Multithreading.简称SMT.让同一个处理器上的多个线程同步执行并共享处理器的执行资源。
核心数、线程数:目前主流CPU都是多核的。增加核心数目就是为了增加线程数,因为操作系统是通过线程来执行任务的,一般情况下它们是1:1对应关系,也就是说四核CPU一般拥有四个线程。但 Intel引入超线程技术后,使核心数与线程数形成1:2的关系
(2) CPU时间片轮转机制
又称RR调度,会导致上下文切换
(3) 上下文切换
即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的执行时间,因为事件片非常短,所有CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行,时间片一般 几十毫秒 (ms)。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切回这个任务时,可以再加载这个任务的状态。所以任务 从保存到再加载的过程就是一次上下文切换。
这就像我们同时读两本书,当我们在读一本英文的技术书时,发现某个单词不认识,于是便打开中英文字典,但是在放下英文技术书之前,大脑必须先记住这本书读到了多少页的第多少行,等查完单词之后,能够继续读这本书。这样的切换是会影响读书效率的,同样上下文
切换也会影响多线程的执行速度。
① 导致原因(线程由于自身原因切出)
1、自发性切换
- Thread.sleep
- wait
- Thread,yield
- Thread,.join
- LockSupport.park
- 发起了IO操作如读文件
- 等待其他线程持有的锁
2、非自发性切换
线程由于调度器的原因被迫切出
Ⅰ被切出的时间片用完
Ⅱ比被切出线程的优先级更高的线程需要被运行
② 开销
Ⅰ直接开销
操作系统保存和恢复上下文所需的开销;线程调度器 进行线程调度的开销
Ⅱ 间接开销
处理器高速缓存重新加载的开销;上下文切换也可能导致一级高速缓存中的内容被冲
③ 如何减少上下文 切换
Ⅰ 无锁并发编程:多线程竞争时,会一起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash的算法取模分段,不同的线程处理不同段的数据。
Ⅱ CAS:java的Atomic包 使用CAS算法来更新数据,而不需要加锁
Ⅲ 使用最少线程:避免创建不需要的线程,比如人物很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
Ⅳ 协程 :在单线程里实现多任务调度,并在单线程里维持多个任务间的切换
(4) 什么是进程和线程
① 进程是程序运行资源分配的最小单位
进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、磁盘IO等,同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的、静态的,进程是活的、动态的。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身,用户进程就是所有由你启动的进程。
②线程是CPU调度的最小单位,必须依赖于进程而存在
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
线程无处不在
任何一个程序都必须要创建线程,特别是Java不管任何程序都必须启动一个main函数的主线程; Java Web开发里面的定时任务、定时器、JSP和 Servlet、异步消息处理机制,远程访问接口RM等,任何一个监听事件, onclick的触发事件等都离不开线程和并发的知识。
(5) 并发和并行
并行:同一时刻,可以同时处理事情的能力
并发:与单位时间相关,在单位时间内可以处理事情的能力
(6) 高并发编程的意义、好处和注意事项
好处:充分利用cpu的资源、加快用户响应的时间,程序模块化,异步化
问题:
线程共享资源,存在冲突;
容易导致死锁;
启用太多的线程,就有搞垮机器的可能
认识Java里的线程
2.1 java 启动线程有哪几种方式
- 类Thread
- 接口Runnable
- 接口 Callable
<code>public class ThreadDemo {
private static class RunDemo implements Runnable{
@Override
public void run( ) {
}
}
private static class CallableDemo implements Callable<string>{
@Override
public String call( ) throws Exception {
return "hello world";
}
}
public static void main( String[] args ) throws ExecutionException, InterruptedException {
RunDemo runDemo = new RunDemo();
new Thread(runDemo).start();
CallableDemo callableDemo = new CallableDemo();
FutureTask<string> futureTask = new FutureTask<string>(callableDemo);
new Thread(futureTask).start();
System.out.println(futureTask.get());
}
}/<string>/<string>/<string>/<code>
2.2 下面我们看下Java中存在的线程
<code>public class MainDemo {
public static void main( String[] args ) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,false);
for(ThreadInfo threadInfo:threadInfos){
System.out.println("["+threadInfo.getThreadId()+"]"+" " + threadInfo.getThreadName());
}
}
}/<code>
执行结果:
<code>[6] Monitor Ctrl-Break
[5] Attach Listener
[4] Signal Dispatcher
[3] Finalizer
[2] Reference Handler
[1] main/<code>
6] Monitor Ctrl-Break :
[5] Attach Listener : 获取当前运行的程序的相关信息,比如内存大小、系统属性等。
[4] Signal Dispatcher : 分发给我虚拟机信号的线程
[3] Finalizer : 调用我们对象 finalize 方法
[2] Reference Handler : 清除引用的线程
[1] main : 用户程序的入口
2.2 线程的状态
2.3 线程的中断
在java 中,线程中断是一种重要的线程协作 机制,严格 讲,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程,有人希望你退出,至于目标线程接到通知后如何处理,则完全由目标线程自行决定。
线程中断的三个方法:
- public void Thread.interrupt() //中断线程
- public boolean Thread.isInterrupted() //判断是否被 中断
- public static boolean Thread.interrupted() // 判断是否被中断,并清除当前中断 状态
<code>public class InterruptDemo {
private static int i;
public static void main( String[] args ) throws InterruptedException {
Thread t1 = new Thread(()->{
while (true){
System.out.println(i++);
}
});
t1 .start();
Thread.sleep(2000);
t1 .interrupt();/<code>
上面代码不会被中断,因为没有中断处理逻辑,即使被加上了中断 状态,但是这个中断不会发生作用。
如果希望t1 在中断后退出,就必须为它增加相应的中断 处理代码
<code>public class InterruptDemo {
private static int i;
public static void main( String[] args ) throws InterruptedException {
Thread thread = new Thread(()->{
while (true){
if(Thread.interrupted()){
break;
}
System.out.println(i++);
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();/<code>
测试结果:无限循环下去
<code>public class InterruptDemo2 {
private static int i;
public static void main( String[] args ) throws InterruptedException {
Thread thread = new Thread(()->{
while (true){
boolean in = Thread.currentThread().isInterrupted();
if(in){
System.out.println("before:"+in);
Thread.interrupted();//设置复位
System.out.println("after:"+Thread.currentThread().isInterrupted());
}
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();/<code>
测试结果:
before:true
after:false
<code>public class InterruptDemo3 {
private static int i;
public static void main( String[] args ) throws InterruptedException {
Thread thread = new Thread(()->{
while (true){
if(Thread.currentThread().isInterrupted()){
System.out.println("Interrupted");
break;
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// 异常里会清除中断标志位catche
System.out.println("Interrupted When Sleep");
//设置中断
Thread.currentThread().interrupt();
}
}
});
thread.start();
Thread.sleep(2000);
thread.interrupt();
System.out.println("before:"+thread.isInterrupted());
Thread.sleep(2000);
System.out.println("after:"+thread.isInterrupted());
/<code>
上述代码,如果不在 catch里 重新设置中断,则下面代码不会执行:
<code> if(Thread.currentThread().isInterrupted()){
System.out.println("Interrupted");
break;
}/<code>
调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。
isInterrupted() 判定当前线程是否处于中断状态。
static方法interrupted() 判定当前线程是否处于中断状态,同时中断标志位改为false。
方法里如果抛出InterruptedException,线程的中断标志位会被复位成false,如果确实是需要中断线程,要求我们自己在catch语句块里再次调用interrupt()。
2.4 守护线程和非守护线程
守护线程的做法:
<code>Thread t = new Thread();
t.setDaemon();
t.start/<code>
閱讀更多 碼農的一天 的文章