第14章 多线程基础
14.1 线程
几个基本概念:
程序:我们写的代码
进程:运行中的程序 或者说程序的一次执行过程 是动态的 有本身产生 存在 消亡的过程
线程:由进程创建 作为进程的一个实体 一个进程可以有多个线程
单线程:同一时刻只能执行一个线程
多线程:同一时刻可以执行多个线程
并发:同一时刻 多个任务交替执行 看起来是貌似同时 举例:单核CPU实现的多任务
并行:同一时刻 多个任务同时执行 举例:多核CPU可以实现并行
14.2 线程基本使用
创建线程两种方式
- 继承Thread类 重写run方法
import javax.xml.transform.Source; import java.sql.SQLOutput; public class Test { public static void main(String[] args) throws InterruptedException { Cat cat=new Cat(); cat.start(); System.out.println("主线程继续执行"); for(int i=1;i<=10;i++){ System.out.println("小狗叫 "+i+" "+Thread.currentThread().getName()); Thread.sleep(1000); } } } class Cat extends Thread{ int times=0; @Override public void run(){ while(true){ System.out.println("小猫叫 "+(++times)+" "+Thread.currentThread().getName()); try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } if(times==20){ break; } } } }
所以在控制台的输出为先main的Thread执行 再Cat的Thread执行 说明在调用cat.run()之后 只是发出请求告知可以执行 具体取决于CPU
- 实现Runnable接口 重写run方法
首先 因为java是单继承的 如果一个类已经继承了某个父类 再继承Thread类显然不现实 所以有另一种方式 实现Runnable接口来创建线程
import javax.xml.transform.Source; import java.sql.SQLOutput; public class Test { public static void main(String[] args) throws InterruptedException { Cat cat=new Cat(); Thread thread=new Thread(cat); thread.start(); System.out.println("主线程继续执行"); for(int i=1;i<=10;i++){ System.out.println("小狗叫 "+i+" "+Thread.currentThread().getName()); Thread.sleep(1000); } } } class Cat implements Runnable{ int times=0; @Override public void run(){ while(true){ System.out.println("小猫叫 "+(++times)+" "+Thread.currentThread().getName()); try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); } if(times==20){ break; } } } }
个人理解:
实际上创建线程都要借助thread类 只不过对于没有父类的类来说 可以直接继承thread并重写run方法进而调用它的start方法创建线程 而对于有父类的类来说 因为java的单线程特性 不能再继承thread 所以需要实现Runnable接口 再将该类的实例对象传入Thread 进而再调用thread的start方法
14.3二者区别:
就上面的个人理解 本质上二者都是为了调用thread的start方法 没有区别
但 实现Runnable接口方式更加适合多个线程共享一个资源的情况 而且避免的单继承的限制 所以推荐使用Runnable接口;而且 Runnable接口代码复用性比较高;而且实现Runnable
接口的类只是定义了任务逻辑,它与Thread
类是一种组合关系(通过Thread
构造函数将Runnable
实例传入)。这样,Thread
类负责线程相关的操作,Runnable
类负责任务逻辑,职责更加分明。
public class Test { public static void main(String[] args) { sellticket1 window1=new sellticket1(); sellticket1 window2=new sellticket1(); sellticket1 window3 =new sellticket1(); window1.start(); window2.start(); window3.start(); } } class sellticket1 extends Thread{ private int ticket_count=100; @Override public void run(){ while (true){ if(ticket_count<0){ System.out.println("票售罄"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口"+ Thread.currentThread().getName()+"售出一张票 剩余票数:"+(--ticket_count)); } } } class selltickets2 implements Runnable{ private int ticket_count=100; @Override public void run(){ while (true){ if(ticket_count<0){ System.out.println("票售罄"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口"+ Thread.currentThread().getName()+"售出一张票 剩余票数:"+(--ticket_count)); } } }
很明显 出现了超卖的问题
public class Test { public static void main(String[] args) { sellticket2 window1=new sellticket2(); sellticket2 window2=new sellticket2(); sellticket2 window3 =new sellticket2(); new Thread(window1).start(); new Thread(window2).start(); new Thread(window3).start(); } } class sellticket1 extends Thread{ private int ticket_count=100; @Override public void run(){ while (true){ if(ticket_count<0){ System.out.println("票售罄"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口"+ Thread.currentThread().getName()+"售出一张票 剩余票数:"+(--ticket_count)); } } } class sellticket2 implements Runnable{ private int ticket_count=100; @Override public void run(){ while (true){ if(ticket_count<0){ System.out.println("票售罄"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口"+ Thread.currentThread().getName()+"售出一张票 剩余票数:"+(--ticket_count)); } } }
同样出现了超卖问题
14.4线程终止
- 线程完成任务后 会自动退出
- 还可以通过使用变量控制run方法退出 即通知方式
public class Test { public static void main(String[] args) { sellticket1 window1=new sellticket1(); sellticket1 window2=new sellticket1(); sellticket1 window3 =new sellticket1(); window1.start(); window2.start(); window3.start(); window1.setLoop(false); } } class sellticket1 extends Thread{ private int ticket_count=100; private boolean loop=true; public void setLoop(boolean loop) { this.loop = loop; } @Override public void run(){ while (loop){ if(ticket_count<0){ System.out.println("票售罄"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口"+ Thread.currentThread().getName()+"售出一张票 剩余票数:"+(--ticket_count)); } } } class sellticket2 implements Runnable{ private int ticket_count=100; @Override public void run(){ while (true){ if(ticket_count<0){ System.out.println("票售罄"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口"+ Thread.currentThread().getName()+"售出一张票 剩余票数:"+(--ticket_count)); } } }
可以看到 已经没有window0的售票记录 因为我们将window0的loop设置为false
14.5 线程常用方法
class Test{ public static void main(String[] args) throws InterruptedException { T t=new T(); t.setName("byx"); t.setPriority(Thread.MIN_PRIORITY); t.start(); for(int i=0;i<5;i++){ Thread.sleep(1000); System.out.println(i); } System.out.println(t.getPriority()); t.interrupt();//将主线程打断 } } class T extends Thread{ @Override public void run(){ while (true){ for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+i); } try { System.out.println(Thread.currentThread().getName()+"休眠中"); Thread.sleep(20000); }catch (InterruptedException e){ System.out.println(Thread.currentThread().getName()+"被interrupt了"); } } } }
public class ThreadMethod02 { public static void main(String[] args) throws InterruptedException { T2 t2 = new T2(); t2.start(); for(int i = 1; i <= 20; i++) { Thread.sleep(1000); System.out.println("主线程(小弟) 吃了 " + i + " 包子"); if(i == 5) { System.out.println("主线程(小弟) 让 子线程(老大) 先吃"); //join, 线程插队 t2.join();// 这里相当于让t2 线程先执行完毕 //Thread.yield();//礼让,不一定成功.. System.out.println("线程(老大) 吃完了 主线程(小弟) 接着吃.."); } } } } class T2 extends Thread { @Override public void run() { for (int i = 1; i <= 20; i++) { try { Thread.sleep(1000);//休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程(老大) 吃了 " + i + " 包子"); } } }
join()线程插队一定成功
yield()线程礼让不一定成功 因为礼让时间不一定
练习:
public class ThreadMethod02 { public static void main(String[] args) throws InterruptedException { Thread t2 = new Thread(new T2()); for(int i = 1; i <= 10; i++) { Thread.sleep(1000); System.out.println("hi"+i); if(i == 5) { t2.start(); t2.join(); } Thread.sleep(1000); } } } class T2 extends Thread { int count=0; @Override public void run() { while (true){ System.out.println("hello"+(++count)); try { Thread.sleep(1000);//休眠1秒 } catch (InterruptedException e) { e.printStackTrace(); } if(count==10){ break; } } } }
用户线程和守护线程
public class ThreadMethod03 { public static void main(String[] args) throws InterruptedException { MyDaemonThread myDaemonThread = new MyDaemonThread(); //如果我们希望当main线程结束后,子线程自动结束 //,只需将子线程设为守护线程即可 myDaemonThread.setDaemon(true); myDaemonThread.start(); for( int i = 1; i <= 10; i++) {//main线程 System.out.println("宝强在辛苦的工作..."); Thread.sleep(1000); } } } class MyDaemonThread extends Thread { public void run() { for (; ; ) {//无限循环 try { Thread.sleep(1000);//休眠1000毫秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~"); } } }
14.6 线程生命周期
14.7 Synchronized
线程同步机制
同步具体方法
原理:
14.8 互斥锁
package com.hspedu.syn; public class SellTicket { public static void main(String[] args) { //测试一把 SellTicket03 sellTicket03 = new SellTicket03(); new Thread(sellTicket03).start();//第1个线程-窗口 new Thread(sellTicket03).start();//第2个线程-窗口 new Thread(sellTicket03).start();//第3个线程-窗口 } } //实现接口方式, 使用synchronized实现线程同步 class SellTicket03 implements Runnable { private int ticketNum = 100;//让多个线程共享 ticketNum private boolean loop = true;//控制run方法变量 Object object = new Object(); //同步方法(静态的)的锁为当前类本身 //解读 //1. public synchronized static void m1() {} 锁是加在 SellTicket03.class //2. 如果在静态方法中,实现一个同步代码块. /* synchronized (SellTicket03.class) { System.out.println("m2"); } */ public synchronized static void m1() {} public static void m2() { synchronized (SellTicket03.class) { System.out.println("m2"); } } //说明 //1. public synchronized void sell() {} 就是一个同步方法 //2. 这时锁在 this对象 //3. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象 public /*synchronized*/ void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法 synchronized (/*this*/ object) { if (ticketNum <= 0) { System.out.println("售票结束..."); loop = false; return; } //休眠50毫秒, 模拟 try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票" + " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2 } } @Override public void run() { while (loop) { sell();//sell方法是一共同步方法 } } }
总的来说,
this
对象用于实例方法的同步,确保同一对象实例的同步方法在同一时刻只有一个线程访问;类对象用于静态方法的同步,确保对于整个类的静态同步方法在同一时刻只有一个线程访问。这是 Java 为了适应实例级别和类级别不同的同步需求而设计的机制。
14.9 线程死锁
14.10 释放锁场景:
不会释放锁场景:
14.11 银行取钱
public class Homework02 {
public static void main(String[] args) {
T t = new T();
Thread thread1 = new Thread(t);
thread1.setName("t1");
Thread thread2 = new Thread(t);
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
//编程取款的线程
//1.因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
//2. 每次取出 1000
class T implements Runnable {
private int money = 10000;
@Override
public void run() {
while (true) {
//解读
//1. 这里使用 synchronized 实现了线程同步
//2. 当多个线程执行到这里时,就会去争夺 this对象锁
//3. 哪个线程争夺到(获取)this对象锁,就执行 synchronized 代码块, 执行完后,会释放this对象锁
//4. 争夺不到this对象锁,就blocked ,准备继续争夺
//5. this对象锁是非公平锁.
synchronized (this) {//
//判断余额是否够
if (money < 1000) {
System.out.println("余额不足");
break;
}
money -= 1000;
System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money);
}
//休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2024/11/26 1:29了 作者困得不行了 明早还有课 所以最后synchronized囫囵吞枣 请各位读者监督明天再顺一遍 并复习完IO流