【Java】多线程(更新ing)

多线程

基础概念


  • 程序:为完成特定任务,用某种语言编写的一组指令的集合。(代码)
  • 进程:运行中的程序。(Run代码)
    ① 启动进程需要CPU和操作系统分配的内存空间
    ② 进程是一个动态的过程,有自身的产生、存在和消亡的过程
  • 线程:线程由进程创建,是进程的一个实体。(如百度某盘可以同时下载多个文件,下载一个文件就是一个线程)
    1. 单线程:同一时刻,只允许执行一个线程。
    2. 多线程:同一时刻,可以执行多个线程。
    3. 并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉。即单核CPU实现的多任务并发
    4. 并行:同一时刻,多个任务同时执行。即多核CPU可以实现并行。
      在这里插入图片描述

一个小程序:计算自己电脑CPU核数

public class CpuNums {
    public static void main(String[] args) {
        Runtime runtime= Runtime.getRuntime();
        int cpuNums=runtime.availableProcessors();
        System.out.println("我的CPU是"+cpuNums+"核");
    }
}

在这里插入图片描述

线程的基本使用


  • 创建线程有两种方式:
    1. 继承Thread类,重写run()方法
    2. 实现Runnable接口,重写run()方法
      Thread类与Runnable接口的关系图:
      在这里插入图片描述

方式一:继承Thread类,重写run()方法例子

public class Thread01 {
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.start();//启动线程-> 最终会执行cat的run()方法
        int times=0;
        while (true){
            System.out.println("main working..."+Thread.currentThread().getName());//获得主线程的状态
            try {
                Thread.sleep(1000);//休息1000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            times++;
            if(times==20)
                break;
        }

    }
}
class Computer extends Thread{
    @Override
    public void run() {
        int times=0;
        while(true){
            System.out.println("Computer working..."+Thread.currentThread().getName());//获得当前线程的状态
            try {
                Thread.sleep(1000);//休息1000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            times++;
            //执行60次后退出
            if(times==60){
                break;
            }
        }
    }
}

在这里插入图片描述
如图当 main 线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
在这里插入图片描述

  • Q:若将start()换成run()有何不同
    在这里插入图片描述
    如图所示,换成run()后,run()相当于一个普通的方法,先执行完run()方法后再执行main()中的内容,而进程只有一个main
    在这里插入图片描述

方式二:实现Runnable接口,重写run()方法

public class Thread02 {
    public static void main(String[] args) {
        Computer computer = new Computer();
        //computer.start();//接口Runnable中没有start()方法,因此这个创建线程的方法失效

        //创建Thread对象将computer放入,用thread调用start()方法
        Thread thread = new Thread(computer);
        thread.start();
    }
}
class Computer implements Runnable{
    int times=0;
    @Override
    public void run() {
        while(true){
            System.out.println("Computer"+times+Thread.currentThread().getName()+ "is working...");
            times++;
            try {
                Thread.sleep(1000);//休息1000毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(times==5)//执行5次后停止
                break;
        }
    }
}

在这里插入图片描述

  • Q:computer.start()失效的底层原因?
    没有这个函数
    在这里插入图片描述

代理模式:模拟实现Runnable接口

public class SimulationThead {
    public static void main(String[] args) {
        Computer02 computer02 = new Computer02();//实现了 Runnable
        ProxyThread threadProxy = new ProxyThread(computer02);
        threadProxy.start();
    }
}
class Computer02 implements Runnable{
    @Override
    public void run() {
        System.out.println("Computer02 is working...");
    }
}
class ProxyThread implements Runnable{

    private Runnable target=null;
    @Override
    public void run() {
        if(target!=null)
            target.run();//动态绑定
    }

    public ProxyThread(Runnable target) {
        this.target=target;
    }
    public void start(){
        start01();
    }
    public void start01(){
        run();
    }
}

模拟一个售票机

一个售票机有三个窗口同时售卖票

public class SellTicket {
    public static void main(String[] args) {
//        //Thread方式
//        SellTicket01 sellTicket0101 = new SellTicket01();
//        SellTicket01 sellTicket0102 = new SellTicket01();
//        SellTicket01 sellTicket0103 = new SellTicket01();
//        sellTicket0101.start();
//        sellTicket0102.start();
//        sellTicket0103.start();
        //Runnable方式
        SellTicket02 sellTicket02 = new SellTicket02();
        new Thread(sellTicket02).start();
        new Thread(sellTicket02).start();
        new Thread(sellTicket02).start();

    }
}
class SellTicket01 extends Thread{
    private static int ticket=1000;
    @Override
    public void run() {

        while(true){
            if(ticket<=0){
                System.out.println("售完");
                break;
            }
            System.out.println(Thread.currentThread().getName()+"运行ing... 此时还有"+ticket+"张票,花费一张后还有"+--ticket+"张");
            try {
                Thread.sleep(1);//休息1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}
class SellTicket02 implements Runnable{

    private static int ticket=1000;
    @Override
    public void run() {

        while(true){
            if(ticket<=0){
                System.out.println("售完");
                break;
            }
            System.out.println(Thread.currentThread().getName()+"运行ing... 此时还有"+ticket+"张票,花费一张后还有"+--ticket+"张");
            try {
                Thread.sleep(1);//休息1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

运行结果:
方法一的结果:
在这里插入图片描述
方法二的结果:
在这里插入图片描述
存在的问题:不管是Thread还是Runnable创建3个进程,这3个进程同时进入run()函数,会出现如上图的问题。

线程方法


线程终止

  • 线程终止有两种方式:
    1. 自动退出:当线程完成执行完毕后,线程自动退出
    2. 通知退出:使用变量来控制run()方法停止线程,即通知方式
      //方法二:使用变量loop来控制run()方法停止线程
      public class ThreadExit {
          public static void main(String[] args) throws InterruptedException {
              Thread01 thread01 = new Thread01();//创建thread01线程
              thread01.start();//thread01启动
              
              //主线程休息2s后结束thread01
              Thread.sleep(2000);
              thread01.setLoop(false);//通过设置loop变量来停止thread01的运行
              System.out.println(Thread.currentThread().getName()+"运行ing... 结束thread01线程");
          }
      }
      class Thread01 extends Thread{
          private int counts=0;
          private boolean loop=true;
          @Override
          public void run() {
              while (loop){
                  System.out.println(Thread.currentThread().getName()+"运行ing... 运行了"+(++counts));
                  try {
                      Thread.sleep(100);//休息0.1s
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      
          public void setLoop(boolean loop) {
              this.loop = loop;
          }
      }
      
      在这里插入图片描述

线程中断

方法名功能说明
interrupt()中断线程中断的一般是休眠中的线程

线程插队

方法名功能说明
yield()线程的礼让礼让时间不确定,不一定礼让成果
join()线程插队若插队成功,先将插队的内容执行完毕
/* eg.
    1.主线程每隔1s输出hi,输出5次
    2.当输出2次后,启动子线程,子线程每隔1s输出hello,输出5次后退出子线程
    3.主线程继续输出hi直到结束
 */
public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new T());
        System.out.println("主线程启动");
        for(int i=1;i<=5;i++){
            System.out.println("hi");
            Thread.sleep(1000);
            if(i==2){
                t.start();//启动子进程
                t.join();//子进程插队
            }
        }
        System.out.println("主线程结束");
    }
}
class T implements Runnable{
    private int count=0;
    @Override
    public void run() {
        System.out.println("子线程启动");
        while(true){
            System.out.println("hello");
            count++;
            try {
                Thread.sleep(1000);//休息1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(count==5){
                System.out.println("子线程结束");
                break;
            }
        }
    }
}

运行结果
在这里插入图片描述

守护线程

  1. 用户线程(工作线程):线程的任务执行完毕/通知方式结束
  2. 守护线程:当所有用户线程结束,守护线程自动结束,如:垃圾回收机制
工作线程设置为守护线程
/*
    主线程启动子线程,主线程输出完5次hello后,子线程随之停止工作
 */
public class ThreadMethod {
    public static void main(String[] args) throws InterruptedException {
        MyDaemon t = new MyDaemon();
        t.setDaemon(true);//注意要先将子线程t设置为守护线程
        t.start();//启动子线程t
        for (int i=0;i<5;i++){
            System.out.println("hello");
            Thread.sleep(1000);
        }
    }
}
class MyDaemon extends Thread{
    @Override
    public void run() {
        while(true){
            System.out.println("hi");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果: 若不将t设置为守护线程则子线程不会停止工作
在这里插入图片描述

线程的生命周期


在这里插入图片描述
【说明】:在JDK的文档中 Thread.State只有6中状态,但Runnable中包含两个状态Ready和Running,所以可以看成有7个状态
在这里插入图片描述

//eg.测试状态NEW,RUNNABLE,WAITING,TERMINATED的存在
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(500);
        }
        System.out.println(t.getName() + " 状态 " + t.getState());
    }
}
class T extends Thread{
    @Override
    public void run() {
        for (int i=0;i<5;i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:
在这里插入图片描述

Synchronized(线程同步)


  • 线程同步机制:当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对这个内存地址进行操作。
  • 同步原理:方法需要线程有锁才能使用,第一个线程使用完这个方法后将锁给下一个线程,在前一个线程未使用完当前线程无法进入该方法。
  • 两种同步方法:
    1. 同步代码块
          synchronized (对象){
          	//得到对象的锁才能操作同步代码
              //需要被同步的代码
          }
      
    2. 同步方法:将关键字 synchronized 放在方法声明中
          public synchronized void func(){
          //需要被同步的代码
      }
      

解决售票机存在的问题

方法一:同步方法

public class SellTicket {
    public static void main(String[] args) {
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
    }
}

class SellTicket03 implements Runnable{
    private static int ticket=1000;
    private boolean loop=true;


    @Override
    public synchronized  void run() {
        while(loop){
            if(ticket<=0){
                System.out.println("售完");
                loop=false;
                return;
            }
            System.out.println(Thread.currentThread().getName()+"运行ing... 此时还有"+ticket+"张票,花费一张后还有"+--ticket+"张");
            try {
                Thread.sleep(1);//休息1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

方法二:同步代码块

public class SellTicket {
    public static void main(String[] args) {
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
        new Thread(sellTicket03).start();
    }
}

class SellTicket03 implements Runnable{
    private static int ticket=1000;
    private boolean loop=true;


    @Override
    public void run() {
        synchronized (this){
            while(loop){
                if(ticket<=0){
                    System.out.println("售完");
                    loop=false;
                    return;
                }
                System.out.println(Thread.currentThread().getName()+"运行ing... 此时还有"+ticket+"张票,花费一张后还有"+--ticket+"张");
                try {
                    Thread.sleep(1);//休息1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

运行结果:
在这里插入图片描述

  • Q:为什么同步代码块的对象是this
    (互斥锁解释)

  • Q:为什么不使用继承Thread的类
    (互斥锁解释)+ 观察Thread和Runnable创建对象的不同,可以发现Thread创建了三个不同的对象,所以每个对象对应着自己的this,此时同步失效。

        SellTicket03 sellTicket0301 = new SellTicket03();
        SellTicket03 sellTicket0302 = new SellTicket03();
        SellTicket03 sellTicket0303 = new SellTicket03();
        sellTicket0301.start();
        sellTicket0302.start();
        sellTicket0303.start();

在这里插入图片描述


互斥锁

  • 互斥锁(Mutual exclusion):防止两条线程同时对同一公共资源(比如全域變數)进行读写的机制
    当某个对象用 Synchronized 修饰,表明该对象在任一时刻只能由一个线程访问

【说明】:

  • 同步的局限性:导致程序执行效率变低
  • 同步方法的锁:
    1. 非静态:this
    2. 静态:当前类本身

死锁

  • 死锁:两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
    在这里插入图片描述
  • 死锁形成的条件:
    1. 互斥条件:线程(进程)对于所分配到的资源具有排它性,即一个资源只能被一个线程(进程)占用,直到被该线程(进程)释放
    2. 请求与保持条件:一个线程(进程)因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
    3. 不剥夺条件:线程(进程)已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
    4. 循环等待条件:当发生死锁时,所等待的线程(进程)必定会形成一个环路(类似于死循环),造成永久阻塞
//模拟死锁
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{
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2
    private boolean flag=true;
    public DeadLockDemo(boolean flag) {//构造器
        this.flag = flag;
    }
    @Override
    public void run() {
        if(flag){
            synchronized (resource1){
                System.out.println("1) 资源 1 被 线程"+Thread.currentThread().getName()+" 占用");
                synchronized (resource2){
                    System.out.println("2) 资源 2 被 线程"+Thread.currentThread().getName()+" 占用");
                }
            }
        }
        else {
            synchronized (resource2){
                System.out.println("3) 资源 2 被 线程"+Thread.currentThread().getName()+" 占用");
                synchronized (resource1){
                    System.out.println("4) 资源 1 被 线程"+Thread.currentThread().getName()+" 占用");
                }
            }

        }

    }
}

运行结果:
在这里插入图片描述

释放锁

  • 释放锁有四种情况:
    1.

【附录】线程常用方法

方法名功能说明
setName()设置线程名字-
getName()返回线程名字-
start()线程开始执行-
run()调用线程对象run方法-
setPriority()设置线程优先级最高:10,中间:5,最低:1;main函数默认5
getPriority()返回线程优先级-
sleep()休眠(毫秒)-
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XiYang-DING

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值