JavaEE 【知识改变命运】03 多线程(2)

发布于:2024-11-28 ⋅ 阅读:(29) ⋅ 点赞:(0)

复习

1.1 进程和线程的区别

  1. 进程是申请资源的最小单位
  2. 线程是cpu调度的最小单位
  3. 创建一个进程,里面会自动生成一个主线程,进程里面包含线程
  4. 进程与进程之间互不影响,线程与线程之间可能会造成影响,一个线程的崩溃会导致整个进程的结束。
    5 线程之间共享进程的资源

1.2 线程创建的方式

继承Thread类创建方式(线程对象),重写run()方法(线程执行的任务)
实现Runnable接口创建方式,重写run()方法(线程执行的任务)
其他三种:匿名类Thread,匿名Runnable,lambda表达式Runnable接口创建方式

1.3 两者创建的区别

因为java是单继承模式,所以当一个类继承了父类,就不能再继承Thread这个类了,Runnable接口式实现,解决了单继承的这种限制
接口式创建,有高内聚低耦合的特点,把线程类和要执行的代码块分开了,后面修改代码对程序的影响较小,

2 多线程

2.1多线程的优势-增加了运行的速度

场景1:进行两次自增10_0000_0000次的自增

    private static long count=10_0000_0000;
    public static void main(String[] args) {
        //串行方式
        serivalThread();
        //并行方式
        parallelThread();
    }

    private static void parallelThread() {
        long start = System.currentTimeMillis();
        Thread thread01 = new Thread(()-> {
            int a = 0;
            for(int i = 0; i <count; i++) {
                a += i;
            }
        });
        Thread thread02 = new Thread(()-> {
            int b = 0;
            for(int i = 0; i <count; i++) {
                b += i;
            }
        });
        thread01.start();
        thread02.start();
        try {
            thread02.join();
            thread01.join();//一定要加这个join等待,要不然算出来的时间只是创建一个线程的时间
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        System.out.println("并行执行时间:"+(end-start));
    }

    private static void serivalThread() {
        long start = System.currentTimeMillis();
        int a = 0;
        for(int i = 0; i <count; i++) {
            a += i;
        }
        int b = 0;
        for(int i = 0; i <count; i++) {
            b += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("串行执行时间:"+(end-start));
    } }

执行结果:
在这里插入图片描述
这里注意一下:
通过多线程的方式明显提升效率,并行的耗时是串行的一半多一点时间,多一点的时间是创建线程时候消耗的时间

场景2:我们把自加次数降为50万次 执行结果;
在这里插入图片描述
注意:并不是任何时候多线程的效率比单线程的高,当任务量很少的时候,单线程的效率可能会比多线程更高
因为创建线程本身就是也有一定的系统开销,这个开销没有进程的开销大,两个线程在cpu上面调度也需要一定的时间

2.2Thread类及常用的方法

2.2.1常用见的构造方法

在这里插入图片描述
创建线程对象
这里是引用
使用Runnable创建线程对象
在这里插入图片描述
创建线程对象,并命名
在这里插入图片描述
使用Runnable创建线程对象,并命名
默认的线程名Thread-N,N>=0;

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println("我的线程名:"+Thread.currentThread().getName());
            }
        },"Runnable线程");
        thread.start();
        
    } } ```执行结果: ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c68e1eee57af43ed948b4cc0e3d53176.png)

2.2.2获取当前类的信息

public class Mian {
    public static void main(String[] args) {


    Thread thread = new Thread(()->{
        //获取类名
        String className = Mian.class.getName();
        System.out.println(className);
        //获取线程对象
        Thread thread1 = Thread.currentThread();
        System.out.println(thread1);
        //获取线程名
        String name = thread1.getName();
        System.out.println(name);
        String mName= thread1.getStackTrace()[1].getMethodName();
        System.out.println(mName);
    },"我是一个线程");
    thread.start();
    }
}

2.2.3Thread 的⼏个常⻅属性

在这里插入图片描述

  1. id:jvm层面的,jvm默认为Thread对象生成一个编号,和PCB区分开(操作系统层面) 名字:java层面
  2. 状态:java层面定义的状态
  3. 优先级:
  4. 是否后台线程:java层面,线程分为前台线程和后台线程,通过这个表示位来区分当前前台线程还是后台线程,关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。
  5. 是否存活:PCB层的,表示系统中的PCB是否销毁,与Thread对应没啥关系 ,即简单的理解,为 run ⽅法是否运⾏结束了
  6. 是否被中断:通过设置一个标志位让线程执行时候判断是否要退出
  7. 名称:名称是各种调试⼯具⽤到
  8. 注意:Thread是java中的类-----创建Thread对象----->调用strat()方法----->JVM调用系统的API生成一个PCB----PCB与Thread对象一一对应
    Thread对象与PCB所处的环境不同,所以他们的生命周期也不同。
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 我还活着");
                            Thread.sleep(1 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 我即将死去");
        });
        System.out.println(thread.getName()
                + ": ID: " + thread.getId());
        System.out.println(thread.getName()
                + ": 名称: " + thread.getName());
        System.out.println(thread.getName()
                + ": 状态: " + thread.getState());
        System.out.println(thread.getName()
                        + ": 优先级: " + thread.getPriority());
        System.out.println(thread.getName()
                + ": 后台线程: " + thread.isDaemon());
        System.out.println(thread.getName()
                + ": 活着: " + thread.isAlive());
        System.out.println(thread.getName()
                + ": 被中断: " + thread.isInterrupted());
        thread.start();
        while (thread.isAlive()) {}
        System.out.println(thread.getName()
                + ": 状态: " + thread.getState());
    } }

在这里插入图片描述

1 演示后台线程
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                while (true){
                    System.out.println("hello myRunnable");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
       // thread.setDaemon(true);
        thread.start();
        System.out.println("thread 是否存活"+thread.isAlive());
        System.out.println("main thread 已经结束");

    } }

在这里插入图片描述

  1. 子线程没有设置为后台线程,main线程结束,子线程不会结束 ,进程不结束在这里插入图片描述
  2. 子线程设置为后台线程,main线程结束,子线程会跟着结束,进程结束
  3. 补充:后台线程类似守护线程,main线程类似用户线程
  4. 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
  5. 守护线程:一般是为了工作线程服务的,当所有的用户线程结束守护线程自动结束,
  6. 常见的守护线程:垃圾回收机制
  7. 前台线程可以阻止进程结束
  8. 后台线程不能阻止进程结束
2 线程是否存活
InterruptedException {
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                {

                    System.out.println("hello myRunnable");
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        System.out.println("thread 是否存活"+thread.isAlive());//main
        thread.start();//子线程
        System.out.println("thread 是否存活"+thread.isAlive());//main
        thread.join();
        // 保证PCB 已经结束
        System.out.println("thread 是否存活"+thread.isAlive());//main,虽然PCB结束但是子线程java层面的对象还在,因为生命周期不一样
        //System.out.println("main thread 已经结束");
    } }

在这里插入图片描述

3 名称
    public static void main(String[] args) {


    Thread thread = new Thread(()->{
        //获取类名
        String className = Mian.class.getName();
        //获取方法名对象
        Thread thread1 = Thread.currentThread();
        //获取线程名
        String name = thread1.getName();
        //获取线程的方法名
        String mName= thread1.getStackTrace()[1].getMethodName();
        System.out.println("当前类名"+className+"方法名"+mName+"()"+"线程名"+name);

    },"我是一个线程");
    thread.start();
    thread.setName("hha")//设置线程名称,使之与参数name相同
    } }

在这里插入图片描述
通过类+方法+线程的名称的方法 可以明确的记录某一个线程所产生的日志

4 线程中断
  1. 通过共享的标记来进⾏沟通
    private static class MyRunnable implements Runnable {
        public volatile boolean isQuit = false;
        @Override
        public void run() {
            while (!isQuit) {
                System.out.println(Thread.currentThread().getName()
                        + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + ": 啊!险些误了⼤事");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();
        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");
        target.isQuit = true;
    } } ```
  1. 调⽤ interrupt() ⽅法来通知 使⽤ Thread.interrupted() 或者
    Thread.currentThread().isInterrupted() 代替⾃定义标志位.
    private static class MyRunnable implements Runnable {
        @Override
        public void run() {
            // 两种⽅法均可以
            while (!Thread.interrupted()) {
                //while (!Thread.currentThread().isInterrupted()) {
                System.out.println(Thread.currentThread().getName()
                        + ": 别管我,我忙着转账呢!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName()
                            + ": 有内⻤,终⽌交易!");
                    // 注意此处的 break
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName()
                    + ": 啊!险些误了⼤事");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        MyRunnable target = new MyRunnable();
        Thread thread = new Thread(target, "李四");
        System.out.println(Thread.currentThread().getName()
                + ": 让李四开始转账。");
        thread.start();
        Thread.sleep(10 * 1000);
        System.out.println(Thread.currentThread().getName()
                + ": ⽼板来电话了,得赶紧通知李四对⽅是个骗⼦!");
        try {
            thread.interrupt();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    } } ```

调用thread.interrput();方法时候
1:如果线程在运行状态,直接中断线程,不会报异常,符合程序预期
2:如果线程处于等待状态,就会报一个中断异常,要在异常处理代码块中段逻辑实现
在这里插入图片描述
当线程sleep状态时,执行中断操作,中断的是休眠状态的线程,就会抛出这个异常
在这里插入图片描述
3:PCB依然存在,任务继续执行
4. thread 收到通知的⽅式有两种:

  1. 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志 ◦ 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法.可以选择忽 略这个异常, 也可以跳出循环结束线程.
  2. 否则,只是内部的⼀个中断标志被设置,thread 可以通过 ◦ Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志这种⽅式通知收到的更及时,即使线程正在 sleep 也可以⻢上收到。
5 等待⼀个线程 - join()

在这里插入图片描述

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Runnable target = () -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName()
                            + ": 我还在⼯作!");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 我结束了!");
        };

         Thread thread1 = new Thread(target, "李四");
         Thread thread2 = new Thread(target, "王五");
         System.out.println("先让李四开始⼯作");
         thread1.start();
         thread1.join();
         System.out.println("李四⼯作结束了,让王五开始⼯作");
         thread2.start();
        thread2.join();
         System.out.println("王五⼯作结束了");
         }
 }
6 获取当前线程引⽤

在这里插入图片描述
Thread.currentThread()在那个线程中调用获取的就是那个线程的对象。

7 休眠当前线程

在这里插入图片描述
因为线程的调度是不可控的,所以,这个⽅法只能保证实际休眠时间是⼤于等于参数设置的休眠时间的。

2.3.4 线程的状态

线程的状态继承在一个枚举中
public class Main {
    public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
        }
    }
}

总共有六种状态:
在这里插入图片描述
NEW:表示创建好了一个java线程对象,安排好了任务,但是没有启动,没有调用start()方法之前是不会创建PCB的,和PCB没有关系
RUNNABLE::运行+就绪状态,在执行任务时候最常见的状态之一,在系统中有对应PCB
BLOCKED:等待锁状态,阻塞的一种
WATING:没有等待时间,一直死等,直到被唤醒 TIMED_WAIRING:指定了等待时间的阻塞状态,过时不候
TERMINATED:结束,完成状态,PCB已经销毁,但是java线程对象还在

在这里插入图片描述
在这里插入图片描述
有时侯会说有七种状态,是因为又把RUNNABLE又细分为两个状态:Ready(就绪)Running
其中的yield:线程的礼让,让出cpu,让其他线程执行,但是礼让的时间不确定,所以也不一定礼让成功

观察 1: 关注 NEW 、 RUNNABLE 、 TERMINATED 状态的转换

public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(() -> {
                for (int i = 0; i < 1000_0000; i++) {

                }
            }, "李四");
            System.out.println(t.getName() + ": " + t.getState());;
            t.start();
            while (t.isAlive()) {
                System.out.println(t.getName() + ": " + t.getState());;
            }
            System.out.println(t.getName() + ": " + t.getState());;
}  ``

观察 2: 关注 WAITING 、 BLOCKED TIMED_WAITING 状态的转换

public static void main(String[] args) {
        final Object object = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    while (true) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "t1");
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("hehe");
                }
            }
        }, "t2");
        t2.start();
    } ```
    
 使⽤ jconsole 可以看到 t1 的状态是 TIMED_WAITING , t2 的状态是 BLOCKED

修改上⾯的代码, 把 t1 中的 sleep 换成 wait

public static void main(String[] args) {
        final Object object = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    while (true) {
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "t1");
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("hehe");
                }
            }
        }, "t2");
        t2.start();
    } ``
    使⽤ jconsole 可以看到 t1 的状态是 WAITING

结论:
BLOCKED 表⽰等待获取锁, WAITING 和 TIMED_WAITING 表⽰等待其他线程发来通知. •
TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在⽆限等待唤醒
wait()是Object类里面的,Join()和sleep()是Thread类里面的。