关于Java中多线程

发布时间 2023-06-07 18:25:27作者: 武帅祺

基本概念

什么是进程-->是操作系统资源分配和调度的最小(基本)单位(操作系统分配给当前进程一个内存区域供其使用)

什么是线程-->是程序运行的基本单位(等待操作系统分配时间片 让CPU执行该内存区域中的代码)

进程和线程的关系-->一个进程可以存在多个线程 线程是由进程创建的(寄生在进程中) 线程是进程中一个负责程序执行的控制单元

什么是单线程-->只处理一个任务

什么是多线程-->宏观上同时处理多个任务即并发执行也就是说允许单个进程运行多个线程来完成不同的任务(站在CPU角度微观上其实1个CPU每时刻只能执行一个任务 但是宏观上看这CPU一会执行这个一会执行那个)

谈谈线程的几种状态-->开始(new)-->就绪(runnable)-->运行(runnint)-->阻塞(blocked)-->销毁(dead)

多线程有什么好处-->异步处理任务 将IO任务异步(阻塞IO线程让其他线程进入开始状态)操作提高CPU利用率

多线程有什么缺点-->线程也是程序当线程越来越多会导致占用内存大 多线程需要协同管理需要跟棕栈 线程之间对共享资源的访问必须解决竟用的问题 线程太多会导致程序过于复杂难以排除bug

一个java程序至少有几个线程-->当我们启动java程序的时候会立即执行main()方法这个线程被称为程序的主线程和垃圾回收线程

聊聊Java中的多线程

java.util.concurrent简称JUC

实现多线程的方式-->调用Thread.start()方法

  • 重写Runnable接口中的run()方法 实例化Runnable接口实现类并作为参数传递给Thread
  • 继承Thread类并重写run()方法 实例化继承类
  • 重写Callable接口中的call()方法产生的回调结果参数传递给FutureTask()构造函数然后将实例对象作为参数传递给Thread构造函数

怎么开启一个线程-->不管通过哪种方式实例化Thread的时候 线程就到开始状态了

什么是初始态-->仅仅在语言层面创建一个线程实例

怎么到就绪态-->调用Thread.start()方法 线程进入就绪态等待操作系统分配时间片

怎么到运行态-->操作系统分配时间片给当前线程

怎么从运行态回到就绪态-->时间片用完还没有完成任务那么就又回到就绪态

怎么从运行态到阻塞态-->进行耗时的IO操作时 操作系统拿到其时间片将其分配给其他线程 或者Thread.sleep()方式强制进入睡眠状态

怎么从阻塞态回到就绪态-->在阻塞态的线程完成其操作回到就绪队列中等待操作系统分配时间片

怎么从运行态到销毁-->线程中的代码执行完毕然后jvm收回线程占用的资源!

什么是主线程-->Main线程 垃圾回收线程

怎么让线程停止-->Thread类提供stop()方法用于停止一个已启动的线程但本质是不安全的

如何安全停止一个正在运行的线程-->线程对象在执行完run()方法所有代码执行完成后线程会自然消亡因此需要在运行过程提前停止线程 可以通过更改变量值的方法run()方法提前结束

创建线程的三种方式

public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("操作系统分配时间片给我啦!");
    }
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("操作系统分配时间片给我啦!");
    }
}
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "操作系统分配时间片给我!!";//产生一个返回结果
    }
}

测试类

public class TestCreate {
    @Test
    public void test1() throws Exception{
        MyRunnable myRunnable = new MyRunnable();
        //创建线程
        Thread t1=new Thread(myRunnable,"实现runnable接口的方式");
        MyThread t2=new MyThread("继承thread的方式");
        MyCallable myCallable = new MyCallable();
        FutureTask<String> futureTask=new FutureTask<>(myCallable);
        Thread t3 = new Thread(futureTask);//futureTask负责调用call()函数
        t3.setName("我是实现Callable的方式");
        t1.start();
        t2.start();
        t3.start();
        String s = futureTask.get();//通过get方式拿到返回的结果
        System.out.println("这里是返回的结果-->"+s);
    }
}

线程中断

打断正在执行的线程

  • Thread实例.stop()方式

stop方式会杀死一个进程 如果此时该线程锁住了一些共享资源 那么它被杀死以后就再也没有机会释放锁 其他线程将永远无法获取锁

  • System.exit(int)方式停止线程

目的是停止一个线程 但这样会让整个程序都停止!

打断被堵塞的线程

  • 两阶段终止模式
public class MonitorThread{
    //维护一个Thread实例 来监控是否被打断
    private Thread monitor;
    public void start(){
        monitor=new Thread(()->{
            while (true){
                //isInterrupted不会清除打断标记
                if (monitor.isInterrupted()){
                    System.out.println("料理后事...");
                    break;
                }
                try {
                    //每次5秒1记录
                    Thread.sleep(5);
                    System.out.println("监控记录");
                }catch (InterruptedException e){
                    e.printStackTrace();
                    System.out.println(monitor.isInterrupted());//false
                    monitor.interrupt();//重置打断变量
                }
            }
        });
        monitor.start();
    }
    //停止监控线程
    public void stop(){
        monitor.interrupt();
    }

    public static void main(String[] args) throws InterruptedException {
        MonitorThread monitorThread = new MonitorThread();
        monitorThread.start();
        Thread.sleep(100);
        monitorThread.stop();
    }
}

守护线程

主线程执行完毕-->守护线程无论是否执行完毕就dead

守护线程-->垃圾回收器 tomcat中的分发器

public static void main(String[] args) throws InterruptedException {
    Thread t1=new Thread(()->{
        while (true)
            System.out.println("我是daemon线程...");
    });
    //设置为守护线程
    t1.setDaemon(true);
    t1.start();//线程不可以多次执行start()方法
    Thread.sleep(10);
    System.out.println("主线程准备结束--守护线程也被挂掉了");
}

烧水喝茶

graph LR; a(洗水壶1分钟)-->b(煮水15分钟)-->c(泡茶叶) d(洗茶壶 拿茶叶 洗茶叶 4分钟)-->c
public static void main(String[] args) {
    //两个线程模拟烧水喝茶的过程
    Thread t1=new Thread(()->{
        try {
            System.out.println("水壶清洗...");
            Thread.sleep(1000);
            System.out.println("烧水...");
            Thread.sleep(15000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    });
    Thread t2=new Thread(()->{
        try {
            System.out.println("洗茶壶");
            Thread.sleep(1000);
            System.out.println("拿茶叶");
            Thread.sleep(2000);
            System.out.println("洗茶叶");
            Thread.sleep(1000);
            t1.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("泡茶ok");
    });
    t1.start();//小王
    t2.start();//小张
}

共享带来的问题

一个程序运行多个线程本身是没有问题的 但问题出在多个线程访问共同的资源

//全局变量2个线程访问
public class Test3 {
    static int global=0;
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
           for (int i=0;i<2500;i++){
               global++;
           }
        });
        Thread t2=new Thread(()->{
            for (int i=0;i<2500;i++){
                global--;
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //为什么执行2500次g++和2500次g--结果并不一定是0 字节码角度分析
        // 自增自减运算符并非一条指令(非原子操作)
        System.out.println(global);
    }
}

在一个线程中

临界区