多線程編程
多線程就是把操作系統中的這種並發執行機制原理運用在一個程序中,把一個程序划分為若干個子任務,多個子任務並發執行,每一個任務就是一個線程。 這就是多線程程序 。
1、使用線程可以把占據時間長的 程序 中的 任務 放到 后台 去處理 。
2、用戶界面可以更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度 。
3、程序的運行速度可能加快 。
4、在一些等待的 任務 實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。 在這種 情況 下可以釋放一些珍貴的資源如 內存 占用等 。
5、 多線程技術 在IOS軟件開發中也有舉足輕重的作用 。
線程常用
synchronized //同步鎖
volatile //可見
CountDownLatch //計數器
ReenTrantLock //重入鎖(公平鎖)
TimeUnit //配合時間 例(TimeUnit.SECONDS.sleep(1)睡眠一秒)
Semaphore
lockInterruptibly //中斷鎖
lock //上鎖
unlock //解鎖
tryLock //判斷有無鎖 返回boolean
wait //線程等待
sleep //線程睡眠
notify //線程喚醒
多線程實現的方法
一共有三種,常用的有繼承Thread類、實現Runnable接口
還有一種實現Callable接口下面主要介紹常用的兩種
因為繼承只能單繼承而實現可以多實現因此推薦使用實現接口
繼承Thread類
1.新建類繼承Thread類
public class Test1 {
public static void main(String[] args) {
T1 t = new T1();
t.start();
}
}
class T1 extends Thread {
@Override
public void run() {
}
}
2.匿名內部類
public class Test1 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
}
}
3.lambda表達式
public class Test1 {
public static void main(String[] args) {
new Thread(() -> {
}).start();
}
}
實現Runnable接口
1.新建類實現接口
public class Test1 {
public static void main(String[] args) {
T1 t =new T1();
Thread tt =new Thread(t);
tt.start();
}
}
class T1 implements Runnable{
@Override
public void run() {
}
}
2.匿名內部類
public class Test1 {
public static void main(String[] args) {
var t = new Runnable() {
@Override
public void run() {
}
};
Thread tt =new Thread(t);
tt.start();
}
}
多線程常用方法
下面是多線程狀態一定要記住!!!!
設置和獲取線程的優先級
優先級的范圍(1-10)默認是5
//Thread.MAX_PRIORITY 10
//Thread.MIN_PRIORITY 1
//Thread.NORM_PRIORITY 5
Thread.currentThread().setPriority(Thread.MAX_PRIORITY)//設置該線程優先級
Thread.currentThread().getPriority()//獲取該線程優先級
設置和獲取線程的名字
Thread.currentThread().setName("t1")//設置該線程姓名
Thread.currentThread().getName()//獲取該線程姓名
線程睡眠
sleep不會釋放同步鎖后面會詳解先介紹使用方法
Thread.sleep(1000)//讓該線程睡眠1秒TimeUnit.SECONDS.sleep(1)//讓該線程睡眠一秒
線程等待
wait會釋放同步鎖進行等待后面會配合介紹
wait()//
多線程工具類
Timer TimerTask
定時器
public class ThreadTimer { public static void main(String[] args) throws ParseException { Timer timer = new Timer(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date d = sdf.parse("2021-8-24 20:14:30");//設置指定的時間 timer.schedule(new TimerTask() { @Override public void run() { System.out.printf("%1$tF %1$tT %n", System.currentTimeMillis()); } }, d, 3000);//d是開始執行的時間到d才會執行 3000是開始執行后每3秒執行一次 Timer t2 = new Timer(); t2.schedule(new TimerTask() { @Override public void run() { timer.cancel();//對第一個定時器的撤銷 t2.cancel();//t2定時器撤銷 } }, 20 * 1000);//20秒后開始執行 }}
TimeUnit
java.util.concurrent包下面的一個類表示給定單元粒度的時間段
CountDownLatch
public class Thread9 { public static void main(String[] args) { new Thread(Test::add, "t1").start();//這里第一個參數是lambda表達式表示使用add方法第二個參數是設置該線程名字 new Thread(() -> { while (!Test.count()) ; System.out.println("已經有五個了"); }, "t2").start(); }}class Test { static volatile List<String> list = new ArrayList<>(); private static CountDownLatch latch = new CountDownLatch(1);//這里的參數是表示要打開幾次才能打開 public static void add() { String tn = Thread.currentThread().getName();//獲取線程名稱 System.out.println(tn + "線程啟動"); while (true) { try { TimeUnit.SECONDS.sleep(1);//睡眠一秒 } catch (InterruptedException e) { e.printStackTrace(); } list.add("item:" + (list.size() + 1));每次循環在list加一個元素 System.out.printf("%s = %s %n", list.size(), list.get(list.size() - 1)); //當list有5個元素時打開一次 if (list.size() == 5) { latch.countDown(); } } } //用於計數判斷個數 public static boolean count() { boolean f = false; try { latch.await();//線程等待當門閂被打開時將才會像下執行 f = true; } catch (InterruptedException e) { e.printStackTrace(); } return f; }}
synchronized
同步鎖(獨占鎖)
ReentrantLock常常對比着synchronized來分析,我們先對比着來看然后再一點一點分析。
(1)synchronized是獨占鎖,加鎖和解鎖的過程自動進行,易於操作,但不夠靈活。ReentrantLock也是獨占鎖,加鎖和解鎖的過程需要手動進行,不易操作,但非常靈活。
(2)synchronized可重入,因為加鎖和解鎖自動進行,不必擔心最后是否釋放鎖;ReentrantLock也可重入,但加鎖和解鎖需要手動進行,且次數需一樣,否則其他線程無法獲得鎖。
(3)synchronized不可響應中斷,一個線程獲取不到鎖就一直等着;ReentrantLock可以相應中斷。
ReentrantLock好像比synchronized關鍵字沒好太多,我們再去看看synchronized所沒有的,一個最主要的就是ReentrantLock還可以實現公平鎖機制。什么叫公平鎖呢?也就是在鎖上等待時間最長的線程將獲得鎖的使用權。通俗的理解就是誰排隊時間最長誰先執行獲取鎖。
同步方法格式(鎖的對象是this)
修飾符 synchronized 返回值類型 方法名(方法參數) {方法體;}
同步靜態方法格式(鎖的對象是.class)
修飾符 static synchronized 返回值類型 方法名(方法參數) {方法體;}
同步代碼塊(鎖的對象是由自己定)
synchronized(對象){方法體}
下面用一個購票來使用同步鎖
若不加鎖的話線程是不安全的則購票就會出現臟讀和錯讀
例票會出現同樣的和票數會出現負數
public class Thread6 { public static void main(String[] args) throws InterruptedException { Thicket t = new Thicket(); Thread t1 = new Thread(t, "t1"); Thread t2 = new Thread(t, "t2"); Thread t3 = new Thread(t, "t3"); t1.start(); t2.start(); t3.start(); }}class Thicket implements Runnable { int i = 100; @Override public void run() { while (true) { synchronized (this) { if (i == 0) { break; } System.out.println(Thread.currentThread().getName() + "拿到了第" + i + "張票"); i--; } } }}
ReentrantLock
重入鎖(公平鎖):每一個線程都會執行一遍后在執行
public class Test1 { Lock lock = new ReentrantLock(true);//設置為true公平鎖,效率低但公平 void m() { for (int i = 0; i <= 20; i++) { lock.lock();//加鎖 System.out.println(Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } lock.unlock();//解鎖 } } public static void main(String[] args) { var t = new Test1(); new Thread(t::m, "T1").start(); new Thread(t::m, "T2").start(); new Thread(t::m, "T3").start(); new Thread(t::m, "T4").start(); }}
/** * @author sulishijian * @date 2021/8/26-14:14 * @since 16 */public class Test1 { static Lock lock = new ReentrantLock(); public static void main(String[] args) { var t = new Thread(Test1::test); t.start(); t.interrupt();//打破鎖 } static void test() { try { lock.lockInterruptibly(); for (int i = 0; i < 10; i++) { System.out.println(i); } } catch (InterruptedException e) { System.out.println("......"); } finally { if (lock.tryLock()) { lock.unlock(); System.out.println("線程二被打斷"); } } }}
wait sleep
1 sleep()實現線程阻塞的方法,我們稱之為“線程睡眠”,方式是超時等待
2 wait()方法實現線程阻塞的方法,我們稱之為“線程等待”和sleep()方法一樣,通過傳入“睡眠時間”作為參數,時間到了就“醒了”;
不傳入時間,進行一次“無限期的等待”,只用通過notify()方法來“喚醒”。
3 sleep()釋放CPU執行權,但不釋放同步鎖;
4 wait()釋放CPU執行權,也釋放同步鎖,使得其他線程可以使用同步控制塊或者方法。
兩個案例第一個是管程法第二個是信號燈法來深入去了解wait
package demo1;/** * @author sulishijian * @date 2021/8/20-19:24 * @since 16 */public class Thread4 { public static void main(String[] args) { Panel panel = new Panel(); new Thread(() -> { for (int i = 0; i < 100; i++) { System.out.printf("生產了第%d只雞%n", i); panel.push(new Chicken(i)); } }).start(); new Thread(() -> { for (int i = 0; i < 100; i++) { System.out.printf("消費了第%d只雞%n", panel.pop().id); } }).start(); }}//定義一個共同的類class Chicken { int id; public Chicken(int id) { this.id = id; }}//容器類class Panel { volatile Chicken[] chickens = new Chicken[10];//定義一個對象數組用來存儲數據volatile用於讓兩個線程都可同時看到數組數據 int count = 0; public synchronized void push(Chicken chicken) { //如果個數有十個就進行等待消費 if (count == chickens.length) { try { this.wait();//釋放鎖進行等待喚醒 } catch (InterruptedException e) { e.printStackTrace(); } } chickens[count] = chicken;//將生產的東西放入數組中 count++;//計數器加一 this.notify();//如果沒有十個則去喚醒消費者去消費 } public synchronized Chicken pop() { //如果個數為0則進行等待生產 if (count == 0) { try { this.wait();//進行等待 } catch (InterruptedException e) { e.printStackTrace(); } } count--;//計數器減一 Chicken chicken = chickens[count];//將東西取出 this.notify();//喚醒生產者生產 return chicken; }}
package demo1;/** * @author sulishijian * @date 2021/8/26-20:17 * @since 16 */public class Thread14 { public static void main(String[] args) { TV tv = new TV(); new Thread(() -> { for (int i = 0; i < 20; i++) { if (i % 2 == 0) { tv.play("航海王"); } else { tv.play("斗羅大陸"); } } }).start(); new Thread(() -> { for (int i = 0; i < 20; i++) { tv.watch(); } }).start(); }}class TV { //若沒有節目則flag為false開始表演 若有節目則為true開始觀看 volatile boolean flag = false; String voice; public synchronized void play(String voice) { //如果為true則有節目 進行等待觀眾觀看 if (flag) { try { this.wait();//釋放鎖進行等待喚醒 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("表演了:" + voice); this.notifyAll();//此時為false喚醒觀眾進行觀看 this.voice = voice;//表演的節目 this.flag = !this.flag;//false變成true } public synchronized void watch() { //如果為false則為沒有節目 進行等待演員表演 if (!flag) { try { this.wait();//釋放鎖進行等待喚醒 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("觀看了:" + voice); this.notifyAll();//喚醒演員進行表演 this.flag = !this.flag;//將true變成false }}
join
ThreadTest t1=new ThreadTest("A");
ThreadTest t2=new ThreadTest("B");
t1.start();
t1.join();
t2.start();
先執行t1線程,再執行t2線程。ThreadTest t1=new ThreadTest("A");
ThreadTest t2=new ThreadTest("B");
ThreadTest t3=new ThreadTest("C");
t1.start();
t2.start();
t1.join();
t3.start();
t1線程和t2線程交替執行,當t1線程執行完后開始t3線程,執行過程中和t2沒有關系多次實驗可以看出,主線程在t1.join()方法處停止,並需要等待A線程執行完畢后才會執行t3.start(),然而,並不影響B線程的執行。因此,可以得出結論,t.join()方法只會使主線程進入等待池並等待t線程執行完畢后才會被喚醒。並不影響同一時刻處在運行狀態的其他線程。
PS:join源碼中,只會調用wait方法,並沒有在結束時調用notify,這是因為線程在die的時候會自動調用自身的notifyAll方法,來釋放所有的資源和鎖。
實現三個線程,運行輸出 A1 B2 C3 A4 B5 C6 …..
/** * @author sulishijian * @date 2021/8/26-14:14 * @since 16 */public class Test1 { AtomicInteger num = new AtomicInteger(0); synchronized void work() { try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } String n = Thread.currentThread().getName(); System.out.printf("%s%d ", n, num.incrementAndGet()); if ("C".equals(n)) { System.out.println(); } } public static void main(String[] args) throws InterruptedException { var t = new Test1(); while (true) { var t1 = new Thread(t::work, "A"); t1.start(); t1.join(); var t2 = new Thread(t::work, "B"); t2.start(); t2.join(); var t3 = new Thread(t::work, "C"); t3.start(); t3.join(); } }}
interrupt
/** * @author sulishijian * @date 2021/8/26-16:44 * @since 16 */public class Thread12 { static boolean flag = false; public static void main(String[] args) { var t = new Thread(Thread12::work); t.start(); try { TimeUnit.SECONDS.sleep(5); t.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } public static void work() { Thread t = Thread.currentThread(); String tt = t.getName(); System.out.printf("%s線程啟動了%n", tt); System.out.println(t.isInterrupted()); while (!t.isInterrupted()) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { break; } System.out.println("......."); } System.out.println(t.isInterrupted()); System.out.printf("%s線程結束了%n", tt); }}
多線程實例
線程安全
出現安全問題的條件
是多線程環境有共享數據有多條語句操作共享數據
解決方法
線程同步(排隊機制)
有線程安全問題的變量
//局部變量永遠都不會存在線程安全問題(局部變量在棧中永遠不會共享)實例變量靜態變量
死鎖
例如有A線程和B線程 第一個線程在A鎖里面寫B鎖 第二個線程在B鎖里面寫A鎖
A拿着A鎖在去拿B鎖 B拿着B鎖在去拿A鎖兩個線程都占着鎖想要對方的鎖就會一直僵持然后形成死鎖
建議在鎖里面睡眠一秒 否則有可能A運行過快以下拿完全部鎖
/** * @author sulishijian * @date 2021/8/26-14:14 * @since 16 */public class Test1 { static Object A = new Object(); static Object B = new Object(); public static void TA() { synchronized (A) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A"); synchronized (B) { System.out.println("B"); } } } public static void TB() { synchronized (B) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B"); synchronized (A) { System.out.println("A"); } } } public static void main(String[] args) { new Thread(Test1::TA).start(); new Thread(Test1::TB).start(); }}
求123456789 之間放+-和100的表達式,如果一個線程求出結果,立即告訴其它停止
/** * @author sulishijian * @date 2021/8/25-15:47 * @since 16 */public class Thread10 { static volatile Set<String> set = new HashSet<>(); static void op() { System.out.println(Thread.currentThread().getName() + "啟動中"); String[] o = new String[]{"", "+", "-"}; Random rand = new Random(); Pattern p = Pattern.compile("-?\\d+"); while (set.size() != 10) { StringBuffer sbf = new StringBuffer("1"); for (int i = 2; i <= 9; i++) { sbf.append(String.format("%s%d", o[rand.nextInt(o.length)], i)); } String s = sbf.toString(); Matcher m = p.matcher(s); List<Integer> list = new ArrayList<>(); while (m.find()) { list.add(Integer.parseInt(m.group())); } int sum = list.stream().reduce(0, Integer::sum); if (sum == 100 && !set.contains(s)) { set.add(s); System.out.println(Thread.currentThread().getName() + ":" + s); } } } public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(Thread10::op, "i" + i).start(); } }}
加10個元素到容器中,線程2實現監控元素的個數,當個數到5個時,線程2給出提示並結束
/** * @author sulishijian * @date 2021/8/25-9:46 * @since 16 */public class Thread9 { public static void main(String[] args) { new Thread(Test::add, "t1").start(); new Thread(() -> { while (!Test.count()) ; System.out.println("已經有五個了"); }, "t2").start(); }}class Test { static volatile List<String> list = new ArrayList<>(); private static CountDownLatch latch = new CountDownLatch(3); public static void add() { String tn = Thread.currentThread().getName(); System.out.println(tn + "線程啟動"); while (true) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } list.add("item:" + (list.size() + 1)); System.out.printf("%s = %s %n", list.size(), list.get(list.size() - 1)); if (list.size() % 5 == 0) { latch.countDown(); } } } public static boolean count() { boolean f = false; try { latch.await(); f = true; } catch (InterruptedException e) { e.printStackTrace(); } return f; }}