15-线程基础

发布时间 2023-12-07 23:04:43作者: TomLove

线程(基础)

image-20231206082951949

程序 program 简单来说就是我们写的代码

进程 正在运行中的程序

image-20231206083549914

进程产生线程

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

多线程:同一时刻,可以执行多个线程

并发:同一时刻,多个任务交替执行 ---------单核 cpu 实现的多任务就是并发

并行:同一时刻,多个任务同时执行 -----------多核 cpu 可以实现并行, 也可能并发和并行同时存在

package com.wxledu.threaduse;

/**
 * @author 王鑫磊
 * @version 1.0
 * 演示通过继承 Thread 类创建进程
 */
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个 cat  对象, 可以当做线程使用
        Cat cat = new Cat();

        // 启动线程
        cat.start(); // 最终会执行 cat 的 run 方法

        // 当 main 线程 启动一个子线程的时候,主线程不会阻塞,会继续执行
        // 这时主线程和子线程交替执行
        System.out.println("主线程不会阻塞,会继续执行" + Thread.currentThread().getName());  // 主线程的名字就叫 main
        for (int i = 0; i < 10; i++) {
            System.out.println(i);

            Thread.sleep(1000); // 让主线程休眠
        }
    }
}

// 1. 当一个类继承了 Thread 类 该类就可以当做线程使用
// 2. 我们会重写 run 方法,写上自己的的业务代码
// 3. run Thread 类实现了 Runnable 接口的 run 方法
/*
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
 */
class Cat extends Thread{
    @Override
    public void run() {
        // 写上自己的额业务逻辑

        int times = 0;
        while(true){
            // 每隔 1 s ,在控制台输出 “喵喵, 我是小猫咪”
            System.out.println("喵喵, 我是小喵咪" + (++times) + " " + Thread.currentThread().getName());

            // 让该线程休眠 1 s
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            if (times == 80){ // 如果输出了 8 次就退出
                break;
            }
        }

    }
}

实现 runnable 接口

说明:

java 是单继承,在某些情况下一个类可能已经继承了某个父类,在这个时候,用 Thread 类方法来实现创建线程显然就不可能了

java 设计者们提供了另外一种方式创建线程,就是通过 Runnable 接口的方式来创建线程

image-20231206150914129

setName 设置线程名称
getName 返回该线程的名称
start 使该线程开始执行
run 调用线程对象的 run 方法
setPriority 更改线程的优先级
getPriority 获得线程的优先级
sleep 在指定的毫秒数内让当前正在执行的线程休眠
interrupt 中断线程

用户线程和守护线程

用户线程:也叫工作线程,当线程的任务执行完或通知方式结束

守护线程: 一般为工作线程服务,当所有的用户线程结束,守护线程也就结束

常见的额守护线程 : 垃圾回收机制

image-20231207154106093

上图是现成的生命周期

Synchronized 非公平锁

线程同步机制

  1. 在多线程编程中,一些敏感的个人数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多只有一个线程访问,以保证数据的完整性
  2. 也可以这样理解:线程同步,即当有一个线程在对内存进行操作的时候,其他线程都不可以对这个内存的地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
  • 同步具体方法 - Synchronized

1) 同步代码块

​ synchronized (对象){ // 得到独像的锁, 才能操作同步代码

​ // 需要被同步的代码

}

2) synchronized 还可以放在方法声明中, 表示整个方法- 为实现同步

​ public synchronized void m (String name){

​ // 需要被同步的代码

​ }

3) 如何理解:就好像某小伙伴上厕所前先把,门关上(上锁) 完事后再出来(解锁), 那么其他小伙伴就可以在使用厕所了

4) 使用 Synchronized 解决售票问题 , 把对票数的操作封装到 一个方法中 用 Synchronized 进行声明

互斥锁

  1. Java 语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
  2. 每个对象都对应于一个可称为 “ 互斥锁的标记” 这个标记保证在任意时刻,只能有一个线程访问对象
  3. 关键字 synchronized 来与对象的互斥锁联系, 当某个独像与 synchronized 修饰时,表名该对象在唉任一时刻只能有一个线程访问
  4. 同步的局限性: 导致程序的执行效率降低
  5. 同步方法(非静态的) 的锁可以是 this 也可以是其他线程(要求是同一个对象)
  6. 同步方法(静态的) 的锁为当前类本身
  • 注意事项和细节
  1. 同步方法如果没有使用 static 修饰: 默认锁对象就是 this
  2. 如果方法使用 static 修饰 ,默认锁的对象:当前类.class
  • 实现落地步骤
  1. 需要先分析上锁代码
  2. 选择同步代码或同步方法
  3. 要求多个线程的锁对象为同一个即可

线程死锁

多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程时一定要避免死锁的发生

释放锁

  • 什么情况下不会释放锁
  1. 线程执行同步代码块或者同步方法的时候,程序调用了 Thread.sleep() Thread.yield() 暂停当前线程的执行不会释放锁
  2. 线程执行同步代码快的时候,其他线程调用了 该线程的 suppend() 方法,将该线程挂起,该线程不会被释放

提示:应尽量避免使用 supped() 和 resume() 来控制线程,方法不推荐使用

package com.wxledu.homework;

/**
 * @author 王XX
 * @version 1.0
 *
 * 要求
 * 1. 有两个用户分别从 同一张卡里取钱(总额 : 10000)
 * 2. 每次都取 1000 ,当余额不足时, 就不用取了
 * 3. 不能出现超取现象 => 线程同步问题
 */
public class Homework {
    public static void main(String[] args) {
        T t = new T();
        Thread thread1 = new Thread(t);
        thread1.start();
        Thread thread2 = new Thread(t);
        thread2.start();
    }
}

/*
class Qu extends Thread{

    public static int money = 10000;
    private static boolean loop = true;

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

    // 定义一个取钱方法, 因为有多个用户,取同一张卡上面的钱
    public static synchronized void drawMoney(){
        if (money <= 0){
            System.out.println("当前余额不足,请改天来取");
            loop = false;
            return;
        }

        System.out.println(Thread.currentThread().getName() + "取了1000元,剩" + (money -= 1000));

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void run() {
        while(loop){
            drawMoney();
        }
    }
}

*/

// 1. 多个线程同时共享访问同一变量, 实现 Runnable

class T implements Runnable{
    private int money = 10000;

    @Override
    public void run() {
        while (true){

            // 多个线程互斥的对共享变量进行访问
            synchronized (this){
                if (money <= 0){
                    System.out.println("余额不足");
                    break;
                }

                money -= 1000;
                System.out.println(Thread.currentThread().getName() + "取了 1000 元, 剩" + money);
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

纸上得来终觉浅,绝知此事要躬行。