大家好,今天給各位分享getinstance的一些知識,其中也會對getdeclaredmethod和getmethod進行解釋,文章篇幅可能偏長,如果能碰巧解決你現在面臨的問題,別忘了關注本站,現在就馬上開始吧!
c++的單例模式為什么不直接全部使用static,而是非要實例化一個對象
有時候對象需要初始化,需要在運行過程中才能獲得一些信息,而static函數就不方便獲得這些信息,需要單獨調用一次initiative函數,并且如果此對象有一些狀態需要保存,用static變量也不是很方便。
volatile關鍵字是什么
主要從以下三點講解volatile關鍵字:
volatile關鍵字是什么?volatile關鍵字能解決什么問題?使用場景是什么?volatile關鍵字實現的原理?volatile關鍵字是什么?在Sun的JDK官方文檔是這樣形容volatile的:
TheJavaprogramminglanguageprovidesasecondmechanism,volatilefields,thatismoreconvenientthanlockingforsomepurposes.Afieldmaybedeclaredvolatile,inwhichcasetheJavaMemoryModelensuresthatallthreadsseeaconsistentvalueforthevariable.也就是說,如果一個變量加了volatile關鍵字,就會告訴編譯器和JVM的內存模型:這個變量是對所有線程共享的、可見的,每次JVM都會讀取最新寫入的值并使其最新值在所有CPU可見。volatile可以保證線程的可見性并且提供了一定的有序性,但是無法保證原子性。在JVM底層volatile是采用內存屏障來實現的。
通過這段話,我們可以知道volatile有兩個特性:
保證可見性、不保證原子性
禁止指令重排序原子性和可見性原子性是指一個操作或多個操作要么全部執行并且執行的過程不會被任何因素打斷,要么都不執行。性質和數據庫中事務一樣,一組操作要么都成功,要么都失敗。看下面幾個簡單例子來理解原子性:
i==0;//1
j=i;//2
i++;//3
i=j+1;//4
在看答案之前,可以先思考一下上面四個操作,哪些是原子操作?哪些是非原子操作?
答案揭曉:
1——是:在Java中,對基本數據類型的變量賦值操作都是原子性操作(Java有八大基本數據類型,分別是byte,short,int,long,char,float,double,boolean)
2——不是:包含兩個動作:讀取i值,將i值賦值給j
3——不是:包含了三個動作:讀取i值,i+1,將i+1結果賦值給i
4——不是:包含了三個動作:讀取j值,j+1,將j+1結果賦值給i
也就是說,只有簡單的讀取、賦值(而且必須是將數字賦值給某個變量,變量之間的相互賦值不是原子操作)才是原子操作。
注:由于以前的操作系統是32位,64位數據(long型,double型)在Java中是8個字節表示,一共占用64位,因此需要分成兩次操作采用完成一個變量的賦值或者讀取操作。隨著64位操作系統越來越普及,在64位的HotSpotJVM實現中,對64位數據(long型,double型)做原子性處理(由于JVM規范沒有明確規定,不排除別的JVM實現還是按照32位的方式處理)。
在單線程環境中我們可以認為上述步驟都是原子性操作,但是在多線程環境下,Java只保證了上述基本數據類型的賦值操作是原子性的,其他操作都有可能在運算過程中出現錯誤。為此在多線程環境下為了保證一些操作的原子性引入了鎖和synchronized等關鍵字。
上面說到volatile關鍵字保證了變量的可見性,不保證原子性。原子性已經說了,下面說下可見性。
可見性其實和Java內存模型的設定有關:Java內存模型規定所有的變量都是存在主存(線程共享區域)當中,每個線程都有自己的工作內存(私有內存)。線程對變量的所有操作都必須在工作內存中進行,而不直接對主存進行操作。并且每個線程不能訪問其他線程的工作內存。
舉個簡單栗子:
比如上面i++操作,在Java中,執行i++語句:
執行線程首先從主存中讀取i(原始值)到工作內存中,然后在工作內存中執行運算+1操作(主存的i值未變),最后將運算結果刷新到主存中。
數據運算是在執行線程的私有內存中進行的,線程執行完運算后,并不一定會立即將運算結果刷新到主存中(雖然最后一定會更新主存),刷新到主存動作是由CPU自行選擇一個合適的時間觸發的。假設數值未更新到主存之前,當其他線程去讀取時(而且優先讀取的是工作內存中的數據而非主存),此時主存中可能還是原來的舊值,就有可能導致運算結果出錯。
以下代碼是測試代碼:
packagecom.wupx.test;/**
*@authorwupx
*@date2019/10/31
*/
publicclassVolatileTest{
privatebooleanflag=false;
classThreadOneimplementsRunnable{
@Override
publicvoidrun(){
while(!flag){
System.out.println("執行操作");
try{
Thread.sleep(1000L);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
System.out.println("任務停止");
}
}
classThreadTwoimplementsRunnable{
@Override
publicvoidrun(){
try{
Thread.sleep(2000L);
System.out.println("flag狀態改變");
flag=true;
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
}
publicstaticvoidmain(String[]args){
VolatileTesttestVolatile=newVolatileTest();
Threadthread1=newThread(testVolatile.newThreadOne());
Threadthread2=newThread(testVolatile.newThreadTwo());
thread1.start();
thread2.start();
}
}
上述結果有可能在線程2執行完flag=true之后,并不能保證線程1中的while能立即停止循環,原因在于flag狀態首先是在線程2的私有內存中改變的,刷新到主存的時機不固定,而且線程1讀取flag的值也是在自己的私有內存中,而線程1的私有內存中flag仍未false,這樣就有可能導致線程仍然會繼續while循環。運行結果如下:
執行操作執行操作
執行操作
flag狀態改變
任務停止
避免上述不可預知問題的發生就是用volatile關鍵字修飾flag,volatile修飾的共享變量可以保證修改的值會在操作后立即更新到主存里面,當有其他線程需要操作該變量時,不是從私有內存中讀取,而是強制從主存中讀取新值。即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
指令重排序一般來說,處理器為了提高程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行先后順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。
比如下面的代碼
inti=0;
booleanflag=false;
i=1;//1
flag=true;//2
代碼定義了一個int型變量,定義了一個boolean類型變量,然后分別對兩個變量進行賦值操作。從代碼順序上看,語句1是在語句2前面的,那么JVM在真正執行這段代碼的時候會保證語句1一定會在語句2前面執行嗎?不一定,為什么呢?這里可能會發生指令重排序(InstructionReorder)。
語句1和語句2誰先執行對最終的程序結果并沒有影響,那么就有可能在執行過程中,語句2先執行而語句1后執行。
但是要注意,雖然處理器會對指令進行重排序,但是它會保證程序最終結果會和代碼順序執行結果相同,那么它靠什么保證的呢?再看下面一個例子:
inta=10;//1intr=2;//2
a=a+3;//3
r=a*a;//4
這段代碼執行的順序可能是1->2->3->4或者是2->1->3->4,但是3和4的執行順序是不會變的,因為處理器在進行重排序時是會考慮指令之間的數據依賴性,如果一個指令Instruction2必須用到Instruction1的結果,那么處理器會保證Instruction1會在Instruction2之前執行。
雖然重排序不會影響單個線程內程序執行的結果,但是多線程呢?下面看一個例子:
//線程1Stringconfig=initConfig();//1
booleaninited=true;//2
//線程2
while(!inited){
sleep();
}
doSomeThingWithConfig(config);
上面代碼中,由于語句1和語句2沒有數據依賴性,因此可能會被重排序。假如發生了重排序,在線程1執行過程中先執行語句2,而此時線程2會以為初始化工作已經完成,那么就會跳出while循環,去執行doSomeThingWithConfig(config)方法,而此時config并沒有被初始化,就會導致程序出錯。
從上面可以看出,指令重排序不會影響單個線程的執行,但是會影響到線程并發執行的正確性。
那么volatile關鍵字修飾的變量禁止重排序的含義是:
當程序執行到volatile變量的讀操作或者寫操作時,在其前面的操作肯定已經全部進行,且對后面的操作可見,在其后面的操作肯定還沒有進行
在進行指令優化時,不能將volatile變量之前的語句放在對volatile變量的讀寫操作之后,也不能把volatile變量后面的語句放到其前面執行舉個栗子:
x=0;//1
y=1;//2
volatilez=2;//3
x=4;//4
y=5;//5
變量z為volatile變量,那么進行指令重排序時,不會將語句3放到語句1、語句2之前,也不會將語句3放到語句4、語句5后面。但是語句1和語句2、語句4和語句5之間的順序是不作任何保證的,并且volatile關鍵字能保證,執行到語句3時,語句1和語句2必定是執行完畢了的,且語句1和語句2的執行結果是對語句3、語句4、語句5是可見的。
回到之前的例子:
//線程1
Stringconfig=initConfig();//1
volatilebooleaninited=true;//2
//線程2
while(!inited){
sleep();
}
doSomeThingWithConfig(config);
之前說這個例子提到有可能語句2會在語句1之前執行,那么就可能導致執行doSomThingWithConfig()方法時就會導致出錯。
這里如果用volatile關鍵字對inited變量進行修飾,則可以保證在執行語句2時,必定能保證config已經初始化完畢。
volatile應用場景synchronized關鍵字是防止多個線程同時執行一段代碼,那么就會很影響程序執行效率,而volatile關鍵字在某些情況下性能要優于synchronized,但是要注意volatile關鍵字是無法替代synchronized關鍵字的,因為volatile關鍵字無法保證操作的原子性。通常來說,使用volatile必須具備以下三個條件:
對變量的寫入操作不依賴變量的當前值,或者能確保只有單個線程更新變量的值
該變量不會與其他狀態變量一起納入不變性條件中在訪問變量時不需要加鎖上面的三個條件只需要保證是原子性操作,才能保證使用volatile關鍵字的程序在高并發時能夠正確執行。建議不要將volatile用在getAndOperate場合,僅僅set或者get的場景是適合volatile的。
常用的兩個場景是:
狀態標記量
volatilebooleanflag=false;
while(!flag){
doSomething();
}
publicvoidsetFlag(){
flag=true;
}
volatilebooleaninited=false;
//線程1
context=loadContext();
inited=true;
//線程2
while(!inited){
sleep();
}
doSomethingwithconfig(context);
DCL雙重校驗鎖-單例模式publicclassSingleton{
privatevolatilestaticSingletoninstance=null;
privateSingleton(){
}
/**
*當第一次調用getInstance()方法時,instance為空,同步操作,保證多線程實例唯一
*當第一次后調用getInstance()方法時,instance不為空,不進入同步代碼塊,減少了不必要的同步
*/
publicstaticSingletongetInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance=newSingleton();
}
}
}
returninstance;
}
}
使用volatile的原因在上面解釋重排序時已經講過了。主要在于instance=newSingleton(),這并非是一個原子操作,在JVM中這句話做了三件事情:
給instance分配內存
調用Singleton的構造函數來初始化成員變量將instance對象指向分配的內存庫存空間(執行完這步instance就為非null了)但是JVM即時編譯器中存在指令重排序的優化,也就是說上面的第二步和第三步順序是不能保證的,最終的執行順序可能是1-2-3,也可能是1-3-2。如果是后者,線程1在執行完3之后,2之前,被線程2搶占,這時instance已經是非null(但是并沒有進行初始化),所以線程2返回instance使用就會報空指針異常。
volatile特性是如何實現的呢?前面講述了關于volatile關鍵字的一些使用,下面我們來探討一下volatile到底如何保證可見性和禁止指令重排序的。
在《深入理解Java虛擬機》這本書中說道:
觀察加入volatile關鍵字和沒有加入volatile關鍵字時所生成的匯編代碼發現,加入volatile關鍵字時,會多出一個lock前綴指令。接下來舉個栗子:
volatile的Integer自增(i++),其實要分成3步:
讀取volatile變量值到local
增加變量的值把local的值寫回,讓其它的線程可見這3步的JVM指令為:
mov0xc(%r10),%r8d;Load
inc%r8d;Increment
mov%r8d,0xc(%r10);Store
lockaddl$0x0,(%rsp);StoreLoadBarrier
lock前綴指令實際上相當于一個內存屏障(也叫內存柵欄),內存屏障會提供3個功能:
它確保指令重排序時不會把其后面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的后面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成(滿足禁止重排序)
它會強制將對緩存的修改操作立即寫入主存(滿足可見性)如果是寫操作,它會導致其他CPU中對應的緩存行無效(滿足可見性)volatile變量規則是happens-before(先行發生原則)中的一種:對一個變量的寫操作先行發生于后面對這個變量的讀操作。(該特性可以很好解釋DCL雙重檢查鎖單例模式為什么使用volatile關鍵字來修飾能保證并發安全性)
總結變量聲明為volatile類型時,編譯器與運行時都會注意到這個變量是共享的,不會將該變量上的操作與其他內存操作一起重排序。volatile變量不會被緩存在寄存器或者對其他處理器不可見的地方,因此在讀取volatile類型的變量時總會返回最新寫入的值。
在訪問volatile變量時不會執行加鎖操作,也就不會使執行線程阻塞,因此volatile變量是比sychronized關鍵字更輕量級的同步機制。
加鎖機制既可以確保可見性和原子性,而volatile變量只能確保可見性。
想了解更多Java相關,百度搜索圈T社區www.aiquanti.com,免費視頻教程。純干貨
exec怎么添加底層水印
需要itext2.1.5,以下是對pdf加水印的代碼,包括文字水印和圖片水印publicintfileCopy(StringsrcPath,StringdestPath){FileOutputStreamfos=null;FileInputStreamfis=null;try{fos=newFileOutputStream(destPath);fis=newFileInputStream(srcPath);byte[]buffer=newbyte[1024];intlen=0;while((len=fis.read(buffer))>0){fos.write(buffer,0,len);}return1;}catch(FileNotFoundExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}finally{try{fis.close();fos.flush();fos.close();}catch(IOExceptione){//TODOAuto-generatedcatchblocke.printStackTrace();}}return0;}/***為pdf文件加文字水印**@paramsrcPath*源文件路徑*@paramdestPath*目標文件路徑*@paramwaterText*水印文字*@throwsDocumentException*@throwsIOException*/publicvoidwordWaterMark(StringsrcPath,StringdestPath,StringwaterText)throwsDocumentException,IOException{intresult=fileCopy(srcPath,destPath);if(result==1){//待加水印的文件PdfReaderreader=newPdfReader(destPath);//加完水印的文件PdfStamperstamper=newPdfStamper(reader,newFileOutputStream(srcPath));inttotal=reader.getNumberOfPages()+1;PdfContentBytecontent;//設置字體BaseFontbase=BaseFont.createFont(fontPath,BaseFont.IDENTITY_H,BaseFont.NOT_EMBEDDED);//水印文字intj=waterText.length();//文字長度charc=0;inthigh=0;//高度//循環對每頁插入水印for(inti=1;i<total;i++){//水印的起始high=60;content=stamper.getUnderContent(i);PdfGStategs=newPdfGState();gs.setFillOpacity(0.1f);//設置透明度為0.2content.setGState(gs);//開始content.beginText();//設置顏色//content.setColorFill(newColor());//設置字體及字號content.setFontAndSize(base,88);//設置起始位置content.setTextMatrix(120,333);//開始寫入水印for(intk=0;k<j;k++){content.setTextRise(high);c=waterText.charAt(k);content.showText(c+"");high+=20;}content.endText();}stamper.close();System.out.println("添加成功++++++++++++++++++++++++++++++++++++++++++");}else{System.out.println("復制pdf失敗====================");}}publicvoidpicWaterMark(StringsrcPath,StringdestPath,StringimageFilePath)throwsDocumentException,IOException{intresult=fileCopy(srcPath,destPath);if(result==1){//待加水印的文件PdfReaderreader=newPdfReader(destPath);//加完水印的文件PdfStamperstamper=newPdfStamper(reader,newFileOutputStream(srcPath));Imageimg=Image.getInstance(imageFilePath);img.setAbsolutePosition(50,400);//坐標img.setRotation(20);//旋轉弧度img.setRotationDegrees(45);//旋轉角度//image.scaleAbsolute(200,100);//自定義大小img.scalePercent(50);//依照比例縮放intpageSize=reader.getNumberOfPages();for(inti=1;i<=pageSize;i++){PdfContentByteunder=stamper.getUnderContent(i);under.addImage(img);PdfGStategs=newPdfGState();gs.setFillOpacity(0.2f);//設置透明度為0.2under.setGState(gs);}stamper.close();//關閉System.out.println("添加成功++++++++++++++++++++++++++++++++++++++++++");}else{System.out.println("復制pdf失敗====================");}}linux下轉pdf可以用libreoffice,需要安裝,這個是免費的,具體代碼如下:Stringcommand="libreoffice5.0--invisible--convert-topdf:writer_pdf_Export--outdir"+destFilepath+""+source;try{p=Runtime.getRuntime().exec(command);p.waitFor();}catch(InterruptedExceptione){e.printStackTrace();}catch(IOExceptione){e.printStackTrace();}
關于getinstance到此分享完畢,希望能幫助到您。