如何判斷一個線程是否安全呢?如果那個線程滿足以下三個特點,那麼它的線程就是不安全的:
- 是否是多線程環境
- 是否有共享數據
- 是否有多條語句操作共享數據
那麼該如何避免線程不安全呢?這裏有幾個解決方法:
- 同步代碼塊(測試不是同一個鎖的情況,測試是同一個鎖的情況)
synchronized(對象) {
需要被同步的代碼。
}
這裏的對象是任意對象 ,相當於是一把鎖,只要線程進去就把鎖鎖上
代碼演示:
package com.thz_06;
//用同步代碼塊解決代碼線程不安全問題
public class MyThread extends Thread{
//靜態代碼塊,定義票數
static int ticket = 100;
/*
* A:同步代碼塊(測試不是同一個鎖的情況,測試是同一個鎖的情況)
synchronized(對象) {
需要被同步的代碼。
}
*/
//創對象
static Object ob= new Object();
static Object ob1= new Object();
int x = 0;
@Override
public void run() {
if(x%2==0){
//同一對象鎖
synchronized (ob) {
while(true){
//休眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//賣票
if(ticket>0){
System.out.println("這是"+this.getName()+"賣的第"+ticket--+"張票");
}else{
break;
}
}
}
x++;
}else{
addSynchronized();
x++;
}
}
//同一對象
private void addSynchronized() {
synchronized (ob) {
while(true){
//休眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//賣票
if(ticket>0){
System.out.println("這是"+this.getName()+"賣的第"+ticket--+"張票");
}else{
break;
}
}
}
}
/*//不同對象,不安全,會賣同樣的票
private void addSynchronized() {
synchronized (ob1) {
while(true){
//休眠100毫秒
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//賣票
if(ticket>0){
System.out.println("這是"+this.getName()+"賣的第"+ticket--+"張票");
}else{
break;
}
}
}
}*/
}
- 同步方法(僅適用於實現runable接口)
public synchronized void sellTicket(){同步代碼}
鎖的關鍵字是this
代碼演示:
package com.thz_07;
//同步方法(僅適用於實現runable接口)
public class MyThread implements Runnable{
int ticket = 100;
@Override
public void run() {
while(true){
//休眠
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(ticket>0){
sellTicket();
}
}
}
private synchronized void sellTicket() {
synchronized (this) {
System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"張票");
}
}
}
- 靜態同步方法
類的字節碼對象
public static synchronized void sellTicket() {
需要同步的代碼
}
代碼演示:
package com.thz_08;
/*
* 靜態同步方法
類的字節碼對象
*/
public class MyRunnable implements Runnable{
static int ticket = 100;
@Override
public void run() {
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(ticket>0){
MyRunnable.sellTicket();
}
}
}
private synchronized static void sellTicket() {
synchronized (MyRunnable.class) {
System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"張票");
}
}
}
但這樣會顯得我們的代碼頁比較多,有時我們也可以用匿名內部類來啓用線程;
new Thread() {
public void run() {
...
}
}.start();
new Thread(new Runnable(){
public void run() {
...
}
}).start();
之前我們加的鎖都是不需要手動釋放的,在JDK5之後java引入了一個新的概念,讓我們可以手動上鎖,手動解鎖,這就是LOCK類;
static Lock lock = new ReentrantLock();
加鎖:lock.lock();
釋放鎖:lock.unlock();
可以讓我們明確的知道在哪裏加鎖和釋放鎖。
爲了保證我們創建的鎖一定會被釋放,用一下代碼進行改進
try{….}finally{…..}
代碼演示:
package com.thz_09;
//案例:利用匿名內部類,啓動多個線程
public class SigleThread {
public static void main(String[] args) {
//匿名內部類構造Thread對象並調用start方法
new Thread(){
@Override
public void run() {
for (int i = 0; i < 99; i++) {
System.out.println(i);
}
}
}.start();
new Thread(new Runnable(){
public void run() {
for (int i = 100; i < 199; i++) {
System.out.println(i);
}
};
}).start();
}
}
但是引入鎖之後就要避免一個問題的出現,這就是死鎖,什麼是死鎖呢?就是你在一個鎖裏面上了一把鎖,從而導致永遠無法解鎖。
線程裏還有一個比較重要的東西,這就是線程等待和喚醒機制。
鎖對象調用wait()去進行線程等待,等待鎖對象調用notify()去喚醒鎖,在線程等待的時候釋放鎖,從而讓其他程序,只是他自己線程內的程序暫時不運行,直到被喚醒。
代碼演示:
package com.thz_11;
public class NotifyThread extends Thread{
@Override
public void run() {
synchronized (MyLock.obj) {
//喚醒等待線程
MyLock.obj.notify();//喚醒正在等待的線程,喚醒的等待線程的鎖對象,必須和等待線程的鎖對象一致
}
}
}
package com.thz_11;
public class WaitThread extends Thread{
@Override
public void run() {
synchronized (MyLock.obj) {
//讓等待線程處於等待狀態
try {
MyLock.obj.wait();//當線程處於等待狀態的時候,線程就不會繼續往下執行了
//線程在處於等待的時候,會釋放掉自己手中的鎖
//sleep()這個方法,在線程休息的時候會釋放鎖碼?
//答:不會
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("我被喚醒了");
}
}