msm8960和omap4460在多线程下比较,注意是多线程,高通 异步双核核是要打折的。

3837人阅读
多线程多进程(9)
一、多线程使用情景:
1.用户需要同时得到多个反馈,例如下载过程中进度条改变,读取文件的时候显示结果。
2.提高程序执行性能,提高CPU使用效率,。
多线程的主要是需要处理大量的IO操作或者处理的情况需要花大量的时间等等,比如读写文件,网络数据接收,视频图像的采集,处理显示保存等操作缓慢的情形和需大幅度的提高性能的程序中使用。
但也不是都使用多线程,因为多线程过多的线程一般会导致数据共享问题,太多多线程切换也是会影响性能的,所以一般不须采用多线程的不用多线程效果更好。
二、多线程原理和类型:
1.多线程原理
线程是分配CPU资源的最小单位,单CPU多线程是时间轮片的切换,多CPU可以真正的做到多CPU同时工作。
2.多线程具体的实现
每个线程具有自己的栈空间和CPU寄存器副本,多个线程可以执行同一份函数(私有空间)。&其他资源(如文件、全局变量、静态数据、堆内存、同步锁变量、组件对象)由进程中的所有线程共享(共有资源)。使用这些公共资源的线程必须同步。Win32
提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。
3.线程的堆栈
说一下线程自己的堆栈问题。
是的,生成子线程后,它会获取一部分该进程的堆栈空间,作为其名义上的独立的私有空间。(为何是名义上的呢?)由于,这些线程属于同一个进程,其他 线程只要获取了你私有堆栈上某些数据的指针,其他线程便可以自由访问你的名义上的私有空间上的数据变量。(注:而多进程是不可以的,因为不同的进程,相同 的虚拟地址,基本不可能映射到相同的物理地址)
4.创建线程的类型
这个多线程的例子应该很明了了,主线程做自己的事情,生成2个子线程,task1为分离,任其自生自灭,而task2还是继续送外卖,需要等待返回。(因该还记得前面说过僵尸进程吧,线程也是需要等待的。如果不想等待,就设置线程为分离线程)。
1)自生自灭类型:
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
& //设置attr结构为分离 &
pthread_create(&pid1, &attr, task1, NULL); & & & &
//创建线程,返回线程号给pid1,线程属性设置为attr的属性,线程函数入口为task1,参数为NULL &
2)继续等待类型:
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); &
pthread_create(&pid2, &attr, task2, NULL); &
//前台工作 &
ret=pthread_join(pid2, &p); & & & & //等待pid2返回,返回值赋给p &
printf(&after pthread2:ret=%d,p=%d/n&, ret,(int)p); &
三、线程安全函数:
1)可重入函数:概念基本没有比较正式的完整解释,但是它比线程安全要求更严格。根据经验,所谓“重入”,常见的情况是,程序执行到某个函数foo()时,收到信号,于是暂停目前正在执行的函数,转到信号处理函数,而这个信号处理函数的执行过程中,又恰恰也会进入到刚刚执行的函数foo(),这样便发生了所谓的重入。此时如果foo()能够正确的运行,而且处理完成后,之前暂停的foo()也能够正确运行,则说明它是可重入的,&反复调用都得到正确的结果。
可重入函数的判断条件:
要确保函数可重入,需满足以下几个条件:
1、不在函数内部使用静态或全局数据&
2、不返回静态或全局数据,所有数据都由函数的调用者提供。&
3、使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。
4、不调用不可重入函数。
&可重入与线程安全并不等同,一般说来,可重入的函数一定是线程安全的,但反过来不一定成立。
2)线程安全函数:
确保函数线程安全,主要需要考虑的是线程之间的共享变量。属于同一进程的不同线程会共享进程内存空间中的全局区和堆,而私有的线程空间则主要包括栈和寄存 器。因此,对于同一进程的不同线程来说,每个线程的局部变量都是私有的,而全局变量、局部静态变量、分配于堆的变量都是共享的。在对这些共享变量进行访问 时,如果要保证线程安全,则必须通过加锁的方式。
判断线程安全函数的条件:
如果是可重入的函数那么是线程安全的,如果是线程不安全的函数,那么需要通过对共享数据(全局变量/静态变量,文件对象,堆内存)进行加锁同步(事件,信号,临界区,互斥体)可以转换为线程安全的函数。
线程函数和线程调用的函数要求细则:
1.正确的要求,有子线程回调调用那么要求可重入的,否则至少是线程安全的:
多线程中调用,如果是一个线程来回(回调)反复调用的那么要求是可重入的函数;如果是没有回调的普通线程调用的函数那么需要是线程安全的函数。
2.不区分直接调用导致的后果:
如果对可重入函数,线程安全函数不做处理,那么会导致不可预料的后果:
那在多线程调用的情况下,可能导致的后果是显而易见的——共享变量的值由于不同线程的访问,可能发生不可预料的变化,进而导致程序的错误,甚至崩溃。
“线程安全”是一个什么概念?
以前常听高手告诫MFC对象不要跨线程使用,因为MFC不是线程安全的。比如CWnd对象不要跨线程使用,可以用窗口句柄(HWND)代替。 CSocket/CAsyncSocket对象不要跨线程使用,用SOCKET句柄代替.那么到底什么是线程安全呢?什么时候需要考虑?如果程序涉及到多 线程的话,就应该考虑线程安全问题。比如说设计的接口,将来需要在多线程环境中使用,或者需要跨线程使用某个对象时,这个就必须考虑了。关于线程安全也没 什么权威定义。在这里我只说说我的理解:所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不
用考虑同步的问题。
&&& 一般而言“线程安全”由多线程对共享资源的访问引起。如果调用某个接口时需要我们自己采取同步措施来保护该接口访问的共享资源,则这样的接口不是线程安全 的.MFC和STL都不是线程安全的. 怎样才能设计出线程安全的类或者接口呢?
1)如果接口中访问的数据都属于私有数据,那么这样的接口是线程安全的.
2)或者几个接口对共享数据都是只读操作,那么这 样的接口也是线程安全的
3)如果多个接口之间有共享数据,而且有读有写的话,如果设计者自己采取了同步措施,调用者不需要考虑数据同步问题,则这样的接口是 线程安全的,否则不是线程安全的。
四、线程不安全函数例子-操作其它线程的界面控件出错
一个线程调用外部主线程的函数代码,实现回调的功能不知道行不行?
是可以的,会维护一份自己的栈和寄存器信息,但是系统一般对于线程安全的函数是可以调用的,线程不安全的函数是会运行时报错的(例如一个工作线程调用函数处理一个界面线程的控件,C#中可以用委托实现,C/C++中可以发送消息给界面线程由界面线程处理)。
线程间不安全函数调用失败原因:
因为控件是窗口,窗口是和显示器驱动相关的,因此窗口对象的写不是线程安全的,所以window的线程安全策略不允许子线程中对窗口这种线程不安全的对象进行写操作。最好的办法是发送消息,通知主线程更新控件内容。
MFC子线程中可以用发送消息给主线程实现对控件的操作,主线程通过消息映射函数,收到消息更新界面信息。
.NET中可以通过委托实现子线程对主线程控件的操作。
cocos2dx中是通过CPP引擎层主线程的更新,设置进度条百分比,然后回调到lua里面的函数实现界面控件的更新,而下载和解压线程是子线程,下载解压子线程通过发送消息到更新信息队列,主线程通过互斥锁实现访问更新队列。也就是通过子线程发送消息,主线程每帧去无阻塞的取消息,如果取到消息那么回调实现界面的更新。
cocos2dx中是纹理读取,xml/json文件读取,网络下载,网络消息接收中使用了多线程。
五、多线程的程序设计应该注意的内容
1、尽量少的使用全局变量、static变量做共享数据,尽量使用参数传递对象。被参数传递的对象,应该只包括必需的成员变量。所谓必需的成员变量,就是 必定会被多线程操作的。很多人图省事,会把this指针(可能是任意一个对象指针)当作线程参数传递,致使线程内部有过多的操作权限,对this中的参数 任意妄为。整个程序由一个人完成,可能会非常注意,不会出错,但只要一转手,程序就会面目全非。当两个线程同时操作一个成员变量的时候,程序就开始崩溃
了,更糟的是,这种错误很难被重现。(我就在郁闷这个问题,我们是几个人,把程序编成debug版,经过数天使用,才找到错误。而找到错误只是开始,因为 你要证明这个bug被修改成功了,也非常困难。)其实,线程间数据交互大多是单向的,在线程回调函数入口处,尽可能的将传入的数据备份到局部变量中(当 然,用于线程间通讯的变量不能这么处理),以后只对局部变量做处理,可以很好的解决这种问题。
2、在MFC中请慎用线程。因为MFC的框架假定你的消息处理都是在主线程中完成的。首先窗口句柄是属于线程的,如果拥有窗口句柄的线程退出了,如果另一 个线程处理这个窗口句柄,系统就会出现问题。而MFC为了避免这种情况的发生,使你在子线程中调用消息(窗口)处理函数时,就会不停的出Assert错 误,烦都烦死你。典型的例子就时CSocket,因为CSocket是使用了一个隐藏窗口实现了假阻塞,所以不可避免的使用了消息处理函数,如果你在子线
程中使用CSocket,你就可能看到assert的弹出了。
3、不要在不同的线程中同时注册COM组件。两个线程,一个注册1.ocx, 2.ocx, 3.ocx, 4. 而另一个则注册5.ocx, 6.ocx, 7.ocx, 8.ocx,结果死锁发生了,分别死在FreeLibrary和DllRegisterServer,因为这8个ocx是用MFC中做的,也可能是MFC 的Bug,但DllRegisterServer却死在GetModuleFileName里,而GetModuleFileName则是个API唉!如
果有过客看到,恰巧又知道其原因,请不吝赐教。
4、不要把线程搞的那么复杂。很多初学者,恨不能用上线程相关的所有的函数,这里互斥,那里等待,一会儿起线程,一会儿关线程的,比起goto语句有过之 而无不及。好的多线程程序,应该是尽量少的使用线程。这句话怎么理解呐,就是说尽量统一一块数据共享区存放数据队列,工作子线程从队列中取数据,处理,再 放回数据,这样才会模块化,对象化;而不是每个数据都起一个工作子线程处理,处理完了就关闭,写的时候虽然直接,等维护起来就累了。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:271656次
积分:5146
积分:5146
排名:第3747名
原创:249篇
转载:56篇
译文:10篇
评论:14条
(7)(14)(14)(6)(27)(1)(8)(2)(18)(32)(15)(12)(6)(21)(26)(40)(1)(4)(9)(22)(3)(4)(1)(1)(1)(1)(2)(1)(18)(1)一、异步和多线程有什么区别?
其实,异步是目的,而多线程是实现这个目的的方法。异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作就没有必要异步了),可以继续自顾自的处理它自己的事儿,不用干等着这个耗时操作返回。.Net中的这种异步编程模型,就简化了多线程编程,我们甚至都不用去关心Thread类,就可以做一个异步操作出来。
二、随着拥有多个硬线程CPU(超线程、双核)的普及,多线程和异步操作等并发程序设计方法也受到了更多的关注和讨论。本文主要是想探讨一下如何使用并发来最大化程序的性能。  多线程和异步操作的异同  多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。甚至有些时候我们就认为多线程和异步操作是等同的概念。但是,多线程和异步操作还是有一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别。  
异步操作的本质  所有的程序最终都会由计算机硬件来执行,所以为了更好的理解异步操作的本质,我们有必要了解一下它的硬件基础。 熟悉电脑硬件的朋友肯定对DMA这个词不陌生,硬盘、光驱的技术规格中都有明确DMA的模式指标,其实网卡、声卡、显卡也是有DMA功能的。DMA就是直 接内存访问的意思,也就是说,拥有DMA功能的硬件在和内存进行数据交换的时候可以不消耗CPU资源。只要CPU在发起数据传输时发送一个指令,硬件就开
始自己和内存交换数据,在传输完成之后硬件会触发一个中断来通知操作完成。这些无须消耗CPU时间的I/O操作正是异步操作的硬件基础。所以即使在DOS 这样的单进程(而且无线程概念)系统中也同样可以发起异步的DMA操作。  
线程的本质  线程不是一个计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。  
异步操作的优缺点  因为异步操作无须额外的线程负担,并且使用回调的方式进行处理,在设计良好的情况下,处理函数可以不必使用共享变量(即使无法完全不用,最起码可以减少 共享变量的数量),减少了死锁的可能。当然异步操作也并非完美无暇。编写异步操作的复杂程度较高,程序主要使用回调方式进行处理,与普通人的思维方式有些 初入,而且难以调试。  
多线程的优缺点  多线程的优点很明显,线程中的处理程序依然是顺序执行,符合普通人的思维习惯,所以编程简单。但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担。并且线程间的共享变量可能造成死锁的出现。  
适用范围  在了解了线程与异步操作各自的优缺点之后,我们可以来探讨一下线程和异步的合理用途。我认为:当需要执行I/O操作时,使用异步操作比使用线程+同步 I/O操作更合适。I/O操作不仅包括了直接的文件、网络的读写,还包括数据库操作、Web Service、HttpRequest以及.net Remoting等跨进程的调用。  而线程的适用范围则是那种需要长时间CPU运算的场合,例如耗时较长的图形处理和算法执行。但是往
往由于使用线程编程的简单和符合习惯,所以很多朋友往往会使用线程来执行耗时较长的I/O操作。这样在只有少数几个并发操作的时候还无伤大雅,如果需要处 理大量的并发操作时就不合适了。
异步调用与多线程 异步调用并不是要减少线程的开销, 它的主要目的是让调用方法的主线程不需要同步等待在这个函数调用上, 从而可以让主线程继续执行它下面的代码.与此同时, 系统会通过从ThreadPool中取一个线程来执行,帮助我们将我们要写/读的数据发送到网卡.由于不需要我们等待, 我们等于同时做了两件事情. 这个效果跟自己另外启动一个线程来执行等待方式的写操作是一样的.但是,
异步线程可以利用操作系统/.Net的线程池, 系统可以根据吞吐量动态的管理线程池的大小.
原文出处:/link?url=IOsSzBff3Gsswi0P-0ddhouF3wt75gpO7ir9e7rEfIv-RB19sazbdV2p4FN-RTPdH4VRIOE88M4VeNnrwI0WsgwCQY2TVFURVSlyxhKVhl7
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1812次
排名:千里之外查看: 5416|回复: 35
异步双核、同步双核
TA的每日心情擦汗 00:37签到天数: 3 天[LV.2]偶尔看看I
看看吧,共同进步~~了解内部原理就知道如何设置CPU频率了哦~~~~
转至百度知道
百度砖家:异步是指两个单核拼连,每个周期只能有一个核在工作,另一个核只能休眠。
cpu频率其实是一个振荡波,所谓1g只是指1秒里振荡了万次,因为cpu的电路是交流电的振荡电路。在每振荡一次的周期波形内,cpu只在峰顶才能运转计算执行指令,这时叫活跃期,谷底的时候,cpu没有足够的电流执行指令运算,这时叫休眠期。即每一个周期,一半是活跃期,一半是休眠期。1g频的cpu就是在1秒内经历了万次波顶的活跃期和峰底的休眠期。
做一个比喻,1秒等于1年的话,1ghz就相当这一年有万天,而对于单核,是一个人每一天只有上午是工作的,下午12个小时是休息的,即每天一半工作一半休息。
而对于异步双核,是两个人轮流工作,每一天上午有一个人工作,这一个下午休息,另一个人下午工作,上午休息,天天如此,两个人你工作我休息,我工作你休息。两个人永远不会见面,永远不会在一起休息或一起工作。
小白追问:对比同步双核是两个人,每天上午一起工作,下午一起休息,永远不会分开独立工作或休息。追问那就是说同一时间他的效率和单核一样(上午),同步双核在同一时间的效率将是异步的两倍?那这样异步双核不就等于单核加个班一天一直在工作一样,异步还有一个人偷懒完全不干活,那还有必要加个核?
百度砖家:回答第一,无论单双核,永远都根本不可能下午加班。
因为这是一个正弦波,每一个振荡周期,任何cpu都只能半天工作,半天休息,因为cpu电流就是交流电路,不是直流电路。你先去搞清什么是振荡波,什么是波形。所谓异步双核只是一个正弦波和一个余弦波叠加,同步双核只是两个同样的正弦波叠加。学过三角函数和物理的关于波的理论基本都能明白我在说什么。
所谓异步只是将两个cpu的活跃和休眠期错开。
每一个振荡周期,cpu永远都只能有一半是工作,另一半是不可能做任何工作。即任何cpu永远都不可能加班工作。
第二,异步双核的劣势是,在双线程下,每一次指令,每一个cpu都会有一次轮空。因为两个cpu是不能见面的,a醒来的时候,b的那部分工作还未完成交给a,b却已经睡了,a这次醒来只能是空转,醒着什么都做不了,只能等下一轮b醒来,完成后,在a的下一次醒来才能接手继续做下去。
对比同步双核,异步双核就多了两步,性能要做出80%折让,因为异步20%的周期是在空转,什么都做不了的。
随着线程越多,四线程下,异步4核要比同步4核多出4步,性能折让60%(异步4x1)或64%(异步2x2),因为40%或36%时候,异步4核是在空转。
也就是说异步双核的性能在双线程下和同步双核比是要打折的,越多核越多线程,折让越大,所以越高级cpu越不会采用这种笨办法。
这才是异步双核的缺陷,和什么偷懒根本没关系。
小白追问:那苹果的A5双核和i9220的Exynos 4210那个更好?小米的异步双核怎么会比很多同步双核跑分高(比如有时超i9100)答完这个给你加60分
百度砖家:回答第一,a5处理器其实就是三星exynos4210的cpu+sgx 543mp2的gpu。
而i9220的cpu不是exynos4210,是exynos可以看作1.4ghz的默频是1.2ghz。
由于a5的exynos4210降频到1ghz,纯粹看性能输出的话,a5的cpu比exynosghz低,仅比omap4430的1g,msmg好点。
但a5的gpu sgx543mp2,像素输出性能是目前现役机中最强的,是第二强的gpu——esynos4210的mali 400mp4的1.8倍,是8260的gpu adreno220的3.75倍,游戏特效和画质能力举世无双。除了德州仪器的omap5430的gpu——sgx544mp2,暂时还没其他能超过a5的这个gpu。
第二,不同系统,不能只看性能输出,还要看输出分量等。
的确论cpu性能输出的话,gs2的exynos4210的cpu性能理论值是6000mips,而a5的exynos4210的性能理论值是5000。但是苹果ios系统实质是没有后台,ios4.0以上都是伪后台,一旦转入后台就停止运行,cpu性能输出根本就不会花在后台上。而安卓是真后台,还是后台自动输送,cpu的性能输出要被自动后台吃掉。
对于gs2,实际的性能输出分量是,1g内存可用600m,可用3600mips输出分量,随后台不断增多,可用500m,跌到3000mips输出分量。后台越多,可用ram越小,实际能被使用的性能就越低。
而iphone4s是512m可用330mhz,输出分量3200mips,但是不用后台,不会越用越少。随着gs2的后台增多,iphone4s可用的性能输出分量自然就超过gs2了。如果是越狱,频率还会恢复成默频,这时性能输出分量更高。
第三,小米用的是msm8260,异步双核,1.5ghz,cpu性能理论值是6300,而htc的sensation是ghz,cpu性能理论值是5040。而exynos4210仅仅是1.2g下就已经是6000了!!!!
即,异步双核只能通过提高频率来应对同步双核,
即1.5g的异步双核=1.2g的同步双核,
而1.2g的异步双核=1g的同步双核。
但是1.4g的同步双核=1.67g的异步双核。
同频下,8260根本就不能和exynos,tegra2,omap44x0这些真双核对等。
在双线程下,异步双核还要做出80%的让步
在双线程下1.2g的exynosg的msm8260
1.4g的exynosg的msm8260
也就是说异步双核只能用比同步核更高的频率来弥补差距。
第四,msm8260是高通的,像vallemo,neocore本来就是高通的软件,自然对高通系有利。但在sunspider,linpack,coremark等反而不利异步。
安兔兔跑分还能做弊,通过提高i/o值,读入写出速度来提高跑分等手段做虚假分。
跑分只能作参考,不是全部。
闲着也是闲着,来点分分
看着看着我就打哈欠了~~~~~~~~
TA的每日心情慵懒 00:16签到天数: 3 天[LV.2]偶尔看看I
该用户从未签到
头晕了!!!!!
该用户从未签到
受教了,脑中不断在脑补正弦波哈哈、
该用户从未签到
头晕了!!!!!
该用户从未签到
感谢分享~原来8260也是缩水的
该用户从未签到
额,长见识了……
cpu里面电流居然是交流的???
该用户从未签到
可是我不能加分啊& &果断的要加分
该用户从未签到
说再多。。。还是苹果的速度要快
安卓的运行还是慢。。。每个程序都是需要独立运行空间,就好比一间公司分了好多个房间,每个程序需要多个开门动作一样。。。每个房间都做着不同的事情
苹果就像是一个开放的空间(一个大房间)不需要开门动作等。做事情就快了。另外是全部人一齐做一个事情。
这个比较好理解了吧
该用户从未签到
有点说不通的地方。
1.5G的8260比1G的8260跑分快了一千多,怎么也说不同了。posts - 591,&
comments - 0,&
trackbacks - 0
这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都看过,但是本文写作的重心就是所有的问题都会按照自己的理解回答一遍,不会去看网上的答案,因此可能有些问题讲的不对,能指正的希望大家不吝指教。
40个问题汇总
1、多线程有什么用?
一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓&知其然知其所以然&,&会用&只是&知其然&,&为什么用&才是&知其所以然&,只有达到&知其然知其所以然&的程度才可以说是把一个知识点运用自如。OK,下面说说我对这个问题的看法:
(1)发挥多核CPU的优势
随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的&多线程&那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程&同时&运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。
(2)防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
(3)便于建模
这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。
2、创建线程的方式
比较常见的一个问题了,一般就是两种:
(1)继承Thread类
(2)实现Runnable接口
至于哪个好,不用说肯定是后者好,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程也是设计模式6大原则的核心。
3、start()方法和run()方法的区别
只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行完毕之后,另外一个线程才可以执行其run()方法里面的代码。
4、Runnable接口和Callable接口的区别
有点深的问题了,也看出一个Java程序员学习知识的广度。
Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。
5、CyclicBarrier和CountDownLatch的区别
两个看上去有点像的类,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:
(1)CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行
(2)CyclicBarrier只能唤起一个任务,CountDownLatch可以唤起多个任务
(3)CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了
6、Volatile关键字的作用
一个非常重要的问题,是每个学习、应用多线程的Java程序员都必须掌握的。理解volatile关键字的作用的前提是要理解Java内存模型,这里就不讲Java内存模型了,可以参见第31点,volatile关键字的作用主要有两个:
(1)多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据
(2)代码底层执行不像我们看到的高级语言&-Java程序这么简单,它的执行是Java代码&&字节码&&根据字节码执行对应的C/C++代码&&C/C++代码被编译成汇编语言&&和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率
从实践角度而言,volatile的一个重要作用就是和CAS结合,保证了原子性,详细的可以参见java.util.concurrent.atomic包下的类,比如AtomicInteger。
7、什么是线程安全
又是一个理论的问题,各式各样的答案有很多,我给出一个个人认为解释地最好的:如果你的代码在多线程下执行和在单线程下执行永远都能获得一样的结果,那么你的代码就是线程安全的。
这个问题有值得一提的地方,就是线程安全也是有几个级别的:
(1)不可变
像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
(2)绝对线程安全
不管运行时环境如何,调用者都不需要额外的同步措施。要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet
(3)相对线程安全
相对线程安全也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但也仅限于此,如果有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制。
(4)线程非安全
这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程非安全的类
8、Java中如何获取到线程dump文件
死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:
(1)获取到线程的pid,可以通过使用jps命令,在Linux环境下还可以使用ps -ef | grep java
(2)打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid
另外提一点,Thread类提供了一个getStackTrace()方法也可以用于获取线程堆栈。这是一个实例方法,因此此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈,
9、一个线程如果出现了运行时异常会怎么样
如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放
10、如何在两个线程之间共享数据
通过在线程之间共享对象就可以了,然后通过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的
11、sleep方法和wait方法有什么区别
这个问题常问,sleep方法和wait方法都可以用来放弃CPU一定的时间,不同点在于如果线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器
12、生产者消费者模型的作用是什么
这个问题很理论,但是很重要:
(1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用
(2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约
13、ThreadLocal有什么用
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了
14、为什么wait()方法和notify()/notifyAll()方法要在同步块中被调用
这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先获得对象的锁
15、wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别
wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。
16、为什么要使用线程池
避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。
17、怎么检测一个线程是否持有对象监视器
我也是在网上看到一道多线程面试题才知道有方法可以判断某个线程是否持有对象监视器:Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着&某条线程&指的是当前线程。
18、synchronized和ReentrantLock的区别
synchronized是和if、else、for、while一样的关键字,ReentrantLock是类,这是二者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,可以被继承、可以有方法、可以有各种各样的类变量,ReentrantLock比synchronized的扩展性体现在几点上:
(1)ReentrantLock可以对获取锁的等待时间进行设置,这样就避免了死锁
(2)ReentrantLock可以获取各种锁的信息
(3)ReentrantLock可以灵活地实现多路通知
另外,二者的锁机制其实也是不一样的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操作的应该是对象头中mark word,这点我不能确定。
19、ConcurrentHashMap的并发度是什么
ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时可以有16条线程操作ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优势,任何情况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?
20、ReadWriteLock是什么
首先明确一下,不是说ReentrantLock不好,只是ReentrantLock某些时候有局限。如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。
因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
21、FutureTask是什么
这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。
22、Linux环境下如何查找哪个线程使用CPU最长
这是一个比较偏实践的问题,这种问题我觉得挺有意义的。可以这么做:
(1)获取项目的pid,jps或者ps -ef | grep java,这个前面有讲过
(2)top -H -p pid,顺序不能改变
这样就可以打印出当前的项目,每条线程占用CPU时间的百分比。注意这里打出的是LWP,也就是操作系统原生线程的线程号,我笔记本山没有部署Linux环境下的Java工程,因此没有办法截图演示,网友朋友们如果公司是使用Linux环境部署项目的话,可以尝试一下。
使用&top -H -p pid&+&jps pid&可以很容易地找到某条占用CPU高的线程的线程堆栈,从而定位占用CPU高的原因,一般是因为不当的代码操作导致了死循环。
最后提一点,&top -H -p pid&打出来的LWP是十进制的,&jps pid&打出来的本地线程号是十六进制的,转换一下,就能定位到占用CPU高的线程的当前线程堆栈了。
23、Java编程写一个会导致死锁的程序
第一次看到这个题目,觉得这是一个非常好的问题。很多人都知道死锁是怎么一回事儿:线程A和线程B相互等待对方持有的锁导致程序无限死循环下去。当然也仅限于此了,问一下怎么写一个死锁的程序就不知道了,这种情况说白了就是不懂什么是死锁,懂一个理论就完事儿了,实践中碰到死锁的问题基本上是看不出来的。
真正理解什么是死锁,这个问题其实不难,几个步骤:
(1)两个线程里面分别持有两个Object对象:lock1和lock2。这两个lock作为同步代码块的锁;
(2)线程1的run()方法中同步代码块先获取lock1的对象锁,Thread.sleep(xxx),时间不需要太多,50毫秒差不多了,然后接着获取lock2的对象锁。这么做主要是为了防止线程1启动一下子就连续获得了lock1和lock2两个对象的对象锁
(3)线程2的run)(方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,当然这时lock1的对象锁已经被线程1锁持有,线程2肯定是要等待线程1释放lock1的对象锁的
这样,线程1&P睡觉&睡完,线程2已经获取了lock2的对象锁了,线程1此时尝试获取lock2的对象锁,便被阻塞,此时一个死锁就形成了。代码就不写了,占的篇幅有点多,Java多线程7:死锁这篇文章里面有,就是上面步骤的代码实现。
24、怎么唤醒一个阻塞的线程
如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。
25、不可变对象对多线程有什么帮助
前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不需要进行额外的同步手段,提升了代码执行效率。
26、什么是多线程的上下文切换
多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。
27、如果你提交任务时,线程池队列已满,这时会发生什么
如果你使用的LinkedBlockingQueue,也就是无界队列的话,没关系,继续添加任务到阻塞队列中等待执行,因为LinkedBlockingQueue可以近乎认为是一个无穷大的队列,可以无限存放任务;如果你使用的是有界队列比方说ArrayBlockingQueue的话,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy。
28、Java中用到的线程调度算法是什么
抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。
29、Thread.sleep(0)的作用是什么
这个问题和上面那个问题是相关的,我就连在一起了。由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。
30、什么是自旋
很多synchronized里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行地非常快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
31、什么是Java内存模型
Java内存模型定义了一种多线程访问Java内存的规范。Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几部分内容:
(1)Java内存模型将内存分为了主内存和工作内存。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去
(2)定义了几个原子操作,用于操作主内存和工作内存中的变量
(3)定义了volatile变量的使用规则
(4)happens-before,即先行发生原则,定义了操作A必然先行发生于操作B的一些规则,比如在同一个线程内控制流前面的代码一定先行发生于控制流后面的代码、一个释放锁unlock的动作一定先行发生于后面对于同一个锁进行锁定lock的动作等等,只要符合这些规则,则不需要额外做同步措施,如果某段代码不符合所有的happens-before规则,则这段代码一定是线程非安全的
32、什么是CAS
CAS,全称为Compare and Set,即比较-设置。假设有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为B并返回true,否则什么都不做并返回false。当然CAS一定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,否则旧的预期值A对某条线程来说,永远是一个不会变的值A,只要某次CAS操作失败,永远都不可能成功。
33、什么是乐观锁和悲观锁
(1)乐观锁:就像它的名字一样,对于并发间操作产生的线程安全问题持乐观状态,乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-设置这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
(2)悲观锁:还是像它的名字一样,对于并发间操作产生的线程安全问题持悲观状态,悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。
34、什么是AQS
简单说一下AQS,AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。
如果说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS实际上以双向队列的形式连接所有的Entry,比方说ReentrantLock,所有等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。
AQS定义了对双向队列所有的操作,而只开放了tryLock和tryRelease方法给开发者使用,开发者可以根据自己的实现重写tryLock和tryRelease方法,以实现自己的并发功能。
35、单例模式的线程安全性
老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,我总结一下:
(1)饿汉式单例模式的写法:线程安全
(2)懒汉式单例模式的写法:非线程安全
(3)双检锁单例模式的写法:线程安全
36、Semaphore有什么作用
Semaphore就是一个信号量,它的作用是限制某段代码块的并发数。Semaphore有一个构造函数,可以传入一个int型整数n,表示某段代码最多只有n个线程可以访问,如果超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此可以看出如果Semaphore构造函数中传入的int型整数n=1,相当于变成了一个synchronized了。
37、Hashtable的size()方法中明明只有一条语句&return count&,为什么还要做同步?
这是我之前的一个困惑,不知道大家有没有想过这个问题。某个方法中如果有多条语句,并且都在操作同一个类变量,那么在多线程环境下不加锁,势必会引发线程安全问题,这很好理解,但是size()方法明明只有一条语句,为什么还要加锁?
关于这个问题,在慢慢地工作、学习中,有了理解,主要原因有两点:
(1)同一时间只能有一条线程执行固定类的同步方法,但是对于类的非同步方法,可以多条线程同时访问。所以,这样就有问题了,可能线程A在执行Hashtable的put方法添加数据,线程B则可以正常调用size()方法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数据,但是没有对size++,线程B就已经读取size了,那么对于线程B来说读取到的size一定是不准确的。而给size()方法加了同步之后,意味着线程B调用size()方法只有在线程A调用put方法完毕之后才可以调用,这样就保证了线程安全性
(2)CPU执行代码,执行的不是Java代码,这点很关键,一定得记住。Java代码最终是被翻译成汇编代码执行的,汇编代码才是真正可以和硬件电路交互的代码。即使你看到Java代码只有一行,甚至你看到Java代码编译之后生成的字节码也只有一行,也不意味着对于底层来说这句语句的操作只有一个。一句&return count&假设被翻译成了三句汇编语句执行,完全可能执行完第一句,线程就切换了。
38、线程类的构造方法、静态块是被哪个线程调用的
这是一个非常刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。
如果说上面的说法让你感到困惑,那么我举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:
(1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的
(2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的
39、同步方法和同步块,哪个是更好的选择
同步块,这意味着同步块之外的代码是异步执行的,这比同步整个方法更提升代码的效率。请知道一条原则:同步的范围越少越好。
借着这一条,我额外提一点,虽说同步的范围越少越好,但是在Java虚拟机中还是存在着一种叫做锁粗化的优化方法,这种方法就是把同步范围变大。这是有用的,比方说StringBuffer,它是一个线程安全的类,自然最常用的append()方法是一个同步方法,我们写代码的时候会反复append字符串,这意味着要进行反复的加锁-&解锁,这对性能不利,因为这意味着Java虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,因此Java虚拟机会将多次append方法调用的代码进行一个锁粗化的操作,将多次的append的操作扩展到append方法的头尾,变成一个大的同步块,这样就减少了加锁&&解锁的次数,有效地提升了代码执行的效率。
40、高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
这是我在并发编程网上看到的一个问题,把这个问题放在最后一个,希望每个人都能看到并且思考一下,因为这个问题非常好、非常实际、非常专业。关于这个问题,个人看法是:
(1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
(2)并发不高、任务执行时间长的业务要区分开看:
a)假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务
b)假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换
(3)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦。
阅读(...) 评论()

我要回帖

更多关于 高通 异步双核 的文章

 

随机推荐