多執行緒
在看多執行緒之前先來看看必要的一些東西:
執行緒與行程
行程:
是指一個記憶體中運行的應用程式,每個行程都有一個獨立的記憶體空間,
執行緒:
是行程中的一個執行路徑,共享一個記憶體空間,執行緒之間可以自由切換,并發執行. 一個行程最少有一個執行緒,
執行緒實際上是在行程基礎之上的進一步劃分,一個行程啟動之后,里面的若干執行路徑又可以劃分成若干個執行緒,
同步與異步
同步: 排隊執行 , 效率低但是安全.
異步: 同時執行 , 效率高但是資料不安全.
并發與并行
并發: 指兩個或多個事件在同一個時間段內發生,
并行: 指兩個或多個事件在同一時刻發生(同時發生),
好了這些相關的知識了解一下就可以看看多執行緒了,
執行緒的定義
1.繼承Thread
繼承java.lang.Thread類,重寫run()方法 ,
格式如下:
//這是繼承格式,記住一定要重寫run()方法
public class MyThread extends Thread {
//run方法就是執行緒要執行的任務方法
@Override
public void run() {
//這里的代碼就是一條新的執行路徑
//這個執行路徑是觸發方式,不是呼叫run方法,而是通過thread物件的start方法來啟動任務
}
}
//這個是創建格式,和之前類的物件的創建是一樣的
//但是一定要記住要用"物件.start()"來啟動執行緒,要不然就是執行緒創建了但是不會運行
public class Demo1 {
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
}
}
上述就完成并啟動了一個執行緒,
2.實作Runnable
實作java.lang.Runnable介面,并將其傳入到Thread類物件中
格式如下:
//與上述Thread格式一模一樣,就是一個是繼承類,一個是實作介面
public class MyRunnable implements Runnable{
@Override
public void run() {
//執行緒的任務
}
}
//創建格式
public class Demo1 {
public static void main(String[] args) {
//實作runnable
//1 創建一個任務物件
MyRunnable r = new MyRunnable();
//創建一個執行緒并給他一個任務
Thread t = new Thread(r);
//啟動執行緒
t.start();
}
}
//這里就是比Tread多了一步,就是給他一個任務,
那么這兩種方法到底那種好呢,有人覺得1好應為1比2少一步,不用分配任務,但是實際上是2比較好,
來看看,實作Runnable與繼承Thread相比有如下優勢:
1、通過創建任務,然后給執行緒分配任務的方式實作多執行緒,更適合多個執行緒同時執行任務的情況
2、可以避免單繼承所帶來的局限性
3、任務與執行緒是分離的,提高了程式的健壯性
4、后期學習的執行緒池技術,接受Runnable型別的任務,不接受Thread型別的執行緒,
執行緒安全問題
先來看看什么是執行緒安全問題
package thread;
public class Demo7 {
public static void main(String[] args) {
//執行緒不安全
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//總票數
private int count = 10;
@Override
public void run() {
while (count>0){
//賣票
System.out.println("正在準備賣票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("賣票結束,余票:"+count);
}
}
}
}
讀者可以試試這個代碼,有時候也要多運行幾次,它會出現以下情況:

我們可以看到在運行結果中出現了負數,也就是說票買到了負的,你想想這肯定是不符合邏輯的,但是程式也沒有出錯,也沒有例外,這就是執行緒不安全,也叫叫做執行緒安全問題,下面就看看執行緒不安全的解決方法,
解決方案1 同步代碼塊
格式如下:
synchronized(鎖物件){
}
也就是將上述代碼修改為如下:
package thread;
//執行緒同步synchronized
public class Demo8 {
public static void main(String[] args) {
Object o = new Object();
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//總票數
private int count = 10;
//這個鎖是公共的
private Object o = new Object();
@Override
public void run() {
//Object o = new Object(); //這里不是同一把鎖,所以鎖不住
while (true) {
synchronized (o) {
if (count > 0) {
//賣票
System.out.println("正在準備賣票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
//列印執行緒名和票的余數
System.out.println(Thread.currentThread().getName()+"賣票結束,余票:" + count);
}else {
break;
}
}
}
}
}
}
有的讀者會問,上面怎么有兩個地方有“Object o = new Object(); ”這個陳述句,在上面的一個叫做公共鎖,下面的一個叫做自己的鎖,只用公共鎖才能鎖的住執行緒,打個比方,你去試衣間試衣服,門上肯定有一把鎖,這個可以叫做公共鎖,要是是鎖住的別人就不會進來,如果試衣服的人要進試衣間,進去之前不是看門上的鎖,而是看自己口袋的鎖(也就是自己的鎖)的話,那么自己的鎖肯定一開始是開的,那么來一個人看一下自己的鎖(是開的可以進試衣間),就進去了,那不是里面得打起來?
解決方案2 同步方法
將上述代碼修改為如下:
package thread;
//執行緒同步synchronized
public class Demo9 {
public static void main(String[] args) {
Object o = new Object();
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//總票數
private int count = 10;
@Override
public void run() {
while (true) {
boolean flag = sale();
if(!flag){
break;
}
}
}
public synchronized boolean sale(){
if (count > 0) {
//賣票
System.out.println("正在準備賣票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"賣票結束,余票:" + count);
return true;
}
return false;
}
}
}
也很容易理解,就是寫方法來限制,
解決方案3 顯示鎖 Lock 子類 ReentrantLock
將上述代碼修改為如下:
package thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//同步代碼塊和同步方法都屬于隱式鎖
//執行緒同步lock
public class Demo10 {
public static void main(String[] args) {
Object o = new Object();
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//總票數
private int count = 10;
//引數為true表示公平鎖 默認是false 不是公平鎖
private Lock l = new ReentrantLock(true);
@Override
public void run() {
while (true) {
//鎖死
l.lock();
if (count > 0) {
//賣票
System.out.println("正在準備賣票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"賣票結束,余票:" + count);
}else {
break;
}
//開鎖
l.unlock();
}
}
}
}
就是將程式執行的時候將執行緒鎖死,一個執行緒執行完后又打開,就可以將其理解為生活當中正在的試衣間,一個人來試衣服,試衣服的時候就將門鎖住,試完衣服就開鎖,就是這樣,
多執行緒通信問題
現在想想有這樣一個問題,一個廚師和一個服務員,初始做好一個菜,服務員就端一個菜,順序和味道不能弄混淆,來看下面的程式:
package thread;
public class Demo12 {
public static void main(String[] args) {
//多執行緒通信 生產者與消費者問題
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//廚師
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
f.setNameAndTaste("老干媽小米粥","香辣味");
}else {
f.setNameAndTaste("煎餅果子","甜辣味");
}
}
}
}
//服務員
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
public void setNameAndTaste(String name,String taste){
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
}
public void get(){
System.out.println("服務員端走的菜的名稱是:" + name + "味道是:" + taste);
}
}
}
施行結果會出現這樣的情況:

我們看到了這樣的陳述句:
服務員端走的菜的名稱是:煎餅果子味道是:香辣味
服務員端走的菜的名稱是:煎餅果子味道是:甜辣味
欸,出現了陳述句錯誤,因為兩個執行緒在運行時,可能一個執行緒運行的時候另一個沒反應過來,或者說是反應慢了的原因就導致了這種情況,
解決方法,如下代碼:
package thread;
public class Demo12 {
public static void main(String[] args) {
//多執行緒通信 生產者與消費者問題
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//廚師
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
f.setNameAndTaste("老干媽小米粥","香辣味");
}else {
f.setNameAndTaste("煎餅果子","甜辣味");
}
}
}
}
//服務員
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
//true表示可以生產
boolean flag = true;
public synchronized void setNameAndTaste(String name,String taste){
if(flag){
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag){
System.out.println("服務員端走的菜的名稱是:"+name+",味道是:"+taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
和前面的執行緒安全一樣添加同步方法一樣,將get方法改為同步方法,就可以了,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/273334.html
標籤:其他
上一篇:YXcms(mvc架構php的cms)簡單學習與審計
下一篇:再戰JVM (1) 類加載程序
