基础知识
1、CPU核心数和线程数的关系
- 多核心:也指单芯片多处理器( Chip Multiprocessors,简称CMP),CMP是由美国斯坦福大学提出的,其思想是将大规模并行处理器中的SMP(对称多处理器)集成到同一芯片内,各个处理器并行执行不同的进程。这种依靠多个CPU同时并行地运行程序是实现超高速计算的一个重要方向,称为并行处理
- 多线程: Simultaneous Multithreading.简称SMT.SMT可通过复制处理器上的结构状态,让同一个处理器上的多个线程同步执行并共享处理器的执行资源可最大限度地实现宽发射、乱序的超标量处理,提高处理器运算部件的利用率,缓和由于数据相关或 Cache未命中带来的访问内存延时。
核心数、线程数:目前主流CPU有双核、三核和四核,六核也在2010年发布。增加核心数目就是为了增加线程数,因为操作系统是通过线程来执行任务的,一般情况下它们是1:1对应关系,也就是说四核CPU一般拥有四个线程。但 Intel引入超线程技术后,使核心数与线程数形成1:2的关系, -
自己的电脑如下
2、CPU时间片轮转机制
时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称RR调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结来,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾
时间片轮转调度中唯一有趣的一点是时间片的长度。从一个进程切换到另一个进程是需要定时间的,包括保存和装入寄存器值及内存映像,更新各种表格和队列等。假如进程切( processwitch),有时称为上下文切换( context switch),需要5ms,再假设时间片设为20ms,则在做完20ms有用的工作之后,CPU将花费5ms来进行进程切换。CPU时间的20%被浪费在了管理开销上了。
为了提高CPU效率,我们可以将时间片设为5000ms。这时浪费的时间只有0.1%。但考虑到在一个分时系统中,如果有10个交互用户几乎同时按下回车键,将发生什么情况?假设所有其他进程都用足它们的时间片的话,最后一个不幸的进程不得不等待5s才获得运行机会。多数用户无法忍受一条简短命令要5才能做出响应,同样的问题在一台支持多道程序的个人计算机上也会发
结论可以归结如下:时间片设得太短会导致过多的进程切换,降低了CPU效率:而设得太长又可能引起对短的交互请求的响应变差。将时间片设为100ms通常是一个比较合理的折衷。3、什么是进程和线程
进程是程序运行资源分配的最小单位
进程是操作系统进行资源分配的最小单位,其中资源包括:CPU、内存空间、磁盘等,同一进程中的多条线程共享该进程中的全部系统资源,而进程和进程之间是相互独立的。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。显然,程序是死的、静态的,进程是活的、动态的。进程可以分为系统进程和用户进程。凡是用于完成操作系统的各种功能的进程就是系统进程,它们就是处于运行状态下的操作系统本身,用户进程就是所有由你启动的进程。线程是cpu调度的最小单位,必须依赖于进程而存在
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的、能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
线程无处不在
任何一个程序都必须要创建线程,特别是Java不管任何程序都必须启动一个main函数的主线程; Java Web开发里面的定时任务、定时器、JSP和 Servlet、异步消息处理机制,远程访问接口RM等,任何一个监听事件, onclick的触发事件等都离不开线程和并发的知识。4、并行和并发
如果有条高速公路A上面并排有8条车道,那么最大的并行车辆就是8辆此条高速公路A同时并排行走的车辆小于等于8辆的时候,车辆就可以并行运行。CPU也是这个原理,一个CPU相当于一个高速公路A,核心数或者线程数就相当于并排可以通行的车道;而多个CPU就相当于并排有多条高速公路,而每个高速公路并排有多个车道。
当谈论并发的时候一定要加个单位时间,也就是说单位时间内并发量是多少?离开了单位时间其实是没有意义的。
综合来说:
并发:指应用能够交替执行不同的任务,比如单CPU核心下执行多线程并非是同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉到的速度不断去切换这两个任务,已达到"同时执行效果",其实并不是的,只是计算机的速度太快,我们无法察觉到而已.
并行:指应用能够同时执行不同的任务,例:吃饭的时候可以边吃饭边打电话,这两件事情可以同时执行两者区别:一个是交替执行,一个是同时执行.
5、高并发编程的意义、好处和注意事项
充分利用CPU的资源
加快响应用户的时间
可以使你的代码模块化,异步化,简单化
安卓的ui线程
多线程程序需要注意事项
(1)线程之间的安全性
在同一个进程里面的多线程是资源共享的,也就是都可以访问同一个内存地址当中的一个变量。例如:若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的:若有多个线程同时执行写操作,一般都需要考虑线程同步,否则就可能影响线程安全(2)线程之间的死循环过程:这个特别严重,死锁
为了解决线程之间的安全性引入了Java的锁机制,而一不小心就会产生Java线程死锁的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成(3)线程太多了会将服务器资源耗尽形成死机当机
Java里的程序天生就是多线程的
一个Java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上Java程序天生就是多线程程序,因为执行main()方法的是一个名称为main的线程。
- [6] Monitor Ctrl-Break //监控Ctrl-Break中断信号的
- [5] Attach Listener //内存dump,线程dump,类信息统计,获取系统属性等
- [4] Signal Dispatcher // 分发处理发送给JVM信号的线程
- [3] Finalizer // 调用对象finalize方法的线程
- [2] Reference Handler//清除Reference的线程
- [1] main //main线程,用户程序入
线程的启动与中止
启动
启动线程的方式有:
- 1、X extends Thread;,然后X.run
- 2、X implements Runnable;然后交给Thread运行
- 3、X implements Callable;然后交给Thread运行
第1、2方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。从Java 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果。
但是其实只有两种,因为第三种的方式看着像是实现了Callable的接口,但是呢,其实到底还是实现了Runnable的接口,一个类实现了Callable,然后new 出对象,对象交给FutureTask,然后通过线程start
1、看FutureTask这个类,是实现了RunnableFuture接口
2、RunnableFuture继承了,Runnable的接口和Future接口,所以到底还是Runnable的之类,所以就只有两种
Callable、Future和FutureTask
Runnable是一个接口,在它里面只声明了一个run()方法,由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。
Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call(),这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。
FutureTask类实现了RunnableFuture接口,RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
事实上,FutureTask是Future接口的一个唯一实现类。
要new一个FutureTask的实例,有两种方法
中止
线程自然终止:要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
手动中止
暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。但是这些API是过期的,也就是不建议使用的。不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。
安全的中止则是其他线程通过调用某个线程A的interrupt()方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求。因为java里的线程是协作式的,不是抢占式的。线程通过检查自身的中断标志位是否被置为true来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。
如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。
不建议自定义一个取消标志位来中止线程的运行。因为run方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,一、一般的阻塞方法,如sleep等本身就支持中断的检查,二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。
注意****:处于****死锁状态****的****线程无法被中断
几个方法
- start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,start()方法不能重复调用。
- run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,可以被单独调用。
- yield()方法:使当前线程让出CPU占有权,但让出的时间是不可设定的。也不会释放锁资源,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行。
- join方法:把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
一张图
一个线程新建,到start()-就绪了,如果获取join()执行权就会运行,时间片到期 yield就会从运行状态转化到就绪
运行的状态,可以通过sleep() 到阻塞状态,同时run()到结束,也是就是一个死亡的状态。Stop不建议去使用,因为这样子会导致线程有很多的碎片化的空间,线程中的任务没有去执行完成,也可以通过wait 去到阻塞的情况
线程阻塞可以通过 notify notifyAll去唤醒他,同时sleep时间到也可以到就绪的状态,如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中
关于 yield的方法,在 ConcurrentHashMap 类中的initTable方法中就调用了
关于Doug Lea ,李大爷,这句话可以这么说:编程不识DougLea,写尽java也枉然
道格拉斯·李(Douglas S. Lea)是纽约州立大学奥斯威戈分校的计算机科学教授和现任计算机科学系主任,他专门研究并发编程和并发数据结构的设计。他是Java Community Process执行委员会的主席,并担任JSR 166主席,该委员会将并发实用程序添加到Java编程语言中(请参阅Java concurrency)。2010年10月22日,Doug Lea通知Java社区流程执行委员会,他不赞成连任。 Lea再次当选为2012 OpenJDK理事会的一般会员
-
Doug Lea写的java.util.concurrent备受推崇。AQS的思想奠基者
-
HashMap也是李大爷的杰出之作