一、如今有T一、T二、T3三個線程,你怎樣保證T2在T1執行完後執行,T3在T2執行完後執行javascript
答: thread.Join把指定的線程加入到當前線程,能夠將兩個交替執行的線程合併爲順序執行的線程。 使用線程的join方法,該方法的做用是「等待線程執行結束」,即join()方法後面的代碼塊都要等待現場執行結束後才能執行 。好比在線程B中調用了線程A的Join()方法,直到線程A執行完畢後,纔會繼續執行線程B。
t.join(); //使調用線程 t 在此以前執行完畢。
t.join(1000); //等待 t 線程,等待時間是1000毫秒html
package cglib;java
public class List1
{
public static void main(String[] args)
{
final Thread t1 = new Thread(new Runnable() {mysql
@Override
public void run() {
System.out.println("t1");
}
});
final Thread t2 = new Thread(new Runnable() {linux
@Override
public void run() {
try {
//引用t1線程,等待t1線程執行完
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2");
}
});
Thread t3 = new Thread(new Runnable() {程序員
@Override
public void run() {
try {
//引用t2線程,等待t2線程執行完
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3");
}
});
t3.start();
t2.start();
t1.start();
}
}sql
輸出:數據庫
t1
t2
t3編程
二、3*0.1==0.3返回falsewindows
由於 3*0.1 java生成0.30000000000000004,
修改爲 float i=(float) (3*0.1);
i==0.3 ;
無論是什麼數, 在計算機中最終都會被轉化爲 0 和 1 進行存儲, 因此須要弄明白如下幾點問題
浮點數的二進制表示
首先咱們要了解浮點數二進制表示, 有如下兩個原則:
0.1 的表示是什麼?
咱們繼續按照浮點數的二進制表示來計算
0.1 * 2 = 0.2 整數部分取 0
0.2 * 2 = 0.4 整數部分取 0
0.4 * 2 = 0.8 整數部分取 0
0.8 * 2 = 1.6 整數部分取 1
0.6 * 2 = 1.2 整數部分取 1
0.2 * 2 = 0.4 整數部分取 0
…
因此你會發現, 0.1 的二進制表示是 0.00011001100110011001100110011……0011
0011做爲二進制小數的循環節不斷的進行循環.
這就引出了一個問題, 你永遠不能存下 0.1 的二進制, 即便你把全世界的硬盤都放在一塊兒, 也存不下 0.1 的二進制小數.
浮點數的二進制存儲
Python 和 C 同樣, 採用 IEEE 754 規範來存儲浮點數. IEEE 754 對雙精度浮點數的存儲規範將 64 bit 分爲 3 部分.
並且能夠指出的是, double 能存儲的數的個數是有限的, double 能表明的數必然不超過 2^64 個, 那麼現實世界上有多少個小數呢? 無限個. 計算機能作的只能是一個接近這個小數的值, 是這個值在必定精度下與邏輯認爲的值相等. 換句話說, 每一個小數的存儲(可是不是全部的), 都會伴有精度的丟失.
0.1 在計算機存儲中真正的數字是 0.1000000000000000055511151231257827021181583404541015625
0.2 是
0.200000000000000011102230246251565404236316680908203125
0.3 是
0.299999999999999988897769753748434595763683319091796875
這不是bug,緣由在與十進制到二進制的轉換致使的精度問題!其次這幾乎出如今不少的編程
語言中:C、C++、Java、Javascript、Python中,準確的說:「使用了IEEE754浮點數格式」來存儲浮點類型(float 32,double 64)的任何編程語言都有這個問題!
簡要介紹下IEEE 754浮點格式:它用科學記數法以底數爲2的小數來表示浮點數。IEEE浮點數(共32位)用1位表示數字符號,用8爲表示指數,用23爲來表示尾數(即小數部分)。此處指數用移碼存儲,尾數則是原碼(沒有符號位)。之因此用移碼是由於移碼的負數的符號位爲0,這能夠保證浮點數0的全部位都是0。雙精度浮點數(64位),使用1位符號位、11位指數位、52位尾數位來表示。
由於科學記數法有不少種方式來表示給定的數字,因此要規範化浮點數,以便用底數爲2而且小數點左邊爲1的小數來表示(注意是二進制的,因此只要不爲0則必定有一位爲1),按照須要調節指數就能夠獲得所需的數字。例如:十進制的1.25 => 二進制的1.01 => 則存儲時指數爲0、尾數爲1.0一、符號位爲0.(十進制轉二進制)
回到開頭,爲何「0.1+0.2=0.30000000000000004」?首先聲明這是javascript語言計算的結果(注意Javascript的數字類型是以64位的IEEE 754格式存儲的)。正如同十進制沒法精確表示1/3(0.33333...)同樣,二進制也有沒法精確表示的值。例如1/10。64位浮點數狀況下:
十進制0.1=> 二進制0.00011001100110011...(循環0011)
=>尾數爲1.1001100110011001100...1100(共52位,除了小數點左邊的1),指數爲-4(二進制移碼爲00000000010),符號位爲0=> 存儲爲:0 00000000100 10011001100110011...11001=> 由於尾數最多52位,因此實際存儲的值爲0.00011001100110011001100110011001100110011001100110011001
十進制0.2=> 二進制0.0011001100110011...(循環0011)
=>尾數爲1.1001100110011001100...1100(共52位,除了小數點左邊的1),指數爲-3(二進制移碼爲00000000011),符號位爲0=> 存儲爲:0 00000000011 10011001100110011...11001
由於尾數最多52位,因此實際存儲的值爲0.00110011001100110011001100110011001100110011001100110011
三、說下java堆空間結構及經常使用的jvm內存分析命令和工具
空間結構:
1. New Generation
又稱爲新生代,程序中新建的對象都將分配到新生代中,新生代又由Eden Space和兩塊Survivor Space構成,可經過-Xmn參數來指定其大小,Eden Space的大小和兩塊Survivor Space的大小比例默認爲8,即當New Generation的大小爲10M時,Eden Space的大小爲8M,兩塊Survivor Space各佔1M,這個比例可經過-XX:SurvivorRatio來指定。
2. Old Generation
又稱爲舊生代,用於存放程序中通過幾回垃圾回收還存活的對象,例如緩存的對象等,舊生代所佔用的內存大小即爲-Xmx指定的大小減去-Xmn指定的大小。
經常使用的jvm內存分析命令和工具
1:gc日誌輸出
在jvm啓動參數中加入 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimestamps -XX:+PrintGCApplicationStopedTime,jvm將會按照這些參數順序輸出gc概要信息,詳細信息,gc時間信息,gc形成 的應用暫停時間。若是在剛纔的參數後面加入參數 -Xloggc:文件路徑,gc信息將會輸出到指定的文件中。其餘參數還有
-verbose:gc和-XX:+PrintTenuringDistribution等。
2:jconsole
jconsole是jdk自帶的一個內存分析工具,它提供了圖形界面。能夠查看到被監控的jvm的內存信息,線程信息,類加載信息,MBean信息。
jconsole位於jdk目錄下的bin目錄,在windows下是jconsole.exe,在unix和linux下是 jconsole.sh,jconsole能夠監控本地應用,也能夠監控遠程應用。 要監控本地應用,執行jconsole pid,pid就是運行的java進程id,若是不帶上pid參數,則執行jconsole命令後,會看到一個對話框彈出,上面列出了本地的java進 程,能夠選擇一個進行監控。若是要遠程監控,則要在遠程服務器的jvm參數里加入一些東西,由於jconsole的遠程監控基於jmx的
四、哪些狀況下索引會失效
注意:要想使用or,又想讓索引生效,只能將or條件中的每一個列都加上索引
2.對於多列索引,不是使用的第一部分,則不會使用索引
3.like查詢是以%開頭
4.若是列類型是字符串,那必定要在條件中將數據使用引號引用起來,不然不使用索引
5.若是mysql估計使用全表掃描要比使用索引快,則不使用索引,
如查詢謂詞沒有使用索引的主要邊界,可能會致使不走索引。好比,你查詢的是SELECT * FROM T WHERE Y=XXX;假如你的T表上有一個包含Y值的組合索引,可是優化器會認爲須要一行行的掃描會更有效,這個時候,優化器可能會選擇TABLE ACCESS FULL,可是若是換成了SELECT Y FROM T WHERE Y = XXX,優化器會直接去索引中找到Y的值,由於從B樹中就能夠找到相應的值。
6. 對索引列進行運算致使索引失效,我所指的對索引列進行運算包括(+,-,*,/,! 等)
錯誤的例子:select * from test where id-1=9;
正確的例子:select * from test where id=10;
7. 使用Oracle內部函數致使索引失效.對於這樣狀況應當建立基於函數的索引.
錯誤的例子:select * from test where round(id)=10; 說明,此時id的索引已經不起做用了
正確的例子:首先創建函數索引,create index test_id_fbi_idx on test(round(id));而後 select * from test where round(id)=10; 這時函數索引發做用了
8. 如下使用會使索引失效,應避免使用;
a. 使用 <> 、not in 、not exist、!=
b、當變量採用的是times變量,而表的字段採用的是date變量時.或相反狀況。
9. 不要將空的變量值直接與比較運算符(符號)比較。
若是變量可能爲空,應使用 IS NULL 或 IS NOT NULL 進行比較,或者使用 ISNULL 函數。
此外,查看索引的使用狀況。
十、 若是在B樹索引中有一個空值,那麼查詢諸如SELECT COUNT(*) FROM T 的時候,由於HASHSET中不能存儲空值的,因此優化器不會走索引,有兩種方式可讓索引有效,一種是SELECT COUNT(*) FROM T WHERE XXX IS NOT NULL或者是不能爲空
十一、 在Oracle的初始化參數中,有一個參數是一次讀取的數據塊的數目,好比你的表只有幾個數據塊大小,並且能夠被Oracle一次性抓取,那麼就沒有使用 索引的必要了,由於抓取索引還須要去根據rowid從數據塊中獲取相應的元素值,所以在表特別小的狀況下,索引沒有用到是情理當中的事情。
十二、很長時間沒有作表分析,或者從新收集表狀態信息了,在數據字典中,表的統計信息是不許確的,這個狀況下,可能會使用錯誤的索引,這個效率可能也是比較低的。
show status like ‘Handler_read%’;
handler_read_key:這個值越高越好,越高表示使用索引查詢到的次數
handler_read_rnd_next:這個值越高,說明查詢低效
五、
這是由於PreparedStatement不容許在插入時改變查詢的邏輯結構
Statement和PreparedStatement的關係與區別在於:
① PreparedStatement類是Statement類的子類,擁有更多強大的功能。
② PreparedStatement類能夠防止SQL注入攻擊的問題
③ PreparedStatement會對SQL語句進行預編譯,以減輕數據庫服務器的壓力,而Statement則沒法作到。
Statement主要用於執行靜態SQL語句,即內容固定不變的SQL語句。Statement每執行一次都要對傳入的SQL語句編譯一次,效率較差。
Statement :
String sql = "select * from user where name='" +name+ "'";
頁面帶過來的表單數據name值爲' or 1=1 or name='
咱們若將表單填寫的數據帶到代碼中的SQL語句,就造成以下的SQL命令:
select * from user where name='' or 1=1 or name=''
能夠看到使用Statement對象就是將兩個字符串拼接造成的SQL語句,這樣作極可能會將判斷條件改變,如上面的命令,在where語句中出現了 or 1=1 這樣必定會返回true的語句,就如同程序發送一條「select * from user where true」的句子,那麼數據庫執行這條語句根本不須要篩選條件,只要數據庫有任意用戶,均可以告訴程序你找到了該指定用戶,那麼咱們連密碼都不用填的只需 要惡意SQL語句便可登陸網站。這就是一個SQL注入的典型例子。
PreparedStatement :
sql = "select * from users where NAME = ? and PWD = ?";
而使用PreparedStatement則不會,由於 PreparedStatement的預編譯,會將表單中所填寫的數據進行編譯,這種編譯是包含字符過濾的編譯,就好像對html進行過濾轉義同樣,這字 符過濾最關鍵的因素在於PreparedStatement使用的是佔位符,而不會像Statement那樣由於拼接字符串而引入了引號, 能夠看到在PreparedStatement中即便接收的表單數據中SQL語句以引號包圍,因爲程序中的SQL語句使用佔位符,所以就至關於條件爲 where name=' or 1=1 or name=',顯然數據庫並無這樣的記錄,所以防止了SQL注入的問題。
某些狀況下,SQL語句只是其中的參數有所不一樣,其他子句徹底相同,適用於PreparedStatement。 PreparedStatement 實例包含已事先編譯的 SQL 語句,SQL 語句可有一個或多個 IN 參數,IN參數的值在 SQL 語句建立時未被指定。該語句爲每一個 IN 參數保留一個問號(「?」)做爲佔位符。
每一個問號的值必須在該語句執行以前,經過適當的setInt或者setString 等方法提供。
因爲 PreparedStatement 對象已預編譯過,因此其執行速度要快於 Statement 對象。所以,屢次執行的 SQL 語句常常建立爲 PreparedStatement 對象,以提升效率。
一般批量處理時使用PreparedStatement。
Servlet的生命週期:
(1)裝載Servlet。該操做通常是動態執行。然而,Server一般會提供一個管理的選項,用於在Server啓動時強制裝載和初始化特定的Servlet。
(2)Server建立一個Servlet的實例
(3)Server調用Servlet的init()方法
(4)一個客戶端的請求到達Server
(5)Server建立一個請求對象
(6)Server建立一個響應對象
(7)Server激活Servlet的Service()方法,傳遞請求、響應對象做爲參數
(8)service()方法得到關於請求對象的信息,處理請求,訪問其餘資源,得到須要的信息
(9)service()方法使用響應對象的方法,將響應傳回Server,最終到達客戶端。Service()方法能夠激活其餘方法以處理請求,如doGet()或doPost()或程序員本身開發的新的方法。
(10)對於更多的客戶端請求,Server建立新的請求和響應對象,仍然激活此Servlet的service()方法,將這兩個對象做爲參數傳遞給 該方法。如此重複以上的循環,但無需再調用init()方法。由於,通常Servlet只初始化一次(只有一個實例),而當Server再也不須要 Servlet時(如異常或Server關閉),Server將調用Servlet的destroy()方法。
這個生命週期是至關好理解的。惟一的一點,就是,爲何Servlet只有一個實例?
出於性能的考慮:特別的對於門戶網站而言,每個Servlet在每一秒內的併發訪問量均可以是成千上萬的。在一個面向模塊化開發的如今,經常一個點擊 操做就被定義爲一個Servlet的實現,而若是Servlet的每一次被訪問,都建立一個新的實例的話,服務器的可用資源消耗量將是一個至關重要的問 題。退一步,通常Servlet的訪問是很快的,每個實例被快速的建立,又被快速的回收,GC的回收速度也跟不上,頻繁的內存操做也將可能帶來次生的問 題。因此,Servlet的「單一實例化」是一個很重要的策略。
七、編寫一個基於guava中的緩存組件,有哪些場景須要考慮,怎麼解決
爲何要有本地緩存?
在 系統中,有些數據,數據量小,可是訪問十分頻繁(例如國家標準行政區域數據),針對這種場景,須要將數據搞到應用的本地緩存中,以提高系統的訪問效率,減 少無謂的數據庫訪問(數據庫訪問佔用數據庫鏈接,同時網絡消耗比較大),可是有一點須要注意,就是緩存的佔用空間以及緩存的失效策略。
爲何是本地緩存,而不是分佈式的集羣緩存?
目前的數據,大可能是業務無關的小數據緩存,沒有必要搞分佈式的集羣緩存,目前涉及到訂單和商品的數據,會直接走DB進行請求,再加上分佈式緩存的構建,集羣維護成本比較高,不太適合緊急的業務項目。
這裏介紹一下緩存使用的三個階段(摘自info架構師文檔)
本地緩存在那個區域?
目前考慮的是佔用了JVM的heap區域,再細化一點的就是heap中的old區,目前的數據量來看,都是一些小數據,加起來沒有幾百兆,放在heap區 域最快最方便。後期若是須要放置在本地緩存的數據大的時候,能夠考慮在off-heap區域,可是off-heap區域的話,須要考慮對象的序列化(由於 off-heap區域存儲的是二進制的數據),另一個的話就是off-heap的GC問題。其實,若是真的數據量比較大,那其實就能夠考慮搞一個集中式 的緩存系統,能夠是單機,也能夠是集羣,來承擔緩存的做用。
搞一個單例模式,裏面有個Map的變量來放置數據
關於單例模式,一個既簡單又複雜的模式(http://iamzhongyong.iteye.com/blog/1539642)
很是典型的代碼以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
這種能不能用?能夠用,可是很是侷限
1 2 3 4 5 6 7 8 |
|
引入EhCache來構建緩存(詳細介紹: http://raychase.iteye.com/blog/1545906)
EhCahce的核心類:
A、CacheManager:Cache的管理類;
B、Cache:具體的cache類信息,負責緩存的get和put等操做
C、CacheConfiguration :cache的配置信息,包含策略、最大值等信息
D、Element:cache中單條緩存數據的單位
典型的代碼以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
固然,Cache的配置信息,能夠經過配置文件制定了。。。
優勢:功能強大,有失效策略、最大數量設置等,緩存的持久化只有企業版纔有,組件的緩存同步,能夠經過jgroup來實現
缺點:功能強大的同時,也使其更加複雜
引入guava的cacheBuilder來構建緩存
這個很是強大、簡單,經過一個CacheBuilder類就能夠知足需求。
缺點就是若是要組件同步的話,須要本身實現這個功能。
典型的代碼以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
緩存預熱怎麼搞?
A、全量預熱,固定的時間段移除全部,而後再全量預熱
適用場景:
一、數據更新不頻繁,例如天天晚上3點更新便可的需求;
二、數據基本沒有變化,例如全國區域性數據;
B、增量預熱(緩存查詢,沒有,則查詢數據庫,有則放入緩存)
適用場景:
一、 數據更新要求緩存中同步更新的場景
集羣內部,緩存的一致性如何保證?
若是採用ehcache的話,可使用框架自己的JGroup來實現組內機器之間的緩存同步。
若是是採用google的cacheBuilder的話,須要本身實現緩存的同步。
A、非實時生效數據:數據的更新不會時時發生,應用啓動的時候更新便可,而後定時程序定時去清理緩存;
B、須要實時生效數據:啓動時可預熱也可不預熱,可是緩存數據變動後,集羣之間須要同步
內存緩存須要考慮不少問題,包括併發問題,緩存失效機制,內存不夠用時緩存釋放,緩存的命中率,緩存的移除等等。 Cache在真實場景中有着至關廣的使用範圍。例如,當一個值要經過昂貴的計算和檢索來獲取,而且這個結果會被屢次使用到,這個時候你就應該考慮使用緩存 了。 Cache有點相似於ConcurrentMap,但並非徹底同樣。最根本的區別在於ConcurrentMap會持有添加到Map中的全部元素直到元 素被移除爲止。Cache經過相關的配置項能夠自動驅逐相關的緩存項,達到約束緩存佔用的目的。有些情景中LoadingCache經過自動的內存載入甚 至能夠不進行驅逐緩存項。 一般,Guava緩存工具適用於以下的情景: 一、你願意犧牲一些內存來提高速度。 二、你期待緩存項的查詢大於一次。 三、 你的緩存數據不超過內存(Guava緩存是單個應用中的本地緩存。它不會將數據存儲到文件中,或者外部服務器。若是不適合你,能夠考慮一下 Memcached) 注意:若是你不須要緩存的這些特性,那麼使用ConcurrentHashMap會有更好的內存效率,可是若是想基於舊有的ConcurrentMap複製實現Cache的一些特性,那麼多是很是困難或者根本不可能。
使用緩存,就存在緩存數據一致性的問題,和緩存數據的更新敏感度的問題,這個就是緩存的數據更新問題。
若是是分佈式緩存,就另外涉及到分佈式的數據一致性問題,這裏僅針對本地緩存進行討論。
針對本地緩存,更新方法有不少種,好比最經常使用的:
在高併發場景下,被動更新的回源是要格外當心的,也就是雪崩穿透問題: 若是有太多請求在同一時間回源,後端服務若是沒法支撐這麼高併發,容易引起後端服務崩潰。
這時Guava Cache上場了,Guava Cache裏的CacheLoader在回源的load方法上加了控制,對於同一個key,只讓一個請求回源load,其餘線程阻塞等待結果。同時,在 Guava裏能夠經過配置expireAfterAccess/expireAfterWrite設定key的過時時間,key過時後就單線程回源加載並 放回緩存。
樣經過Guava Cache簡簡單單就較爲安全地實現了緩存的被動更新操做。
爲何是」較爲安全」呢?由於若是同一時間仍有太多的不一樣key過時,仍是會有大量請求穿透緩存而請求到後端服務上,仍然有可能使後端服務崩潰,有什麼辦法解決這個問題呢?
1.將key的過時時間加個隨機值,避免你們一塊兒過時(前提是對業務不影響), 2.本身控制回源的併發數,即便有一萬個key要更新,也只讓100個能夠回源,其他的9900個等着,(能夠經過Guava的Striped實現) 3.在過時前主動更新,更新完成後將過時時間延長
另外,若是對剛纔說的對於同一個key,只讓一個請求回源,其餘線程等待以爲還不爽,雖然對後端服務不會形成壓力,但個人請求都仍是blocked了,整個請求仍是會被堵一下。
別急,Guava Cache還提供了一個refreshAfterWrite的配置項,定時刷新數據,刷新時仍只有一個線程回源取數據,但其餘線程只會稍微等一會,沒等到 就返回舊值,整個請求看起來就比較平滑了。爲何又是「比較平滑」呢?由於默認的刷新回源線程是同步的,若是想達到全過程平滑的效果,能夠將刷新回源線程 作成異步方式。
這樣數據的更新都是在後臺異步作了,但這樣也是有必定的代價的,好比過了刷新時間,仍可能拿到舊值,直到拿回數據更新緩存後纔會返回新值。
由於這個refresh動做並非主動發起的: 好比設置了5秒refresh一下,Guava的作法並非真的每5秒刷一次,而是等請求到了以後,發現須要refresh時纔會真的更新。因此,這一點 須要注意,好比雖然設置了5秒刷新,但若是超過1分鐘都沒有請求(假設key沒有過時),當1分零1秒有請求來時,仍有可能返回舊值。
如下是關於設置Expire過時和Refresh刷新(sync/async)兩種方式,Guava Cache對請求回源的處理示意圖: