這一塊的內容主要是有關死鎖、執行緒間通信、執行緒組、執行緒池以及定時器的內容,
這一部分的內容,如果想搞得比較明白,最好先看一下上一篇大資料進階25的內容,地址如下:
大資料進階25-多執行緒
Lock
在上一篇大資料 進階25-多執行緒 里面,雖然我們可以理解同步代碼塊和同步方法的鎖物件問題,但是我們并沒有直接看到在哪里上了鎖,在哪里釋放了鎖,為了更清晰的表達如何加鎖和釋放鎖,JDK5之后提供了一個新的鎖物件Lock
Lock(介面):
void lock() 獲得鎖,加鎖
void unlock() 釋放鎖
子類:
ReentrantLock
這也是解決執行緒同步安全的第二種方式
class SellTick1 implements Runnable{
//定義票的個數
private int tickets = 100;
//定義鎖物件
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try{
lock.lock();
if (tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+
(tickets--)+"張票");
}
}finally {
//釋放鎖
lock.unlock();
}
}
}
}
public class SellTicketDemo1 {
public static void main(String[] args) {
SellTick1 s = new SellTick1();
//創建三個執行緒視窗
Thread t1 = new Thread(s, "視窗一");
Thread t2 = new Thread(s, "視窗二");
Thread t3 = new Thread(s, "視窗三");
//啟動執行緒
t1.start();
t2.start();
t3.start();
}
}
死鎖
同步的弊端:
- 效率低
- 如果出現了同步嵌套,就容易產生死鎖問題
死鎖:是指兩個或者兩個以上的執行緒在執行的程序中,因爭奪資源產生的一種互相等待現象
//定義兩把鎖,兩個唯一不能被修改的靜態鎖物件
class MyLock{
public static final Object lockA = new Object();
public static final Object lockB = new Object();
}
class DeadLock extends Thread{
private boolean flag;
public DeadLock(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized (MyLock.lockA){
System.out.println("if lockA");
synchronized (MyLock.lockB){
System.out.println("if lockB");
}
}
}else {
synchronized (MyLock.lockB){
System.out.println("else lockB");
synchronized (MyLock.lockA){
System.out.println("else lockA");
}
}
}
}
}
public class DeadLockDemo {
public static void main(String[] args) {
DeadLock foreigner = new DeadLock(true);
DeadLock chinese = new DeadLock(false);
foreigner.start();
chinese.start();
}
}
執行緒間通信
我們之前寫的電影票程式不是特別符合真是情況,所以我們在這之上對其做一個改進,引入執行緒間通信
示例代碼1:
class Student1{
String name;
int age;
}
class SetThread implements Runnable{
private Student1 s;
public SetThread(Student1 s){
this.s = s;
}
@Override
public void run() {
s.name = "A";
s.age = 21;
}
}
class GetThread implements Runnable{
private Student1 s;
public GetThread(Student1 s){
this.s = s;
}
@Override
public void run() {
System.out.println(s.name+"---"+s.age);
}
}
/*
學生類:Student
設定學生資訊類:SetStudent(生產者)
獲取學生資訊類:GetStudent(消費者)
測驗類
問題1:按照思路去寫,發現每次出現的都是null---0
原因:我們在每個執行緒中都創建了新物件,而我們要求的是設定學生資訊和獲取學生資訊的物件應該是同一個
如何實作:在外界把學生物件創建出來,通過構造方法傳遞給其他類
*/
public class StudentDemo1 {
public static void main(String[] args) {
Student1 s = new Student1();
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
}
}
示例代碼2:
class Student2{
String name;
int age;
}
class SetThread2 implements Runnable{
private Student2 s;
private int x = 0;
public SetThread2(Student2 s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (Student2.class){
if(x%2==0){
s.name = "A";
s.age = 21;
}else {
s.name = "B";
s.age = 12;
}
x++;
}
}
}
}
class GetThread2 implements Runnable{
private Student2 s;
public GetThread2(Student2 s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (Student2.class){
System.out.println(s.name+"---"+s.age);
}
}
}
}
/*
學生類:Student
設定學生資訊類:SetStudent(生產者)
獲取學生資訊類:GetStudent(消費者)
測驗類
問題2:為了資料好看一些,更容易出現結果,我們加入了回圈和判斷,給出不同的值,但
又出現了新的問題
1、同一個資料出現了多次
2、姓名和年齡不匹配
原因:
1、同一個資料出現了多次
CPU的一點點時間片的執行權,就足夠執行很多次
2、姓名和年齡不匹配
執行緒運行的隨機性
執行緒安全問題:
1、是否是多執行緒環境 是
2、是否又共享資料 是
3、是否有多條陳述句操作共享資料 是
解決:
加鎖
注意:
1、不同種類的執行緒都要加鎖
2、不同種類的執行緒加的鎖必須是同一把
*/
public class StudentDemo2 {
public static void main(String[] args) {
Student2 s = new Student2();
SetThread2 st = new SetThread2(s);
GetThread2 gt = new GetThread2(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
}
}
等待喚醒機制
問題3:雖然我們的資料是安全的,但是,每一次我們都是一次獲取一大片資料,我想依次一個一個的輸出,如何實作呢?
通過Java提供的等待喚醒機制解決
等待喚醒:
Object類中提供了三個方法:
- wait() 等待
- notify() 喚醒正在等待物件監視器的單個行程
- notifyAll() 喚醒正在等待物件監視器的所有行程
為什么這些方法不定義在Thread類中呢?
這些方法的呼叫必須由鎖物件呼叫,而我們剛剛使用的是synchronized里面的鎖物件與呼叫wait和notify的物件不一致,會出現例外IllegalMonitorStateException,只要鎖物件一致,就不會出錯,這個鎖物件可以是任意物件,而你不確定是哪一個物件,但是我們知道所有類的父類是Object,所以,這些方法就定義在Object中
class Student3{
String name;
int age;
boolean flag;//默認情況是沒有資料的,默認是false,如果是true,說明由資料
}
class SetThread3 implements Runnable{
private Student3 s;
private int x = 0;
public SetThread3(Student3 s){
this.s = s;
}
@Override
public void run() {
while (true){
synchronized (Student3.class){
//判斷有沒有資料
if (s.flag){
try {
Student3.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(x%2==0){
s.name = "A";//剛走到這里,就被其他執行緒搶到了執行權
s.age = 21;
}else {
s.name = "B";//剛走到這里,又被其他執行緒搶到了執行權
s.age = 12;
}
x++;
s.flag = true;
Student3.class.notify();
}
}
}
}
class GetThread3 implements Runnable{
private Student3 s;
public GetThread3(Student3 s){
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (Student3.class) {
if (!s.flag) {
try {
Student3.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
s.flag = false;
Student3.class.notify();
}
}
}
}
public class StudentDemo3 {
public static void main(String[] args) {
Student3 s = new Student3();
SetThread3 st = new SetThread3(s);
GetThread3 gt = new GetThread3(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
}
}
執行緒轉換的幾種情形
執行緒組
Java可以使用 ThreadGroup來表示執行緒組,它可以對一批執行緒進行分類管理,Java允許程式直接對執行緒組進行控制
簡單來說:執行緒組就是把多個執行緒組合到一起
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class ThreadGroupDemo {
public static void main(String[] args) {
fun1();
// fun2();
}
private static void fun2() {
//創建新的執行緒組
//ThreadGroup(String name)
ThreadGroup tg = new ThreadGroup("第一個執行緒組");
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "A");
Thread t2 = new Thread(my, "B");
System.out.println(t1.getThreadGroup().getName());
System.out.println(t2.getThreadGroup().getName());
//通過組名名稱可以直接將組里的執行緒都設定為守護執行緒
tg.setDaemon(true);
t1.start();
t2.start();
}
private static void fun1() {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my, "A");
Thread t2 = new Thread(my, "B");
//ThreadGroup getThreadGroup()
//回傳此執行緒所屬的執行緒組
ThreadGroup tg1 = t1.getThreadGroup();
ThreadGroup tg2 = t2.getThreadGroup();
//String getName()
//回傳此執行緒組的名稱
System.out.println(tg1.getName());//main
System.out.println(tg2.getName());//main
System.out.println(tg1.getMaxPriority());//10
//通過驗證發現,執行緒默認情況下屬于main執行緒組
}
}
使用執行緒組寫一個最終代碼
1、把Student的成員變數都變成私有的
2、把生產和消費的操作封裝成兩個方法,并加入同步和喚醒機制
3、生產和消費的執行緒只需要呼叫方法即可
class Student4{
private String name;
private int age;
private boolean flag;//默認是false
//設定資料,相當于生產者,沒有資料(false)就生產,有資料(true)就等待
public synchronized void set(String name,int age){
if(this.flag){
try {
this.wait();
}catch (Exception e){
e.printStackTrace();
}
}
this.name = name;
this.age = age;
this.flag = true;
this.notify();
}
//獲取資料,相當于消費者,沒有資料(false)就等待(wait),有資料(true)就消費
public synchronized void get(){
if(!this.flag){//加感嘆號是為了順利進入回圈
try {
this.wait();
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println(this.name+"---"+this.age);
this.flag = false;
this.notify();
}
}
class SetThread4 implements Runnable{
private Student4 s;
private int x = 0;
public SetThread4(Student4 s){
this.s = s;
}
@Override
public void run() {
while (true){
if(x%2==0){
s.set("A",21);
}else{
s.set("B",12);
}
x++;
}
}
}
class GetThread4 implements Runnable{
private Student4 s;
public GetThread4(Student4 s){
this.s = s;
}
@Override
public void run() {
while (true){
s.get();
}
}
}
public class StudentDemo4 {
public static void main(String[] args) {
Student4 s = new Student4();
SetThread4 st = new SetThread4(s);
GetThread4 gt = new GetThread4(s);
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
t1.start();
t2.start();
}
}
執行緒池
執行緒池的好處:
執行緒池的每一個執行緒代碼結束后,并不會死亡,而是再次回到執行緒池中成為空閑狀態,等待下一個物件來使用
如何實作執行緒池:
1、創建一個執行緒池物件,控制要創建幾個執行緒物件
public static ExecutorServe newFixedThreadPool(int nThreads)
創建一個執行緒池,指定執行緒池大小
2、哪些執行緒可以放到執行緒池執行
可以執行Runnable物件或者Callable物件代表的執行緒
3、如何執行
Future<?> submit(Runnable task)
提交一個可運行的任務執行,并回傳一個表示該任務的未來,
Future submit(Callable task)
提交值回傳任務以執行,并回傳代表任務待處理結果的Future,
4、想結束任務怎么辦
void shutdown()
啟動有序關閉,其中先前提交的任務將被執行,但是不會接受任何新任務
class MyRunnable2 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
public class ExecutorsDemo {
public static void main(String[] args) {
//創建一個執行緒池物件,控制要創建幾個執行緒物件
ExecutorService fixpool = Executors.newFixedThreadPool(2);
//使用匿名內部類的形式將Runnable物件或者Callable物件放進去并執行
fixpool.submit(new MyRunnable2());
fixpool.submit(new MyRunnable2());
fixpool.shutdown();
}
}
多執行緒的第三種實作方式
第三種方式:實作Callable介面,必須和執行緒池結合使用
class MyCallable implements Callable {
@Override
public Object call() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
return null;
}
}
public class CallableDemo {
public static void main(String[] args) {
//創建執行緒池物件
ExecutorService fixpool = Executors.newFixedThreadPool(2);
MyCallable c1 = new MyCallable();
MyCallable c2 = new MyCallable();
fixpool.submit(c1);
fixpool.submit(c2);
fixpool.shutdown();
}
}
匿名內部類的方式實作多執行緒
匿名內部類的格式:
new 類名或者介面名(){
重寫方法
};
本質:該類或者是介面的子類物件
public static void main(String[] args) {
//繼承Thread類來實作多執行緒
// new Thread(){
// @Override
// public void run() {
// for(int i=0;i<10;i++){
// System.out.println("繼承Thread類:"+Thread.currentThread().getName()+
// "---"+i);
// }
// }
// }.start();
//
// //實作Runnable介面
// new Thread(new Runnable() {
// @Override
// public void run() {
// for(int i=0;i<10;i++){
// System.out.println("實作Runnable介面:"+Thread.currentThread().getName()+
// "---"+i);
// }
// }
// }).start();
//執行的是Thread本身的fun方法,
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println("實作Runnable介面2:"+Thread.currentThread().getName()+
"---"+i);
}
}
}){
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println("繼承自Thread類2:"+Thread.currentThread().getName()+
"---"+i);
}
}
}.start();
}
定時器
定時器是一個應用十分廣泛的執行緒工具,可用于調度多個定時任務以后臺執行緒的方式執行,在Java中,可以通過Timer和TimerTask類來實作定義調度的功能
Timer:定時
Timer() 創建一個新的計時器
void schedule(TimerTask task,long delay)在指定的延遲之后安排指定的任務執行
void schedule(TimerTask task,long delay,long period)在指定的延遲之后開始,重寫執行
void cancel()終止此計數器,丟棄任何當前計劃的任務
TimerTask:任務
class MyTask extends TimerTask{
private Timer timer;
public MyTask(Timer timer){
this.timer = timer;
}
@Override
public void run() {
System.out.println("計時結束" );
timer.cancel();
}
}
public class TimerDemo {
public static void main(String[] args) {
//創建一個計時器物件
Timer timer = new Timer();
// timer.schedule(new MyTask(timer),3000);
//void schedule(TimerTask task,long delay,long period)
//在指定的延遲之后開始 ,重新執行固定延遲執行的指定任務,
timer.schedule(new MyTask(timer),3000);
}
}
感謝閱讀,我是啊帥和和,一位大資料專業即將大四學生,祝你快樂,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/295593.html
標籤:其他
下一篇:初聞 RabbitMQ