自学内容网 自学内容网

【JavaEE】认识线程

4c9c742f3a02521f400df47c0c39dd48.png


一、引入线程

在任务管理器界面可以看到很多进程,可以利用CPU多核心这一点来更有效的执行这些进程,即在处理过程中将CPU的多个时间片分配给每个进程,这样的 "多进程编程"就可以起到并发编程的效果,因为进程可以被调度到

虽然多进程编程可以解决进程多的问题,但也带来了一些麻烦

比如:一个服务器要给多个客户端提供服务,每当有一个客户端连上服务器,服务器就要创建一个进程给其提供服务,当客户端断开时,服务器就要销毁这个进程,那么问题就是如果有客户端频繁的连接、断开,服务器就要频繁的创建和销毁进程,这样的频繁操作会使服务器响应变慢

所以为了解决上述进程太"重量"的问题,就引入了线程(创建和销毁的开销较小)

二、线程(thread)

2.1 再谈PCB

线程可以理解为进程的一部分,一个进程可以包含一个线程或多个线程,上篇文章讲过PCB用来描述进程,实际上是一个PCB描述一个线程,多个PCB联合在一起描述了一个进程

针对PCB的那几个属性:

82304220c6a917bd24cc832c70002460.png

每一个线程都是独立在CPU上调度执行的,所以又称线程是系统调度的基本单位

2.2 为什么线程创建和销毁的开销比进程小?

在创建进程时,会涉及到资源分配和释放,而一个进程中的若干个线程是共享资源的,在创建线程时,此时进程已经创建好了,资源也分配好了,那么创建线程就省去了资源分配和释放的步骤,后续再创建线程就不必再申请资源

同一个进程包含N个线程,只有在创建第一个线程时(也是创建进程时)会进行资源申请操作,后续再创建线程时就不会在进行申请资源的操作了

三、多线程代码

Java提供了一个 Thread类 用来表示线程,在该类中有一个run方法,方法内容就是该线程要执行的任务,所以可以通过写一个类继承Thread并重写run方法,这样就可以自己设定线程要完成的任务

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}

public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
    }
}

run方法只是描述了线程要完成的任务,并没有创建线程,此时就需要调用start方法,通过start方法调用操作系统的提供的"创建线程"api,在内核中创建对应的pcb,在系统调度到这个线程时,就会执行run方法中的逻辑

(run方法不是被start调用的,而是在start创建出来的线程,在线程里被系统调用的)

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}

public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();  //打印出 hello thread
    }
}

多个线程之间的执行顺序是无序的

在上述代码中,有两个线程:1.t 线程,2. main方法所在的线程(主线程),给出下面一段代码

class MyThread extends Thread {
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000); //sleep是让该线程主动处于阻塞状态1s,即代码在此处等待1s,不继续往下执行,由于该方法会抛出异常,所以用try-catch结构进行处理
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello thread");
        }
    }
}
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.start();
        while (true) {
            Thread.sleep(1000);
            System.out.println("hello main");
        }
    }
}

该代码的执行结果为:

ebb3197189140e020785a28889befcdd.png

main线程和t线程并发执行,main线程和t线程的打印都在执行,且每次循环谁先执行的顺序不一定,所以称多个线程之间的执行顺序是无序的

3.1 5种创建线程的方法

1.上述的创建Thread子类并重写run方法就是其中一种

2.通过实现Runnable接口创建线程

Runnable接口:

3d208a5cc226a4a21cf44faca532881c.png

在该接口中只有run方法,run方法用来描述线程要做的事情

class MyRunnable implements Runnable {
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("hello thread");
        }
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new MyRunnable());
        t.start();
        while (true) {
            Thread.sleep(1000);
            System.out.println("hello main");
        }
    }
}

上述的Thread t = new Thread(new MyRunnable());这样写可以解耦合,Thread没有记录任务内容,它只负责执行,任务内容由Runnable记录,这样就将任务和执行分开了,未来改用其他方式执行这些任务的改动成本较低

3.针对1的变形,使用匿名内部类,继承Thread并重写run方法

public class Test {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        t.start();
    }
}

使用匿名内部类的具体步骤如下:

1)创建一个Thread的子类,该类的名字匿名

2)创建子类的同时又创建这个子类的实例

3)最后重写run方法

4.针对Runnable的匿名内部类

Thread t = new Thread(new Runnable() {
    @Override
    public void run() {
        ...
    }
});

此处的匿名内部类只针对Runnable,和Thread没有关系,只是把Runnable的实例作为参数传入到Thread的构造方法中

5.使用lambda表达式

Thread t2 = new Thread(() -> {
    //在这里直接写线程所要执行的任务,也就是run方法中的内容
});

总结:上述5种写法本质上就是2点:1.把线程要执行的任务内容表示出来,2.通过Thread的start方法来创建/启动系统中的线程


🙉本篇文章到此结束,之后会继续探究多线程的内容


原文地址:https://blog.csdn.net/m0_74270127/article/details/137206825

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!