- N +

多線程編程核心技術(用于執行線程核心方法)

很多朋友對于多線程編程核心技術和用于執行線程核心方法不太懂,今天就由小編來為大家分享,希望可以幫助到大家,下面一起來看看吧!

jvm與并發編程先學哪個比較好

個人感覺沒有啥先后順序,不過先學jvm你能讀明白字節碼,一些多線程的例子反編譯過來看字節碼能明白一些事,比如監視器怎么加的。或者你知道了jvm內存幾個區域,有的地方不涉及到共享問題,也不會線程不安全。多線程可以看看java高并發程序設計這本書。jvm可以看看深入java虛擬機這本書。有條件的話,經常用生產上的機器解決一些問題,比如內存滿了用jmap和mat分析,這樣能加強你對jvm熟悉,同時也可以對生產環境進行jvm調優。如果出現應用卡死,可能有死鎖,大事物,死循環這樣的,你可以用jstack和jstat去分析線程棧,會學到一些多線程的知識。同時你可以用gceasy這個在線工具把你的堆棧上傳上去,有個可視化的分析。另外阿里的arthas是個調試神器,jprofiler可以可視化很多東西。xxfox也是個在線學習jvm的好網站。

如果你愿意的話,當你想深入線程的話,可以看看posix線程規范pthread和linux內核的一些知識,能加強你對java線程的理解以及java內存分配的理解。

除了書,推薦你極客時間的java并發編程實戰,深入拆解jvm虛擬機。如果你還想對jvm再深入的話,可以看看openjdk源碼,需要有c的底子,還有自己動手寫java虛擬機這本書。

并發編程是什么意思

所謂并發編程是指在一臺處理器上“同時”處理多個任務。并發是在同一實體上的多個事件。多個事件在同一時間間隔發生。

多線程編程需要注意的幾點

1、不要在子線程操作UI控件2、如果你操作了,也絕對不能調用UpdateData來更新界面,否則程序Crash3、這一條建立在第一條基礎上---你在子線程操作UI控件,不可以讓主線程等待某些條件(如等待子線程關閉,而子線程正在操作UI、等待進入臨界區,而子線程已經進入,并且操作UI),否則會出現假死..

.4、最好方案:子線程操作數據,完成之后,通知主線程進行更新....

c/c++如何進行網絡編程、多線程編程

每個平臺有自己的實現而已,大體意思都一編程先要了解邏輯和思想,至于api那只是每個平臺為了實現功能提供的接口而已。

比如網絡編程,你要知道什么是阻塞,非阻塞,同步,異步的概念,了解了這些以后,再去關注你想學習的系統,比如linux下的非阻塞模型,select,poll,epoll

比如windows下的select,iocp

再比如多線程,你要了解什么是多線程,什么是鎖,什么是線程同步,知道可這些以后你才能了解如何創建線程,什么情況下加什么鎖等等

了解思想是第一步,api是第二步

其實c++11已經在多線程方面有很大改善,可以做到跨平臺

還有boost學下其中的asio那么網絡編程也能實現跨平臺

如何解決秒殺編程高并發問題

高并發問題

就是指在同一個時間點,有大量用戶同時訪問URL地址,比如淘寶雙11都會產生高并發。

高并發帶來的后果

服務端??導致站點服務器、DB服務器資源被占滿崩潰。??數據的存儲和更新結果和理想的設計不一致。用戶角度??尼瑪,網站這么卡,刷新了還這樣,垃圾網站,不玩了

二:分析阻礙服務速度的原因1:事物行級鎖的等待

java的事務管理機制會限制在一次commit之前,下一個用戶線程是無法獲得鎖的,只能等待

2:網絡延遲

3:JAVA的自動回收機制(GC)

三:處理高并發的常見方法

1:首先可以將靜態資源放入CDN中,減少后端服務器的訪問

2:訪問數據使用Redis進行緩存

3:使用Negix實現負載均衡

4:數據庫集群與庫表散列

四:實戰優化秒殺系統

1:分析原因

當用戶在想秒殺時,秒殺時間未到,用戶可能會一直刷新頁面,獲取系統時間和資源(A:此時會一直訪問服務器),當時間到了,大量用戶同時獲取秒殺接口API(B),獲取API之后執行秒殺(C),指令傳輸到各地服務器,服務器執行再將傳遞到中央數據庫執行(D),服務器啟用事務執行減庫存操作,在服務器端JAVA執行過程中,可能因為JAVA的自動回收機制,還需要一部分時間回收內存(E)。

2:優化思路:

面對上面分析可能會影響的過程,我們可以進行如下優化

A:我們可以將一些靜態的資源放到CDN上,這樣可以減少對系統服務器的請求

B:對于暴露秒殺接口,這種動態的無法放到CDN上,我們可以采用Redis進行緩存

request——>Redis——>MySQL

C:數據庫操作,對于MYSQL的執行速度大約可以達到1秒鐘40000次,影響速度的還是因為行級鎖,我們應盡可能減少行級鎖持有時間。

DE:對于數據庫來說操作可以說是相當快了,我們可以將指令放到MYSQL數據庫上去執行,減少網絡延遲以及服務器GC的時間。

3:具體實現

3.1:使用Redis進行緩存

引入redis訪問客戶端Jedis

1<!--redis客戶端:Jedis-->2<dependency>3<groupId>redis.clients</groupId>4<artifactId>jedis</artifactId>5<version>2.7.3</version>6</dependency>

優化暴露秒殺接口:對于SecviceImpl中exportSeckillUrl方法的優化,偽代碼如下

getfromcache//首先我們要從Redis中獲取需要暴露的URL

ifnull//如果從Redis中獲取的為空

getdb//那么我們就訪問MYSQL數據庫進行獲取

putcache//獲取到后放入Redis中

elselocgoin//否則,則直接執行

我們一般不能直接訪問Redis數據庫,首先先建立數據訪問層RedisDao,RedisDao中需要提供兩個方法,一個是getSeckill和putSeckill

在編寫這兩個方法時還需要注意一個問題,那就是序列化的問題,Redis并沒有提供序列化和反序列化,我們需要自定義序列化,我們使用protostuff進行序列化與反序列化操作

引入protostuff依賴包

1<!--protostuff序列化依賴-->2<dependency>3<groupId>com.dyuproject.protostuff</groupId>4<artifactId>protostuff-core</artifactId>5<version>1.0.8</version>6</dependency>7<dependency>8<groupId>com.dyuproject.protostuff</groupId>9<artifactId>protostuff-runtime</artifactId>10<version>1.0.8</version>11</dependency>

編寫數據訪問層RedisDao

1packagecom.xqc.seckill.dao.cache;23importorg.slf4j.Logger;4importorg.slf4j.LoggerFactory;56importcom.dyuproject.protostuff.LinkedBuffer;7importcom.dyuproject.protostuff.ProtostuffIOUtil;8importcom.dyuproject.protostuff.runtime.RuntimeSchema;9importcom.xqc.seckill.entity.Seckill;1011importredis.clients.jedis.Jedis;12importredis.clients.jedis.JedisPool;1314/**15*Redis緩存優化16*17*@authorACang(xqc)18*19*/20publicclassRedisDao{21privatefinalLoggerlogger=LoggerFactory.getLogger(this.getClass());2223privatefinalJedisPooljedisPool;2425publicRedisDao(Stringip,intport){26jedisPool=newJedisPool(ip,port);27}2829privateRuntimeSchema<Seckill>schema=RuntimeSchema.createFrom(Seckill.class);3031publicSeckillgetSeckill(longseckillId){32//redis操作邏輯33try{34Jedisjedis=jedisPool.getResource();35try{36Stringkey="seckill:"+seckillId;37//并沒有實現內部序列化操作38//get->byte[]->反序列化->Object(Seckill)39//采用自定義序列化40//protostuff:pojo.41byte[]bytes=jedis.get(key.getBytes());42//緩存中獲取到bytes43if(bytes!=null){44//空對象45Seckillseckill=schema.newMessage();46ProtostuffIOUtil.mergeFrom(bytes,seckill,schema);47//seckill被反序列化48returnseckill;49}50}finally{51jedis.close();52}53}catch(Exceptione){54logger.error(e.getMessage(),e);55}56returnnull;57}5859publicStringputSeckill(Seckillseckill){60//setObject(Seckill)->序列化->byte[]61try{62Jedisjedis=jedisPool.getResource();63try{64Stringkey="seckill:"+seckill.getSeckillId();65byte[]bytes=ProtostuffIOUtil.toByteArray(seckill,schema,66LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));67//超時緩存68inttimeout=60*60;//1小時69Stringresult=jedis.setex(key.getBytes(),timeout,bytes);70returnresult;71}finally{72jedis.close();73}74}catch(Exceptione){75logger.error(e.getMessage(),e);76}7778returnnull;79}808182}

優化ServiceImpl的exportSeckillUrl的方法

1publicExposerexportSeckillUrl(longseckillId){2//優化點:緩存優化:超時的基礎上維護一致性3//1:訪問redis4Seckillseckill=redisDao.getSeckill(seckillId);5if(seckill==null){6//2:訪問數據庫7seckill=seckillDao.queryById(seckillId);8if(seckill==null){9returnnewExposer(false,seckillId);10}else{11//3:放入redis12redisDao.putSeckill(seckill);13}14}1516DatestartTime=seckill.getStartTime();17DateendTime=seckill.getEndTime();18//系統當前時間19DatenowTime=newDate();20if(nowTime.getTime()<startTime.getTime()21||nowTime.getTime()>endTime.getTime()){22returnnewExposer(false,seckillId,nowTime.getTime(),startTime.getTime(),23endTime.getTime());24}25//轉化特定字符串的過程,不可逆26Stringmd5=getMD5(seckillId);27returnnewExposer(true,md5,seckillId);28}2930privateStringgetMD5(longseckillId){31Stringbase=seckillId+"/"+salt;32Stringmd5=DigestUtils.md5DigestAsHex(base.getBytes());33returnmd5;34}

3.2并發優化:

在執行秒殺操作死,正常的執行應該如下:先減庫存,并且得到行級鎖,再執行插入購買明細,然后再提交釋放行級鎖,這個時候行級鎖鎖住了其他一些操作,我們可以進行如下優化,這時只需要延遲一倍。

修改executeSeckill方法如下:

1@Transactional2/**3*使用注解控制事務方法的優點:4*1:開發團隊達成一致約定,明確標注事務方法的編程風格。5*2:保證事務方法的執行時間盡可能短,不要穿插其他網絡操作RPC/HTTP請求或者剝離到事務方法外部.6*3:不是所有的方法都需要事務,如只有一條修改操作,只讀操作不需要事務控制.7*/8publicSeckillExecutionexecuteSeckill(longseckillId,longuserPhone,Stringmd5)9throwsSeckillException,RepeatKillException,SeckillCloseException{10if(md5==null||!md5.equals(getMD5(seckillId))){11thrownewSeckillException("seckilldatarewrite");12}13//執行秒殺邏輯:減庫存+記錄購買行為14DatenowTime=newDate();1516try{17//記錄購買行為18intinsertCount=successKilledDao.insertSuccessKilled(seckillId,userPhone);19//唯一:seckillId,userPhone20if(insertCount<=0){21//重復秒殺22thrownewRepeatKillException("seckillrepeated");23}else{24//減庫存,熱點商品競爭25intupdateCount=seckillDao.reduceNumber(seckillId,nowTime);26if(updateCount<=0){27//沒有更新到記錄,秒殺結束,rollback28thrownewSeckillCloseException("seckillisclosed");29}else{30//秒殺成功commit31SuccessKilledsuccessKilled=successKilledDao.queryByIdWithSeckill(seckillId,userPhone);32returnnewSeckillExecution(seckillId,SeckillStatEnum.SUCCESS,successKilled);33}34}35}catch(SeckillCloseExceptione1){36throwe1;37}catch(RepeatKillExceptione2){38throwe2;39}catch(Exceptione){40logger.error(e.getMessage(),e);41//所有編譯期異常轉化為運行期異常42thrownewSeckillException("seckillinnererror:"+e.getMessage());43}44}

3.3深度優化:(存儲過程)

定義一個新的接口,使用存儲過程執行秒殺操作

1/**2*執行秒殺操作by存儲過程3*@paramseckillId4*@paramuserPhone5*@parammd56*/7SeckillExecutionexecuteSeckillProcedure(longseckillId,longuserPhone,Stringmd5);

實現executeSeckillProcedure方法

1publicSeckillExecutionexecuteSeckillProcedure(longseckillId,longuserPhone,Stringmd5){2if(md5==null||!md5.equals(getMD5(seckillId))){3returnnewSeckillExecution(seckillId,SeckillStatEnum.DATA_REWRITE);4}5DatekillTime=newDate();6Map<String,Object>map=newHashMap<String,Object>();7map.put("seckillId",seckillId);8map.put("phone",userPhone);9map.put("killTime",killTime);10map.put("result",null);11//執行存儲過程,result被復制12try{13seckillDao.killByProcedure(map);14//獲取result15intresult=MapUtils.getInteger(map,"result",-2);16if(result==1){17SuccessKilledsk=successKilledDao.18queryByIdWithSeckill(seckillId,userPhone);19returnnewSeckillExecution(seckillId,SeckillStatEnum.SUCCESS,sk);20}else{21returnnewSeckillExecution(seckillId,SeckillStatEnum.stateOf(result));22}23}catch(Exceptione){24logger.error(e.getMessage(),e);25returnnewSeckillExecution(seckillId,SeckillStatEnum.INNER_ERROR);2627}2829}

編寫SeckillDao實現有存儲過程執行秒殺的邏輯

1/**2*使用存儲過程執行秒殺3*@paramparamMap4*/5voidkillByProcedure(Map<String,Object>paramMap);

在Mybatis中使用

1<!--mybatis調用存儲過程-->2<selectid="killByProcedure"statementType="CALLABLE">3callexecute_seckill(4#{seckillId,jdbcType=BIGINT,mode=IN},5#{phone,jdbcType=BIGINT,mode=IN},6#{killTime,jdbcType=TIMESTAMP,mode=IN},7#{result,jdbcType=INTEGER,mode=OUT}8)9</select>

在Controller層使用

1@ResponseBody2publicSeckillResult<SeckillExecution>execute(@PathVariable("seckillId")LongseckillId,3@PathVariable("md5")Stringmd5,4@CookieValue(value="killPhone",required=false)Longphone){5//springmvcvalid6if(phone==null){7returnnewSeckillResult<SeckillExecution>(false,"未注冊");8}9SeckillResult<SeckillExecution>result;10try{11//存儲過程調用.12SeckillExecutionexecution=seckillService.executeSeckillProcedure(seckillId,phone,md5);13returnnewSeckillResult<SeckillExecution>(true,execution);14}catch(RepeatKillExceptione){15SeckillExecutionexecution=newSeckillExecution(seckillId,SeckillStatEnum.REPEAT_KILL);16returnnewSeckillResult<SeckillExecution>(true,execution);17}catch(SeckillCloseExceptione){18SeckillExecutionexecution=newSeckillExecution(seckillId,SeckillStatEnum.END);19returnnewSeckillResult<SeckillExecution>(true,execution);20}catch(Exceptione){21logger.error(e.getMessage(),e);22SeckillExecutionexecution=newSeckillExecution(seckillId,SeckillStatEnum.INNER_ERROR);23returnnewSeckillResult<SeckillExecution>(true,execution);24}25}

至此,此系統的代碼優化工作基本完成。但是在部署時可以將其更加優化,我們一般會使用如下架構

CPU的多核心和編程里面的多線程有關聯么

固定的核數,由系統分配N多線程。

如果只有一個核,所有線程都在一個核上跑,這是按時間片調度的。嚴格意義上就沒有并發。

而多個核才是真正并發。雖然不能讓所有線程同時跑,但最多還是能讓CPU核心數的線程一起并發。

OK,本文到此結束,希望對大家有所幫助。

返回列表
上一篇:
下一篇: