2、必須是多線程使用同一把鎖
弊端:多個線需要判斷鎖,較爲消耗資源
1、明確那些代碼是多線程運行代碼2、明確共享數據
凡是是出現2次的變量,中間加一句睡覺,必能看見問題。
3、明確多線程運行代碼中那些語句是操作共享數據的。
特點:延遲加載、多線程時容易出安全問題、同步代碼塊或者同步方法可解決安全問題,但效率不高同步代碼塊加上雙重if判斷,可解決效率問題
public class ThreadTest {
public static void main(String[] args) {
newThread td1=new newThread("線程A");
td1.start();
new Thread(new newThread("-----------b")).start();
}
}
class newThread extends Thread
{
newThread(String name){
super(name);
}
public void run(){
for(int i = 0 ;i<50 ; i++){
System.out.println(this.getName());
}
}
}
-------------------------------------------------1、
根據多次運行,知道
每次運行結果不一樣,
這裏涉及到 CPU機制問題。CPU在同一時刻,只能執行一個程序。
並迅速的在不同程序間做着切換,由於速度太快,而給人以同時運行的效果。
線程的隨機性:
線程的運行,彷彿就是相互爭奪 CPU的執行權利。 誰搶到執行誰,至於執行時間,看CPU。
在Java中,所有的線程都是同時啓動的, 但是什麼時候執行那個個,執行多久,都是CPU來決定
在Java中,每次程序的運行至少啓動了2個線程,1是main主線程,2是垃圾回收線程。
在Java中,只要前臺有一個線程在運行,整個Java程序進程不會消失,可以設置一個後臺程序,
即使Java進程小時,後臺線程依然能夠運行。
2、
線程的幾個方法
Static Thread currentThread() 同this 當前線程的引用。
通常 定義好一個線程後,使用內部類方式書寫:
new Thread(new threadName()).start()
setName() 更改線程名
getName() 獲取線程名
start() 啓動
sleep() 睡眠 sleep() 用處比較廣泛
Thread.sleep(6000); 設置等待6000毫秒。然後幹什麼。
wait() 等待
notify() 喚醒
stop() 停止
【1-2】
// 模擬賣票小程序
//繼承 Thread
class MyThreadT
{
public static void main(String[] args)
{
TicketDemo t1 = new TicketDemo();
TicketDemo t2 = new TicketDemo();
TicketDemo t3 = new TicketDemo();
t2.start();
t3.start();
t1.start();
}
}
class TicketDemo extends Thread
{
private int tick = 100;
public void run(){
while(true){
if(tick>0){
System.out.println(this.getName()+"..."+tick--);
}
}
}
}
//2、實現 Runnable
class MyThreadR
{
public static void main(String[] args)
{
TicketDemo t = new TicketDemo();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TicketDemo implements Runnable
{
private int tick = 100;
public void run(){
while(true){
if(tick>0){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
}
}
}
相同的代碼,只是 一個通過繼承,一個是通過實現 來達到多線程的作用。發現
繼承 Thread 的線程 每一條線程都在從1到100的賣票,也就是300張票。數據
實現 Runnable 的線程 則是才賣100張票
結論:
1、避免了單繼承的侷限性
2、更適合多個線程去處理同一個資源。
建議使用 實現方式的 線程
區別就是 功能代碼都存放在對應的run方法中。
【2】
線程的5個狀態---線程的安全問題。
1、創建狀態 2、運行狀態 3、阻塞狀態 4、消亡狀態 5、凍結狀態
當多個線程被創建時,在內存中會有這樣一個現象:
1、都在具備運行資格,但沒有執行權
2、其中一個有了執行權利,進入執行,可能會:而其他現在發生着類似情況。
1、直接運行完畢,也就是結束了。
2、運行一半 sleep
3、wait了
當屬於2,3時候,當 sleep,世間結束,當 wait被 notify() 喚醒後:
1、繼續獲取到執行權利執行
2、返回到等待狀態,擁有資格沒有權利
4、運行結束或者被 stop 線程消亡
(配圖)
就上述 案例中,
public void run(){
while(true){
if(tick>0){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
}
}
由於 線程的執行權是爭取的,而執行之間是CPU 決定的。邏輯上完全可能出現下來情況:當 tick=1的時候:
1線程 判斷 tick>0 進入 然後臥倒了。 這時2 線程進入 tick>0 然後又臥倒了。
然後3線程 同樣進來了, 這時候也臥倒了,
三個線程都掛這裏後,1線程起來了,這時候不會在判斷而直接打印了當前的tick-- 也就是0
接着2 線程 起來了,也不會在判斷而 打直接印了當前的tick-- 也就是-1了
接着3 線程 起來了,同樣不會在判斷而 打直接印了當前的tick-- 也就是-2了
打印出0 的時候 程序已經就出現問題了。
if(tick>0){
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
只需要在打印前,讓該線程睡上10毫秒,即可達到上述狀況也就是說,涉及到了 線程的安全問題。
問題的產生在於:當一個線程對多條語句只操作一部分的時候,另一個線程參與執行後,導致共享數據的錯亂
解決辦法就是: 對多條操作共享數據的語句,只讓一個線程來執行完成,完成前其他線程不可參與。
Java 對於多線程的安全問題提供了對應的專業技術:
【3】
同步代碼塊:
synchronized(對象){
需要被同步的代碼。
}
那麼上面的代碼就更改爲:
class TicketDemo implements Runnable
{
private int tick = 100;
public void run(){
while(true){
synchronized(this)
{
if(tick>0){
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
}
}
}
}
這時候,既時寫Thread.sleep(10000) 睡上一天,也不會在出現問題了。對象如同鎖,持有鎖的線程可以在同步中執行,
沒有持有鎖,即使線程獲取到了CPU執行權,因爲沒有鎖,也無法進去
聯想一下 老師 ,火車的 衛生間 例子。
裏面的人不出來,外面的人是進不去的。 鎖,生動的解釋了這一現象。
同步代碼 有2個前提:
1、必須2個或以上的線程
2、必須是多個線程使用同一把鎖
必須保證同步中只能有一個線程在運行。
優點:解決了多線程的安全問題
弊端:多個線需要判斷鎖,較爲消耗資源
如何使用同步代碼快?
1、明確那些代碼是多線程運行代碼
2、明確共享數據
凡是是出現2次的變量,中間加一句睡覺,必然能看見問題,也就是說需要同步在一起 如下:
3、明確多線程運行代碼中那些語句是操作共享數據的。
同步函數、同步方法
class bank
{
public static void main(String[] args)
{
Cus b = new Cus();
new Thread(b).start();
new Thread(b).start();
}
}
class BankT
{
private int sum ;
public synchronized void add(int n)
{
sum = sum+n;
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(sum);
}
}
class Cus implements Runnable
{
private BankT b = new BankT();
public void run(){
for(int i = 0 ;i<3 ;i++){
b.add(100);
}
}
}
相比同步代碼塊而言,同步方法,更簡潔一些。持有的鎖爲 當前對象的引用。鎖 具有唯一性,那麼 使用的時候,就使用當前引用,作爲鎖,避免出現問題
同步方法的鎖是 this
同步代碼塊的鎖 也用this ,
當同步方法和同步代碼塊組合應用的時候,纔不會出現問題
出現問題肯定是2個前提。
當同步方法爲靜態的時候,鎖不再是this的, 靜態方法中是沒有this的。
那麼鎖是誰呢?
因爲有 static 進內存的時候是沒有當前對象,所以沒有this
但是一定有當前類對應的字節碼文件對象
該對象的類型是 Class
class mair
{
public static void main(String[] args)
{
TicketDemo t = new TicketDemo();
new Thread(t).start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
new Thread(t).start();
}
}
class TicketDemo implements Runnable
{
//private int tick = 100; 【A】對應非靜態同步方法
private static int tick = 100;
boolean flag= true;
public void run(){
if(flag){
//同步代碼塊
while(true){
//synchronized(this) //【C】對應A
synchronized(TicketDemo.class)
{
if(tick>0){
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...code"+tick--);
}
}
}
}else{
//同步方法
while(true){
this.show();
}
}
}
//public synchronized void show() //【B】對應A [abc 爲非靜態同步方法組合方式]
public static synchronized void show()
{
if(tick>0){
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...show"+tick--);
}
}
}
【重點】總結:當 靜態同步方法和同步代碼塊組合使用的時候,鎖 是 該類字節碼對象
當 非靜態同步方法和同步代碼塊組合使用時候,鎖 是 當前對象引用 this
隨便提一下 經常面試的【單列設計模式】題目
問;懶漢式和惡漢式有什麼不同
懶漢式特點是 對象的延遲加載
問:懶漢式,有沒有問題
有,多線程訪問時容易出現安全問題。
問:怎麼解決,
可以加同步來解決,
加同步代碼塊,或者同步方法 可以解決安全問題,但是效率比較低效
通過雙重判斷,結合同步代碼塊 ,可以解決效率問題
問:懶漢式的鎖是那個
該類的字節碼文件對象。
【重點】//懶漢式 常考 也稱爲 延遲加載的單例設計模式
class Single {
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s == null){
synchronized(Single.class){
if(s == null)
s = new Single();
}
}
return s;
}
}
假設123,線程,1進去,判斷 null ,進入同步代碼塊,臥倒了。2進去,同樣 null ,但是 2沒有鎖,進不去,
1醒了創建實例對象 結束,
2醒了,進入鎖,再判斷, 不爲 null ,出去了,
3進入,一判斷,不爲null */
惡漢式,常用方式
class Single{
private static final Single s = new Single();
private Single(){};
public static Single getInstance(){
return s;
}
}
死鎖同步中嵌套同步
//實現一個死鎖: 同步中嵌套鎖
class ThreadS implements Runnable
{
private boolean falg;
ThreadS(boolean falg){
this.falg = falg;
}
public void run(){
if(falg){
synchronized(mySuo.A){
System.out.println(" if......A");
synchronized(mySuo.B){
System.out.println(" if......BBB");
}
}
}
else{
synchronized(mySuo.B){
System.out.println(" else.....BBB");
synchronized(mySuo.A){
System.out.println(" else.....AAA");
}
}
}
}
}
class mySuo // 鎖
{
static Object A = new Object();//靜態修飾的目的是方便調用
static Object B = new Object();
}
class sisuo
{
public static void main(String [] args){
new Thread(new ThreadS(true)).start();
new Thread(new ThreadS(false)).start();
}
}
//end