【JavaEE】-- 多线程(初阶)1

文章目录

1.认识线程

1.1 概念

1.1.1 线程是什么

多进程可以充分利用CPU资源去处理一些复杂业力,从而提升业务的处理效率。

创建进程--->申请资源--->加入PCB链表--->销毁进程--->释放资源--->把该进程从PCB链表中删除。

其中申请资源和释放资源对系统的性能影响比较大,涉及到内存和文件资源,处理一件事申请一份资源就够了,基于这样的思想,引出了线程的概念。线程用的是进程启动时从操作系统中申请的资源,线程也可以叫做轻量级的进程。

当创建一个进程的时候,每个进程都会包含一个线程,这个线程叫做主线程。每一个进程相当于一个公司,每个线程就相当于公司中的员工。

1.1.2 为啥要有线程

线程的优势:

  1. 线程的创建速度比进程快
  2. 线程的销毁速度比进程快
  3. 线程的CPU调度的速度比进程快

1.1.3 进程和线程的区别

进程和进程之间,涉及的各种资源彼此之间不受影响,也就是说进程与进程之间是相互独立的。

一个进程内线程之间是容易受到影响的。

通过多线程的方式可以提高程序处理任务的效率,创建进程的个数,根据CPU逻辑处理器的数量来作为参考。

问:如果无限制的创建线程,会不会进一步提升效率?

答:不一定。 当线程数小于逻辑处理器数时,会提升效率;当线程数大于逻辑处理器数时,由于过多的线程在阻塞等待状态,并没有真正的发挥并发的效果,反而因为创建线程消耗了系统资源。

问:会不会出现线程争抢资源的问题 ?

答:有可能会出现。某一个线程出现问题,就会影响其他的线程,从而影响整个进程。如果一个线程崩溃就会导致整个进程崩溃。

进程与线程的区别:

  1. 进程中包含线程,至少有一个主线程。
  2. 进程是申请资源的最小单位。
  3. 线程是CPU调度的最小单位。
  4. 线程共享进程申请来的所有资源。
  5. 一个线程如果崩溃了,就会影响整个进程。

1.1.4 Java的线程和操作系统线程的关系

线程是轻量级的进程,是操作系统的该娘,但是操作系统提供了一些API(应用程序编程接口,别人写好的一些函数/方法,直接使用即可

  • List item

)供程序员使用。每个操作系统提供的API都不一样。Java对不同操作系统的API进行了封装,对外统一提供了一种调用方法,在Java中提供了Thread类(标准库中的线程类)

1.2创建线程

1.2.1 方法1继承Thread类

自定义的线程是一个类,run方法

java 复制代码
public class Demo01_Thread {
    public static void main(String[] args) {
        MyThread01 myThread01 = new MyThread01();
        myThread01.start();//启动线程,申请操作系统中的PCB 
    }
}

//自定义一个线程类,继承JDK中的Thread类
class MyThread01 extends Thread{
    //定义线程的任务,根据业务需求,写代码逻辑即可。
    @Override
    public void run() {
        while (true){
            System.out.println("hello my thread....");
        }
    }
}

输出结果:

启动线程之后,一个Java中的Thread对象就和操作系统中的PCB相对应。

操作系统中的线程PCB 和 Java中的线程,Thread类的一个对象,是对PCB的一个抽象。

Java中创建一个线程对象---->JVM调用系统中的API--->创建系统 中的线程。(系统中的线程才参与CPU的调度)

定义两个线程

java 复制代码
public class Demo02_Thread {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        myThread.start();

        while (true){
            Thread.sleep(1000);
            System.out.println("hello main thread....");
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello my thread....");
        }
    }
}

输出结果:

两个线程同时进行,互不干扰,线程的执行顺序没有什么规律,这个和CPU调度有关,由于CPU调度是抢占式执行的,所以哪个线程占用当前CPU资源是不确定的。

Thread类中的run()方法和start()方法之间的区别?

  1. start()方法,是真实的申请系统线程PCB,从而启动一个线程,参与CPU调度。
  2. run()方法,定义线程时指定线程要执行的任务,只是Java对象的一个普通方法而已。

可以通过 JDK安装目录\bin目录下的jconsole.exe(JDK提供的一个查看JVM运行状态的工具)查看JVM中线程的状态。





1.2.2 方法2实现Runnable接口

java 复制代码
public class Demo03_Thread {
    public static void main(String[] args) throws InterruptedException {
        //创建Runnable的实例
        MyRunnable01 myRunnable01 = new MyRunnable01();

        //创建线程
        Thread thread = new Thread(myRunnable01);//线程需要执行什么任务就对应的Runnable即可

        //启动线程,创建PCB,参与CPU调度
        thread.start();

        while (true){
            Thread.sleep(1000);
            System.out.println("hello main thread....");
        }

    }
}

//单独定义了线程的任务,将线程类和业务解耦
class MyRunnable01 implements Runnable{

   //实现具体的任务
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello my runnable...");
        }
    }
}

输出结果:

1.2.3 其他变形

函数式接口:接口中只定义一个方法。

1. 通过Thread匿名内部类的方式创建线程

java 复制代码
public class Demo05_ThreadCreat {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("通过Thread匿名内部类的方式创建线程...");
                }
            }
        };

        thread.start();
    }
}

输出结果:

  1. 创建了一个Thread类的子类,但没有为子类定义类名,匿名
  2. 在0里可以编写子类的代码定义,写法与正常类的实现方式相同
  3. 创建好的匿名内部类的实例赋值给thread变量

2. 通过Runnable接口的匿名内部类的方式创建线程

java 复制代码
public class Demo06_ThreadCreate {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("通过Runnable接口匿名内部类的方式创建线程...");
                }
            }
        });

        thread.start();
    }
}

输出结果:

3. 通过lambda表达式创建线程(推荐使用)

java 复制代码
public class Demo07_ThreadCreate {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println("通过Lambda表达式创建线程...");
        });
        thread.start();
    }
}

输出结果:

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

情景:自增两个10亿次。

java 复制代码
public class Demo01_Thread {
    public static long count = 10_0000_0000l;
    public static void main(String[] args) throws InterruptedException {
        //串行
        serial();
        //并行
        concurrency();
    }

    private static void concurrency() throws InterruptedException {
        //记录开始时间
        long begin = System.currentTimeMillis();

        //定义两个线程,分别进行累加操作
        //第一个线程
        Thread thread01 = new Thread(()->{
            long a = 0l;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        thread01.start();

        //第二个线程
        Thread thread02 = new Thread(()->{
            long a = 0l;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        thread02.start();

        //等待thread01和thread02两个线程执行完成
        thread01.join();
        thread02.join();

        //记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("并行执行耗时:" + (end - begin));

    }

    private static void serial() {
        //记录开始时间
        long begin = System.currentTimeMillis();

        long a = 0l;
        for (int i = 0; i < count; i++) {
            a++;
        }

        long b = 0l;
        for (int i = 0; i < count; i++) {
            b++;
        }

        //记录结束时间
        long end = System.currentTimeMillis();

        System.out.println("串行执行耗时:" + (end - begin));
    }
}

输出结果:

java 复制代码
串行执行耗时:1937
并行执行耗时:1079

通过多线程的方式,可以明显的提升效率,并行的耗时是串行的一半多一点的时间(有创建线程的消耗)。

问:如果仅将10亿改成10万,那么多线程还会不会提升效率?

答:不会。并不是任何时候多线程的效率都要比单线程高,当任务量很少的时候,单线程的效率可能会比多线程更高,创建线程本身也会有一定的系统开销,这个开销没有创建进程的开销大,两个线程在CPU上调度也需要一定的时间。

2.Thread 类及常见方法

2.1Thread的常见构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable target) 使用Runnable对象创建线程对象
Thread(String name) 创建线程对象并命名
Thread(Runnable target, String name) 使用Runnable对象创建线程对象,并命名
java 复制代码
public class Demo02_Thread {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("hello thread...");
            }
        });
        t1.start();

        Thread t2 = new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("我是一个有名字的线程...");
            }
        }, "我是一个线程");
        t2.start();
    }
}

输出结果:

可以通过Thread。currentThread().getName()来获取线程名。

java 复制代码
public class Demo02_Thread {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "  hello thread...");
            }
        });
        t1.start();

        Thread t2 = new Thread(()->{


            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "  我是一个有名字的线程...");
            }
        }, "我是一个线程");
        t2.start();
    }
}

输出结果:

java 复制代码
public class Demo02_Thread {
    public static void main(String[] args) {
        Thread t2 = new Thread(()->{
            //获取当前类名
            String cName = Demo02_Thread.class.getName();
            //获取当前线程名
            Thread thread = Thread.currentThread();
            String tName = thread.getName();
            //获取当前方法名
            String fName = Thread.currentThread().getStackTrace()[1].getMethodName();

            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("当前类:" + cName + "  当前方法:" + fName + "  当前线程:" + tName);

            }
        }, "我是一个线程");
        t2.start();
    }
}

输出结果:

java 复制代码
当前类:lesson02.Demo02_Thread  当前方法:lambda$main$0  当前线程:我是一个线程
当前类:lesson02.Demo02_Thread  当前方法:lambda$main$0  当前线程:我是一个线程
当前类:lesson02.Demo02_Thread  当前方法:lambda$main$0  当前线程:我是一个线程
......

2.2Thread的几个常见属性

属性 获取方法 说明
ID getID() JVM中默认为Thread对象生成的一个编号,是Java层面的,要和操作系统层面的PCB区分开
名称 getName()
状态 getState() 是Java层面定义的线程状态,要和PCB区分开
优先级 getPriority()
是否后台线程 isDaemo() 线程分为前台线程和后台线程,通过这个标识位来区分当前线程是后台还是前台
是否存活 isAlive() 表示的是系统中PCB是否销毁,与thread对象没啥关系
是否被中断 isInterrupted() 通过设置一个标志位让线程在执行时判断是否要退出

Thread是Java中的类-->创建Thread对象-->调用start()方法-->JVM调用系统API生成一个PCB-->PCB与Thread对象一一对应

Thread对象与PCB所处的环境不同 ,所以它们的生命周期也不相同。
线程是否存活

java 复制代码
public class Demo04_Thread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("hello thread....");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("线程执行完成");
        });
        System.out.println("启动之前是否存活:" + thread.isAlive());
        thread.start();
        System.out.println("启动之后是否存活:" + thread.isAlive());

        //等待线程执行完成
        thread.join();
        //等待一会,确保PCB已销毁
        Thread.sleep(1000);
        System.out.println("启动之后查看线程是否存活:" + thread.isAlive());
    }
}

输出结果:

java 复制代码
启动之前是否存活:false
启动之后是否存活:true
hello thread....
hello thread....
hello thread....
hello thread....
hello thread....
线程执行完成
启动之后查看线程是否存活:false

线程中断

  1. 通过共享的标记来沟通
java 复制代码
public class Demo05_Thread {
    //定义一个标志位
    static boolean isQuit = false;//lambda表达式里面如果使用局部变量,会触发"变量捕获",需要把这个变量定义为全局的。
    public static void main(String[] args) throws InterruptedException {
    
        Thread thread = new Thread(()->{
            while (!isQuit) {
                System.out.println("hello thread....");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            //线程退出
            System.out.println("线程退出");
        });

        //启动线程
        thread.start();
        //休眠5秒
        Thread.sleep(5000);

        //修改标志位
        isQuit = true;
    }
}

输出结果:

  1. 调用interrupt()方法来通知
java 复制代码
public class Demo06_Thread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            //通过线程对象内部维护的中断标识,判断当前线程是否需要中断
             while (!Thread.currentThread().isInterrupted()){
                 System.out.println("hello thread...");
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
            System.out.println("线程已退出");
        });

        //启动线程
        thread.start();
        //休眠
        Thread.sleep(5000);

        //中断线程,发出中断信号
        thread.interrupt();
    }
}

输出结果:

可以看到输出的结果是抛出了一个异常,然后继续往下执行,抛出的异常信息显示的是睡眠中断。

线程中具体的任务是打印一句话,所以线程中的大部分时间是在休眠,也就意味着线程是在休眠的时候中断的,所以就是中断的休眠状态下的线程,而不是执行线程任务的线程。

2.3后台线程和前台线程

java 复制代码
public class Demo03_Thread {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            while (true) {
                System.out.println("hello thread...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        //设置为后台线程
        t1.setDaemon(true);
        t1.start();
        System.out.println("线程是否存活:" + t1.isAlive());
        System.out.println("main方法执行完成");
    }
}

输出结果:

如果不设置为后台线程呢?

设置为后台线程之后,main方法执行完成之后整个程序就退出了,子线程也就自动结束了。

如果是前台线程,子线程不会受main方法的影响,会一直运行下去。

创建线程时默认时前台线程。

前台线程可以阻止进程的退出,后台线程不阻止进程的退出。

2.4 中断一个线程

正常执行任务的状态下中断线程

java 复制代码
public class Demo07_Thread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            //通过线程对象内部维护的中断标识,判断当前线程是否需要中断
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hello thread...");
            }
            System.out.println("线程已退出");
        });

        //启动线程
        thread.start();
        //休眠
        Thread.sleep(5000);

        //中断线程,发出中断信号
        thread.interrupt();
    }
}

输出结果:

调用thread.interruptO方法时

  1. 如果线程在运行状态,直接中断线程,不会报异常,符合程序预期。
  2. 如果线程在等待状态,就会报一个中断异常,要在异常处理代码块中进行中断逻辑实现。
相关推荐
考虑考虑2 小时前
Jpa使用union all
java·spring boot·后端
用户3721574261352 小时前
Java 实现 Excel 与 TXT 文本高效互转
java
浮游本尊3 小时前
Java学习第22天 - 云原生与容器化
java
渣哥5 小时前
原来 Java 里线程安全集合有这么多种
java
间彧5 小时前
Spring Boot集成Spring Security完整指南
java
间彧5 小时前
Spring Secutiy基本原理及工作流程
java
Java水解6 小时前
JAVA经典面试题附答案(持续更新版)
java·后端·面试
洛小豆8 小时前
在Java中,Integer.parseInt和Integer.valueOf有什么区别
java·后端·面试
前端小张同学9 小时前
服务器上如何搭建jenkins 服务CI/CD😎😎
java·后端
ytadpole9 小时前
Spring Cloud Gateway:一次不规范 URL 引发的路由转发404问题排查
java·后端