多線程是多個線程同時對同一個數據執行相同或不同的任務。要注意的是,這樣的操作很大可能會出現線程安全問題,和線程死鎖問題。
現介紹一下為什么要用同步線程:
public class MyThread implements Runnable {
private int ticket=10;
@Override
public void run() {
for(int i=0;i<10;i++){
if(this.ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售倒數第:"+this.ticket--+"張票");
}
}
}
}
單多個線程訪問同一個對象是,會出現其中一條線程執行業務代碼的時候,CPU的執行權被另一條線程搶走了,通過上面代碼可以明顯看出輸出了負數,這和我們現實生活是完全不存在的。這就是線程安全問題
這使得我們需要通過靜態化ticket,或者是用同步線程來實現我們要的功能。
下面先介紹一下怎么解決線程安全問題的實現;
方式一:可以使用同步代碼塊來實現。
示例:
public class MyThread implements Runnable {
private int ticket=10;
@Override
public synchronized void run() {
for(int i=0;i<10;i++){
if(this.ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售倒數第:"+this.ticket--+"張票");
}
}
}
}
示例二:使用同步代碼塊來實現
public class MyThread implements Runnable {
private int ticket=10;
@Override
public void run() {
for(int i=0;i<10;i++){
synchronized ("鎖"){
if(this.ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售倒數第:"+this.ticket--+"張票");
}
}
}
}
}
上面兩種方式都能實現代碼的同步,
線程之所以能同步,是因為加了一個鎖對象,在使用同步函數的時候,鎖對象是對象本身this,而在使用同步代碼塊的時候,鎖對象是自己定義的,可以是任何對象。
建議:要是用同步線程的時候,建議優先使用同步代碼塊,因為同步代碼塊鎖對象是自己定義的,方便對鎖進行操作。這個原因下面將要說到。
通過上面的代碼會發現,我在方法中使用了sleep方法。sleep()方法是Thread類中的一個方法,為什么沒有把它放到上一節將,是因為這個方法有點特殊。這個涉及到線程的各種狀態:
線程開始的時候是創建狀態:(new 對象的時候.)
當調用了start方法的時候,線程處于可以運行狀態,在這個狀態下的線程具備CPU的等待權,但是不具備CPU的執行權。
當線程執行的時候他具備了CPU的執行權。這個時候稱為運行狀態。
當完成任務代碼塊的時候,線程死亡。
在可運行狀態和運行狀態之間還有一個狀態,這個狀態稱為臨時阻塞狀態。進入這個狀態需要調用sleep()或wait()方法。這個時候線程不具備CPU的等待資格,和CPU的執行權。
sleep()方法是自己設置阻塞時間。當時間一到,線程會自動轉換成可運行狀態。他是Thread中的一個方法。當一個線程在同步方法中調用了sleep方法,其它線程不能調用它的其它同步方法。
swit()是Object中的一個方法,當線程調用了這個方法。需要通過notify或者notifyAll方法來喚醒線程。線程阻塞是存放在線程池中的。一個對象對應一個線程池。
當線程阻塞是,線程會自動釋放CPU的等待權和CPU的執行權。但是如果使用的sleep方法,線程是不會釋放鎖對象的。而wait方法是會釋放鎖對象。
下面說下經典的線程通訊:
//產品類
class Product{
String name; //名字
double price; //價格
boolean flag = false; //產品是否生產完畢的標識,默認情況是沒有生產完成。
}
//生產者
class Producer extends Thread{
Product p ; //產品
public Producer(Product p) {
this.p = p ;
}
@Override
public void run() {
int i = 0 ;
while(true){
synchronized (p) {
if(p.flag==false){
if(i%2==0){
p.name = "蘋果";
p.price = 6.5;
}else{
p.name="香蕉";
p.price = 2.0;
}
System.out.println("生產者生產出了:"+ p.name+" 價格是:"+ p.price);
p.flag = true;
i++;
p.notifyAll(); //喚醒消費者去消費
}else{
//已經生產 完畢,等待消費者先去消費
try {
p.wait(); //生產者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
//消費者
class Customer extends Thread{
Product p;
public Customer(Product p) {
this.p = p;
}
@Override
public void run() {
while(true){
synchronized (p) {
if(p.flag==true){ //產品已經生產完畢
System.out.println("消費者消費了"+p.name+" 價格:"+ p.price);
p.flag = false;
p.notifyAll(); // 喚醒生產者去生產
}else{
//產品還沒有生產,應該 等待生產者先生產。
try {
p.wait(); //消費者也等待了...
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public class Demo5 {
public static void main(String[] args) {
Product p = new Product(); //產品
//創建生產對象
Producer producer = new Producer(p);
//創建消費者
Customer customer = new Customer(p);
//調用start方法開啟線程
producer.start();
customer.start();
}
}
線程死鎖的體現:
lass DeadLock extends Thread{
public DeadLock(String name){
super(name);
}
public void run() {
if("張三".equals(Thread.currentThread().getName())){
synchronized ("遙控器") {
System.out.println("張三拿到了遙控器,準備 去拿電池!!");
synchronized ("電池") {
System.out.println("張三拿到了遙控器與電池了,開著空調爽歪歪的吹著...");
}
}
}else if("狗娃".equals(Thread.currentThread().getName())){
synchronized ("電池") {
System.out.println("狗娃拿到了電池,準備去拿遙控器!!");
synchronized ("遙控器") {
System.out.println("狗娃拿到了遙控器與電池了,開著空調爽歪歪的吹著...");
}
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
DeadLock thread1 = new DeadLock("張三");
DeadLock thread2 = new DeadLock("狗娃");
//開啟線程
thread1.start();
thread2.start();
}
}
死鎖現象出現 的根本原因:
- 存在兩個或者兩個以上的線程。
- 存在兩個或者兩個以上的共享資源。
【注】講解可能有不足的地方,希望各位小伙伴能幫我補充不足之處,謝謝!
復制得來終覺淺,要想知道自己敲!