> 文档中心 > 万字手撕Java多线程

万字手撕Java多线程

文章目录

    • 什么是多线程
    • 创建线程的两种方式
      • ①、继承 Thread 的方法
      • ②、runnable接口实现run方法
    • 为什么要重写run方法
    • 设计模式
    • 手动实践编程题
    • 编程模拟三个售票窗口售票100张
    • 手动实践编程题
    • 线程常用方法
    • interrupt 中断线程
    • 编程题
    • 用户线程和守护线程
    • 6种线程
    • 线程同步机制(重点)
    • 互斥锁
    • 线程的死锁
    • 编程题
    • 编程题

博主简介:
博客主页:Java知识分享博主
Java零基础入门专栏:Java零基础入门专栏
Java交流社区:飞鸟社区
欢迎阅读,如果文章对你有帮助点赞,支持一下!

什么是多线程

程序、进程、线程

  • 程序:从所周知的,编写代码组成的。

  • 进程:可以理解为运行中的程序,比如启动QQ,就启动一个进程,操作系统会为进程分配内存空间。

  • 线程:由进程创建的,是进程的一个实体。

单线程和多线程的区分:

  • 同一个时刻,只允许执行一个线程

  • 同一个时刻,可以执行多个线程,比如QQ可以同时打开多个聊天窗口

并发和并行(理解)

  • 一个多个任务交替执行,一个多个任务同时执行

创建线程的两种方式

通过继承 Thread 的方法和实现 Runnable 接口的方式创建多线程,哪个好?

实现Runnable接口好,原因有两个:

  • ①、避免了Java单继承的局限性
  • ②、适合多个相同的程序代码去处理同一资源的情况,把线程、代码和数据有效的分离,更符合面向对象的设计思想。

①、继承 Thread 的方法

  1. 当一个类(cat类)继承了 Thread 类, 该类就可以当做线程使用
  2. 我们会重写 run 方法,写上自己的业务代码
  3. run Thread 类 实现了 Runnable 接口的 run
package threaduse;public class Thread01 {    public static void main(String[] args) { Cat cat = new Cat(); cat.start();//启动线程    }}class Cat extends Thread{int times=0; public void run () { //重写 run 方法,写上自己的业务逻辑     while(true) { System.out.println("猫会跑"+times++);  try {      Thread.sleep(1000);  } catch (InterruptedException e) {      e.printStackTrace();  }  if(times==8){ //当时间到第8秒时,自动跳出      break;  }     }    }}

②、runnable接口实现run方法

1、dog.start();这里不能调用 start
2、创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread

package threaduse;public class Thread02 {    public static void main(String[] args) { Dog dog = new Dog();  // dog.start();这里不能调用 start //创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread  Thread thread = new Thread(dog); thread.start();    }}class Dog implements Runnable{//通过实现 Runnable 接口,开发线程    int count=0;    public void run(){ while (true){     System.out.println("小狗会汪汪叫。。"+(++count)+Thread.currentThread().getName());     try {  Thread.sleep(1000); //休眠1秒     } catch (InterruptedException e) {  e.printStackTrace();     }     if(count==8){  break;     } }    }}

为什么要重写run方法

因为run方法是用来封装被线程执行的代码。

run()方法和start()方法有什么区别?

  • run():封装线程执行的代码,直接调用相当于调用普通方法。
  • start():启动线程,然后由JVM 调用此线程的 run() 方法。

设计模式

package threaduse;public class Thread02 {    public static void main(String[] args) {// Dog dog = new Dog();//// dog.start();这里不能调用 start// //创建了 Thread 对象,把 dog 对象(实现 Runnable),放入 Thread// Thread thread = new Thread(dog);// thread.start(); tiger tiger = new tiger(); ThreadProxy threadProxy = new ThreadProxy(tiger); threadProxy.start();    }}class Animal{}class tiger extends Animal implements Runnable{    public void run(){ System.out.println("老虎嗷嗷叫");    }}//线程代理类 , 模拟了一个极简的 Threadclass ThreadProxy implements Runnable{private Runnable target =null;//属性,类型是Runnable    public void run() { if(target!=null){     target.run();//动态绑定(运行类型 Tiger) }    }    public ThreadProxy(Runnable target){ this.target=target;//构造方法    }    public void start(){ start0();//这个方法时真正实现多线程方法    }    public void start0(){ run();    }}class Dog implements Runnable{//通过实现 Runnable 接口,开发线程    int count=0;    public void run(){ while (true){     System.out.println("小狗会汪汪叫。。"+(++count)+Thread.currentThread().getName());     try {  Thread.sleep(1000);     } catch (InterruptedException e) {  e.printStackTrace();     }     if(count==8){  break;     } }    }}

手动实践编程题

编写一个程序,创建两个线程,一个线程每隔1秒输出“helloworld”,输出10次退出, 一个线程每隔1秒输出“hi”,输出5次退出(可以手动敲一下.狗头)

package threaduse;//编写一个程序,创建两个线程,一个线程每隔1秒输出“helloworld”,输出10次退出,// 一个线程每隔1秒输出“hi”,输出5次退出public class Thread03 {    public static void main(String[] args) { T1 t1 = new T1(); T2 t2 = new T2(); Thread thread1 = new Thread(t1); Thread thread2 = new Thread(t2); thread1.start(); thread2.start();    }}class T1 implements Runnable{    int count=0;    public void run(){ while (true){     System.out.println("helloworld"+(++count)+Thread.currentThread().getName());     try {  Thread.sleep(1000);     } catch (InterruptedException e) {  e.printStackTrace();     }     if(count==10){  break;     } }    }}class T2 implements Runnable{    int count=0;    public void run(){ while (true){     System.out.println("hi"+(++count)+Thread.currentThread().getName());     try {  Thread.sleep(1000);     } catch (InterruptedException e) {  e.printStackTrace();     }     if(count==5){  break;     } }    }}

编程模拟三个售票窗口售票100张

package threaduse;//编程模拟三个售票窗口售票100张,// 分别使用继承Thread和实现Runnable方式,并分析有什么问题?public class Thread04 {    public static void main(String[] args) { SellTicket sellTicket = new SellTicket(); new Thread(sellTicket).start();//第一个售票窗口 new Thread(sellTicket).start();//第二个售票窗口 new Thread(sellTicket).start();//第三个售票窗口    }}class SellTicket extends Thread{    private static int  num=100;    public  void run(){ while (true){     if(num<=0){  System.out.println("售票结束。。");  break;     }     try {  Thread.sleep(100);     } catch (InterruptedException e) {  e.printStackTrace();     }     System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"+"剩余票数="+(--num)); }    }}

手动实践编程题

启动一个线程t,要求main线程中去停止线程t,关键代码块

成员内部类中

package exit;public class ThreadExit {    public static void main(String[] args) throws InterruptedException { T t = new T(); new Thread(t).start(); System.out.println("main线程休眠10秒"); Thread.sleep(10*1000); t.setLoop(false);    }}class T implements Runnable{    private boolean loop=true;    private int count=0;    public void run(){ while(loop){     try {  Thread.sleep(50);     } catch (InterruptedException e) {  e.printStackTrace();     }     System.out.println("a运行中。。。"+(++count)); }    }    public void setLoop(boolean loop){ this.loop=loop;    }}

线程常用方法

万字手撕Java多线程

interrupt 中断线程

万字手撕Java多线程

万字手撕Java多线程

编程题

两个线程同时执行,当主线程执行输出5次时,让子线程执行剩下15次输出,主线程再次执行。

public class ThreadMethodExercise {    public static void main(String[] args) throws InterruptedException { Thread t3 = new Thread(new T3());//创建子线程 for (int i = 1; i <= 10; i++) {     System.out.println("hi " + i);     if(i == 5) {//说明主线程输出了 5 次 hi  t3.start();//启动子线程 输出 hello... t3.join();  t3.join();// 立即将 t3 子线程,插入到 main 线程,让 t3 先执行     }     Thread.sleep(1000);//输出一次 hi, 让 main 线程也休眠 1s }    }}class T3 implements Runnable {    private int count = 0;    @Override    public void run() { while (true) {     System.out.println("hello " + (++count));     try {  Thread.sleep(1000);     } catch (InterruptedException e) {  e.printStackTrace();     }     if (count == 10) {  break;     } }    }}

Thread.yield;礼让不一定成功
t3.join();插队一定成功

用户线程和守护线程

万字手撕Java多线程

//设t为子线程,即设为守护线程,当所有线程结束后,t也自动结束//如果没有设置,那么main线程执行完毕,t也不退出//前提是子线程无限循环t.setDaemon(true);//关键字t.start();

万字手撕Java多线程

6种线程

public enum State {    NEW,    RUNNABLE,    BLOCKED,    WAITING,    TIMED_WAITING,    TERMINATED;}
package state;public class ThreadState {    public static void main(String[] args) throws InterruptedException { T t = new T(); System.out.println(t.getName() + " 状态 " + t.getState()); t.start(); while (Thread.State.TERMINATED != t.getState()) {     System.out.println(t.getName() + " 状态 " + t.getState());     Thread.sleep(1000); } System.out.println(t.getName() + " 状态 " + t.getState());    }}class T extends Thread {    @Override    public void run() { while (true) {     for (int i = 0; i < 10; i++) {  System.out.println("hi " + i);  try {      Thread.sleep(1000);  } catch (InterruptedException e) {      e.printStackTrace();  }     }     break; }    }}

线程同步机制(重点)

万字手撕Java多线程

万字手撕Java多线程

  • 编程模拟三个售票窗口售票100张,
  • 分别使用继承Thread和实现Runnable方式,并分析有什么问题?
package sys;public class Thread05 {    public static void main(String[] args) { SellTicket3 sellTicket3 = new SellTicket3(); new Thread(sellTicket3).start();//第一个售票窗口 new Thread(sellTicket3).start();//第二个售票窗口 new Thread(sellTicket3).start();//第三个售票窗口    }}class SellTicket3 implements Runnable{    private int num=100;    private boolean loop =true;    public synchronized void sell(){ //同步线程,只允许一个线程执行 if(num<=0){     System.out.println("售票结束。。");     loop=false;     return; } try {     Thread.sleep(100); } catch (InterruptedException e) {     e.printStackTrace(); } System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"+"剩余票数="+(--num));    }    public void run(){ while (loop){     sell(); }    }}

互斥锁

万字手撕Java多线程

//1. public synchronized static void m1() {} 锁是加在 SellTicket03.class//2. 如果在静态方法中,实现一个同步代码块/*    synchronized (SellTicket4.class) { System.out.println("m2");    }*/    public synchronized static void m1() {    }    public static void m2() { synchronized (SellTicket4.class) {     System.out.println("m2"); }
  • 1、 public synchronized void sell() {} 就是一个同步方法
  • 2、这时锁在this对象
  • 3、 也可以在代码块上写synchronize,同步代码块,互斥锁还是在this对象
package sys;//编程模拟三个售票窗口售票100张,// 分别使用继承Thread和实现Runnable方式,并分析有什么问题?public class Thread06 {    public static void main(String[] args) { SellTicket4 sellTicket3 = new SellTicket4(); new Thread(sellTicket3).start();//第一个售票窗口 new Thread(sellTicket3).start();//第二个售票窗口 new Thread(sellTicket3).start();//第三个售票窗口    }}class SellTicket4 implements Runnable{    private int num=50;    private boolean loop =true;    Object object=new Object();     public  void sell() { //同步方法,同一个时刻,只允许一个sell线程执行 synchronized (object) {     if (num <= 0) {  System.out.println("售票结束。。");  loop = false;  return;     }     try {  Thread.sleep(100);     } catch (InterruptedException e) {  e.printStackTrace();     }     System.out.println("窗口" + Thread.currentThread().getName() + "售出一张票" + "剩余票数=" + (--num)); }    }    public void run(){ while (loop){     sell(); }    }}

万字手撕Java多线程

线程的死锁

多个线程都占用对方的锁资源,但不肯互让,导致了死锁

package DeadLock;public class DeadLock{    public static void main(String[] args) { //模拟死锁现象 DeadLockDemo A = new DeadLockDemo(true); A.setName("A 线程"); DeadLockDemo B = new DeadLockDemo(false); B.setName("B 线程"); A.start(); B.start();    }}class DeadLockDemo extends Thread {    static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用 static    static Object o2 = new Object();    boolean flag;    public DeadLockDemo(boolean flag) {//构造器 this.flag = flag;    }    @Override    public void run() {//下面业务逻辑的分析//1. 如果 flag 为 T, 线程 A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁//2. 如果线程 A 得不到 o2 对象锁,就会 Blocked//3. 如果 flag 为 F, 线程 B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁//4. 如果线程 B 得不到 o1 对象锁,就会 Blocked if (flag) {     synchronized (o1) {//对象互斥锁, 下面就是同步代码  System.out.println(Thread.currentThread().getName() + " 进入 1");  synchronized (o2) { // 这里获得 li 对象的监视权      System.out.println(Thread.currentThread().getName() + " 进入 2");  }     } } else {   synchronized (o2) {  System.out.println(Thread.currentThread().getName() + " 进入 3");  synchronized (o1) { // 这里获得 li 对象的监视权      System.out.println(Thread.currentThread().getName() + " 进入 4");  }     } }    }}

编程题

在main方法中启动两个线程

第1个线程循环随机打印100以内的整数

直到第2个线程从键盘读取了“Q”命令

package Homework01;import java.util.Locale;import java.util.Scanner;public class Homework01 {    public static void main(String[] args) { A a = new A(); B b = new B(a); a.start(); b.start();    }}class A extends Thread{    private boolean loop =true;    public void run(){ while(loop){     System.out.println((int)(Math.random()*100 +1));     try {  Thread.sleep(1000);     } catch (InterruptedException e) {  e.printStackTrace();     } }    }    public void setLoop(boolean loop) { this.loop = loop;    }}class B extends Thread{    private A a;    private Scanner scanner=new Scanner(System.in);    public B(A a){ this.a=a;    }    public void run() { while (true) {     //接受用户输入     System.out.println("请输入(Q)指令结束退出:");     char key=scanner.next().toUpperCase().charAt(0);     if(key=='Q'){  //以通知方式结束a线程  a.setLoop(false);  System.out.println("b线程退出");  break;     } }    }}

万字手撕Java多线程

编程题

有2个用户分别从同一个卡上取钱(总额:1000)

每次都取1000,当余款不足时,就不能取款了

不能出现超取现象 线程同步问题

package Homework02;public class Mon01 {    public static void main(String[] args) { A a = new A(); Thread thread1 = new Thread(a); thread1.setName("用户1"); Thread thread2 = new Thread(a); thread2.setName("用户2"); thread1.start();//第一个用户 thread2.start();//第二个用户    }}//涉及多个线程共享资源,使用接口的实现class A implements Runnable{    private int num=10000;    public boolean loop=true;    public void run(){ while(loop){    synchronized (this){  if(num<0){      System.out.println("余款不足,不能取款");      loop=false;      return;  }  try {      Thread.sleep(1000);  } catch (InterruptedException e) {      e.printStackTrace();  } num -=1000;  System.out.println(Thread.currentThread().getName()+" "+"剩余余款:"+num);     } }    }}

在这里插入图片描述