进程线程

  1. 进程是指运行中的程序的实例, 是程序的一次执行过程, 是系统进行资源分配和调度的一个独立单位
  2. 进程是动态的, 是程序的一次执行过程, 包含它自身的产生, 消亡等过程
  3. 线程由进程创建的, 是进程的一个实体

并发并行

  1. 单线程: 同一个时刻, 只允许执行一个线程
  2. 多线程: 同一时刻可以执行多个线程
  3. 并发: 并发是同一个时刻, 多个任务交替执行
  4. 并行: 并行是同一个时刻, 多个任务同时执行
  5. 并发和并行可能同时存在.
Runtime runtime = Runtime.getRuntime();
// 获取当前电脑的 CPU 核心数
System.out.println(runtime.availableProcessors());
// 32

继承 Thread 创建线程

public class Thread01 {
    public static void main(String[] args) {
        // 创建一个 cat 对象, 可以当作线程使用
        Cat cat = new Cat();
        cat.start();
        // 这样执行一次就结束了, 因为线程里就执行一次
    }
}

// 1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
// 2. 我们一般会重写 run 方法, 写上自己的业务代码
// 3. run Thread 类, 实现了 Runnable 接口的 run 方法
class Cat extends Thread {

    @Override
    public void run() {
        System.out.println("喵喵");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

稍微修改

class Cat extends Thread {
    private int time =0;

    @Override
    public void run() {
        while(true) {
            time++;
            System.out.println("喵喵" + time);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (time >= 5){break;}
        }

    }
}

多线程机制

可以通过 Thread.currentThread().getName() 获取当前线程的名字

  1. 程序启动以后会先开启一个主线程 main, 主线程又可以开启多个子线程
  2. 比如上面的例子, 主线程是 main, 子线程是 Thread-0
  3. 可以是用 JConsole 查看线程的状态
  4. 主线程执行完毕, 子线程还在执行, 程序不会结束

为什么是 start

为什么是 start 方法, 而不是 run 方法.

  1. start 方法是启动线程, 如果是run 方法, 就是普通的方法调用, 没有启动新的线程, 相当于是普通的方法调用
  2. start 方法会启动一个 start0 方法, 是一个 native 方法, 是由 JVM 调用的
  3. 真正实现多线程效果的是 start0 方法.
  4. start 方法调用 start0 方法后, 该线程不会立刻执行, 只是将线程变成了可运行状态, 具体什么时候执行, 取决于 CPU, 由 CPU 统一调度

Runnable 创建线程

  1. Java 是单继承的, 如果一个类已经继承了其他类, 就不能再继承 Thread 类
  2. Java 设计这提供了另一个方法创建线程, 通过实现 Runnable 接口来创建线程
package threaduse;

/**
 * @Author: 大西瓜
 * @Date: 2024/3/27 11:34
 * @Description: 写介绍
 * @Version: v1.0
 */

public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        // 这时候不能调用 start, 但是也不能用 run 啊
        // 因此可以用下面这种方法
        Thread thread = new Thread(dog);
        thread.start();
        // 使用了一个设计模式: 代理模式

    }
}

class Dog implements Runnable{

    int count = 0;

    @Override
    public void run() {
        while (true){
            count ++;
            System.out.println("aoaoaoao"+count+Thread.currentThread().getName());
            if (count>=5){
                break;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

多个子线程案例

public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();

        Thread thread = new Thread(t1);
        Thread thread1 = new Thread(t2);
        thread.start();
        thread1.start();
    }

}

class T1 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while(true){
            count++;
            System.out.println("T1 " + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (count >= 10) {break;}
        }
    }
}

class T2 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while(true){
            count++;
            System.out.println("T2 " + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (count >= 10) {break;}
        }
    }
}
  • Runnable 接口的好处是可以共享资源, 可以让一个对象T1同时放到两个线程里, 共享T1对象的资源
  • Runnable 接口避免了单继承的局限性
  • 建议使用 Runnable 接口创建线程

多线程售票问题

public class SellTicket {
    public static void main(String[] args) {
        SellTicket01 sellTicket1 = new SellTicket01();
        SellTicket01 sellTicket2 = new SellTicket01();
        SellTicket01 sellTicket3 = new SellTicket01();
        SellTicket01 sellTicket4 = new SellTicket01();
        sellTicket1.start();
        sellTicket2.start();
        sellTicket3.start();
        sellTicket4.start();
    }
}

// 三个窗口售票100张
class SellTicket01 extends Thread{
    private static int ticketNum = 100;

    @Override
    public void run() {
        while (true){
            if (ticketNum<=0){
                System.out.println(Thread.currentThread().getName()+" 售票结束");
                break;
            }
            ticketNum--;
            try {
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("窗口: "+Thread.currentThread().getName()+" 售出一张票, 剩余票数: "+ticketNum);
        }
    }
}

正常来说会超卖, 不知道为什么我的没有超卖, 但是这种超卖是正常的, 常识使用 Runnable 接口创建线程.

class SellTicket02 implements Runnable{
    private int ticketNum = 100;

    @Override
    public void run() {
        while (true){
            if (ticketNum<=0){
                System.out.println(Thread.currentThread().getName()+" 售票结束");
                break;
            }
            ticketNum--;
            try {
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("窗口: "+Thread.currentThread().getName()+" 售出一张票, 剩余票数: "+ticketNum);
        }
    }
}

按理来说我这里也会超卖, 不知道为什么没超卖

通知线程退出

public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.start();
        Thread.sleep(5000);
        // 如果希望main 线程去终止 t 线程, 需要可以修改 loop 变量
        // 这种方式称为通知方式
        t.setLoop(false);
    }
}

class T extends Thread{

    private boolean loop = true;

    @Override
    public void run() {
        int count = 0;
        while(loop) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("运行中..."+(++count));
        }
    }

    public void setLoop(Boolean loop){
        this.loop = loop;
    }
}

线程中断

线程还有一些常用方法

  • setName:设置线程名称
  • getName:获取线程名称
  • start:启动线程
  • run:调用对象的 run 方法
  • setPriority:设置线程优先级
  • getPriority:获取线程优先级
  • sleep:线程休眠指定的毫秒数
  • interrupt:中断线程

Priority的优先级最小为1, 一般为 5, 最大为10.

public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        // 测试相关方法
        T t = new T();
        t.setName("大西瓜线程");
        t.setPriority(Thread.MIN_PRIORITY);
        t.start();
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName()+": hi~");
            Thread.sleep(700);
        }
        t.interrupt();

    }
}

class T extends Thread {

    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println("当前线程的名称: " + Thread.currentThread().getName());
            }

            try {
                System.out.println(Thread.currentThread().getName() + " 休眠中...");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println("发生 Interrupt");
            }
        }

    }
}

这个 interrupt 实际上是中断子线程的休眠.

线程插队

常用方法2:

  • yield:线程让步, 释放 CPU 执行权, 但是不会释放锁, 只是让出 CPU 时间片, 重新进入就绪状态
  • join:插队, 让其他线程等待当前线程执行完毕, 也可以设置等待时间

yield 可能会不成功, 比如资源很多, join 是让别人插队, 只执行它.

守护线程

  • 用户线程: 也叫工作线程, 当线程的任务执行完或通知方式结束
  • 守护线程: 一般是为工作线程服务的, 当所有的用户线程结束, 守护线程自动结束
  • 常见的守护线程: 垃圾回收线程

普通线程是主线程结束了, 但是子线程还在执行, 守护线程是主线程结束了, 守护线程也结束了.

public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        // 如果我们希望, 当 main 线程结束后, 子线程自动退出
        // 只需将子线程设置为守护线程即可
        myDaemonThread.setDaemon(true);
        // 先设置在执行
        myDaemonThread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main 线程存活...");
            Thread.sleep(1000);
        }
    }
}


class MyDaemonThread extends Thread {
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("子线程存活...");
        }
    }
}

只需要在 start 前简单设置一下即可.

线程7大状态

  • NEW
  • RUNNABLE
    • Ready
    • Running
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

RUNNABLE 有两种状态, 一种是 Ready, 一种是 Running, 是可运行状态, 但是是否真实运行, 取决于 CPU 调度. 因此可以细分成两种状态. yield 就是切换到 Ready 状态.

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() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            break;
        }
    }
}

大部分都是 TIME_WAITING, 因为 sleep 了 10 秒.

线程同步机制

线程同步机制:

  1. 在多线程编程里, 一些敏感数据不允许被多个线程同时访问, 此时就使用同步访问技术, 保证数据在任何同一时刻, 最多有一个线程访问, 以保证数据的完整性
  2. 线程同步, 即当有一个线程在对内存进行操作时, 其他线程都不可以对这个内存地址进行操作, 直到该线程完成操作, 其他线程才能对该内存地址进行操作.

同步具体方法-Synchronized:

  1. 同步代码块 synchronized(对象){// 需要被同步的代码;}
  2. synchronized 修饰方法 public synchronized void method(){}; 整个方法为同步方法.

互斥锁

synchroniezd 是一种非公平锁.

  1. Java 语言中, 引入了对象互斥锁的概念, 来保证共享数据操作的完整性
  2. 每个对象都对应于一个可称为"互斥锁"的标记, 这个标记用于保证在任一时刻, 只能有一个线程访问该对象
  3. 关键字 synchronized 来与对象的互斥锁联系. 当某个对象用 synchronized 修饰时, 表明该对象在任一时刻只能由一个线程访问
  4. 同步的局限性: 导致程序的执行效率要降低
  5. 同步方法(非静态的)的锁可以是this, 也可以是其他对象(要求是同一个对象)
  6. 同步方法(静态的)的锁, 是当前类本身
// 这时候锁为当前类本身
public synchronized void method() {
   
}

// 如果是静态方法
// 锁是加载 Class 对象上, 比如 xxx.class 
public synchronized static void method() {
   
}

// 因此如果是静态方法要在代码块里面, 需要锁住当前类
public static void method() {
    synchronized (xxx.class) { // 而不能写成 synchronized (this)
        
    }
}

多个线程的锁对象为同一个即可.

线程死锁

A 线程占用了 B 线程需要的一个资源, B 线程拿到了 A 线程需要的一个资源, 但是 A 线程需要 B 线程的资源, B 线程需要 A 线程的资源, 两个线程都在等待对方释放资源, 造成死锁.

释放锁

练习

1

  1. 在 main 方法中启动两个线程
  2. 第一个线程循环打印100个的整数
  3. 直到第二个线程从键盘读到了Q命令
public class Homework1 {
    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 flag = true;

    @Override
    public void run() {
        int count = 0;
        while (flag) {
            count++;
            System.out.println("count: "+ count);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (count >= 100){
                break;
            }
        }
    }

    public void setFlag(boolean flag){
        this.flag = flag;
    }
}

class B extends Thread{

    private A a;
    private Scanner scanner = new Scanner(System.in);

    public B(A a){
        this.a = a;
    }


    @Override
    public void run() {
        while(true){
            System.out.println("请输入");
            char key = scanner.next().toUpperCase().charAt(0);
            if (key == 'Q'){
                a.setFlag(false);
                break;
            }
        }
    }
}

2

  1. 两个用户分别从同一个卡上取钱, 余额为 1000 元
  2. 每次取钱 100 元, 余额不足就不能取款
  3. 不能超取
public class Homework2 {
    public static void main(String[] args) {
        User user = new User();
        User user1 = new User();
        user.start();
        user1.start();
    }
}

class User extends Thread{
    private static Integer balance = 1000;

    @Override
    public void run() {
        while (true){
            synchronized (User.class) {
                if (balance <100){
                    break;
                }
                balance -= 100;
                System.out.println(Thread.currentThread().getName() +" 取出 100元, 余额为: "+balance);
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
    }
}

或者一个User, 然后实现 Runnable 接口, 传入两个线程. 这样就不用静态变量了.