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

文章目录

  • 复习
    • [1.1 进程和线程的区别](#1.1 进程和线程的区别)
    • [1.2 线程创建的方式](#1.2 线程创建的方式)
    • [1.3 两者创建的区别](#1.3 两者创建的区别)
  • [2 多线程](#2 多线程)

复习

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次的自增

sql 复制代码
    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;

sql 复制代码
    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获取当前类的信息

sql 复制代码
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所处的环境不同,所以他们的生命周期也不同。
sql 复制代码
    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 演示后台线程
sql 复制代码
    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 线程是否存活
sql 复制代码
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 名称
sql 复制代码
    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. 通过共享的标记来进⾏沟通
sql 复制代码
    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() 代替⾃定义标志位.
sql 复制代码
    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依然存在,任务继续执行

  1. thread 收到通知的⽅式有两种:

  2. 如果线程因为调⽤ wait/join/sleep 等⽅法⽽阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志 ◦ 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法.可以选择忽 略这个异常, 也可以跳出循环结束线程.

  3. 否则,只是内部的⼀个中断标志被设置,thread 可以通过 ◦ Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志这种⽅式通知收到的更及时,即使线程正在 sleep 也可以⻢上收到。

5 等待⼀个线程 - join()
sql 复制代码
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 线程的状态

sql 复制代码
线程的状态继承在一个枚举中
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 状态的转换

sql 复制代码
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 状态的转换

sql 复制代码
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

sql 复制代码
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类里面的。

相关推荐
01漫游者3 分钟前
JavaScript函数与对象增强知识
开发语言·javascript·ecmascript
GottdesKrieges4 分钟前
OceanBase恢复常见问题
java·数据库·oceanbase
IGAn CTOU4 分钟前
Java高级开发进阶教程之系列
java·开发语言
leo825...7 分钟前
Claude Code Skills 清单(本地)
java·python·ai编程
csbysj202011 分钟前
SQL NULL 函数详解
开发语言
其实防守也摸鱼13 分钟前
CTF密码学综合教学指南--第三章
开发语言·网络·python·安全·网络安全·密码学
NGSI vimp14 分钟前
Java进阶——如何查看Java字节码
java·开发语言
We་ct1 小时前
深度剖析浏览器跨域问题
开发语言·前端·浏览器·跨域·cors·同源·浏览器跨域
身如柳絮随风扬1 小时前
多数据源切换实战:从业务场景到3种实现方案全解析
java·分布式·微服务
skywalk81631 小时前
在考虑双轨制,即在中文语法的基础上,加上数学公式的支持,这样像很多计算将更加简单方便,就像现在的小学数学课本里面一样,比如:定x=2*x + 1
开发语言