多线程Thread(初阶一:认识线程)

目录

一、引用线程的原因

二、线程的概念

三、进程和线程的区别

四、操作系统的内核

五、多线程编程

1、线程创建的几种方式

(1)、继承Thread类,重写run

(2)、实现Runneble接口,重写run

要怎么查看进程的情况呢?

(3)继承Thread类,重写run,但使用匿名内部类

(4)、实现Runneble接口,重写run,使用内部类

六、star()和run()的区别

都看到这了,点个赞再走吧,谢谢谢谢谢!!!


一、引用线程的原因

多任务操作系统,希望系统能同时运行多个任务。所以会涉及到进程,需要对进程进行管理、调度等。

而单任务操作系统,就完全不涉及到进程,也不需要管理、调度了。

而进程,就是解决并发编程的这样问题,事实上,进程也能解决大部分并发编程的问题(Java不提倡多进程编程)。但有些情况就很乏力了,如下:

网站 / Web开发,是一种服务器程序,我们知道,一个网站服务器在同一时刻,会受到很多请求,针对这些请求,会创建进程,一个请求创建一个进程,创建完一个进程,又要销毁这个进程,这意味着这个网站服务器要频繁的创建和释放资源,这个操作开销是比较大的,

原因:

我们知道,进程是资源(CPU,硬盘,内存,网络带宽)分配的基本单位,而一个进程,刚启动的时候,首当其冲的就是申请内存资源,因为进程需要把依赖的代码 / 数据,从磁盘加载到内存中。

而从系统分配一个内存,并非是件容易的事,一般来说,申请内存的时候需要指定一个大小,系统内部就要把各自大小的空闲内存,通过一定的数据结构,给组织起来,实际申请的时候,就需要去这样的空间中进行查找,找到一个大小合适的空闲内存,进行分配。

结论:进程在创建和销毁的时候,开销比较大,主要体现在申请和释放资源上。

这时,我们就引入线程,来解决开销比较大的问题。


二、线程的概念

线程也可以理解成“轻量级进程”,基于进程做的一些改进和调整,使其变得开销(资源的申请和释放)不那么大。

因为进程的独立性,一个进程在内存中申请一块资源时,那个块资源只能让那个进程使用,其他的进程不能使用。而一个线程在内存中申请了一块资源,其他不同的线程也可以使用这块资源,这样就避免了多次的资源申请和释放。PCB可以表示进程,也可以表示线程。

进程在内存中的使用范围,如图

PCB有个属性,是内存指针

多线程的PCB,也有内存指针,但可以指的是同一块内存空间,以及进程有的pid、状态、

上下文、优先级等,线程也有。


三、进程和线程的区别

1、进程包含线程,进程可以理解成多个线程的组合,这些线程称为线程组。

关系图如下:

2、进程扮演的角色是申请内存空间,而线程扮演的角色是调度数据 / 执行代码。

3、1个进程至少有1个线程,每个进程有自己的资源空间,而进程里的线程共用这块资源空间。

4、进程和进程之间不会相互影响,但是进程中的某个线程出问题了,可能会影响到这个进程中的其他线程,导致这个进程也出问题。

5、同一个进程中的线程之间,可能会相互干扰,引起线程安全问题。

6、线程不是越多越好,应该要合适,如果线程太多了,调度开销可能非常明显。


四、操作系统的内核

内核,是操作系统最核心的功能模块,操作系统 = 内核 + 配套的应用程序,操作系统大概可以分为两个模块,用户空间(用户态),内核空间(内核态),操作系统理解为银行,银行大概分为两个区域,如图:

办事窗口里面的区域是做一些比较重要的事,一般人都在大堂,只能在大堂里活动,办事窗口里面能做一些核心,重要的事,外面的人也不能闯进来,这时,我们可以理解为内核空间是办事窗口,用户空间是大堂,如图:

那如果用户空间里的程序,想要使用内核资源,需要针对系统内的软硬件资源进行操作,要怎么办呢,这时,Windows操作系统就站出来了,提供系统的api给这些程序使用,调用内核资源,进一步在内核中完成这样的操作。

为什么要划分出内核空间(内核态)和用户空间(用户态)呢?

我们想想,如果一个应用程序可以直接调用硬件资源,如果出现一个bug了呢?不直接把硬件给干烧了,所以,划分出这两模块,主要目的还是为稳定,系统会封装一些api,这些api都是安全的操作,供应用程序使用,就不至于对系统 / 硬件设备造成危害了。


五、多线程编程

1、线程创建的几种方式

(1)、继承Thread类,重写run

一个简单的线程创建,代码如下:

class MyThread extends Thread {
    @Override
    public void run() {
        //run方法是该线程的入口方法
        System.out.println("Hello World");
    }
}
public class SystemCode {
    public static void main(String[] args) {
        //2、根据刚才的类,创建一个实例
        Thread t = new MyThread();
        //3、调用Thread的start方法,才会真正调用系统的api,在系统内核中创建线程
        t.start();
    }
}

执行结果:

(2)、实现Runneble接口,重写run

代码如下:

class MyThread implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("Hello thread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyThread());
        t.start();
        while (true) {
            System.out.println("Hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

执行效果:

要怎么查看进程的情况呢?

1、使用jdk自带的jconsole.exe文件。

看到这里有这么多的线程,我们发现,其实一个java的进程,包含的线程也挺多的。

这里除了main线程和我们自己创建的线程,其余的线程,都是 jvm 自带的线程,用来完成垃圾回收(gc自动帮你释放内存),监控统计各种指标(如果我们的代码出问题了,这些指标就可以给我们提供一些参考和线索),把统计指标通过网络的方式,传输给其他程序。

堆栈跟踪信息:是很有意义的,描述了线程的调用栈,线程里当前执行道路哪个方法的第几行代码了,这个方法是怎么一层一层调用过去的。

和我们所写的代码行序是对应的

2、使用idea查看线程

debug调试我们的代码,然后打断点到这:

这有个下拉窗,我们就可以看到现有的线程了:

(3)继承Thread类,重写run,但使用匿名内部类

代码:

public class ThreadDemo2 {
    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();
        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

执行效果和上面的一样

说明:内部类的最大用途,也就是匿名内部类,没有名字,意味着不能重复使用,用一次就不要了。new Thread()后面 { }里的意思是要定义一个类,这个类继承自Thread,此处的 { } 中可以定义子类的属性和方法,此处的 { } 最主要目的就是重写run方法。

这个代码还创造出了子类的示例

        

这里的 t 指向的实例,并非单纯的Thread,而是Thread的子类,因为我们不知道这个子类叫啥,因为是匿名的。

(4)、实现Runneble接口,重写run,使用内部类

代码:

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

注意:匿名内部类是在()里面的。

(5)使用lambada(推荐)

代码:

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

注意:lambda表达式来代替功能接口,所以要代替的是实现接口的功能,重写的是run方法,所以要写在Thread()的括号里面。


六、star()和run()的区别

看到这,是不是觉得对这两个区别优点疑惑?但事实上,这两个概念是毫不相干的两个东西,创建出来的线程实例,只有调用 star,才会真正调用系统的api,在系统内核创建出线程;而 run 是线程执行的入口。这里,就有老铁疑惑了,我用 run 也可以调用这个重写的方法啊,和用 start 一样,哎,这就不对了,举个以下例子:

两个代码分别单独执行star 和 run

代码:

run:

class MyThread extends  Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("这是我的线程,正在工作");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.run();
        //t.start();
    }
}

start:

class MyThread extends  Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("这是我的线程,正在工作");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        //t.run();
        t.start();
    }
}

执行效果:

这两代码的执行效果是一样的,但我们现在改一下,如下:

run:

class MyThread extends  Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("这是我的线程,正在工作");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.run();
        //t.start();
        while (true) {
            System.out.println("这是主线程,正在工作");
            Thread.sleep(1000);
        }
    }
}

start:

class MyThread extends  Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("这是我的线程,正在工作");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        //t.run();
        t.start();
        while (true) {
            System.out.println("这是主线程,正在工作");
            Thread.sleep(1000);
        }
    }
}

分别执行效果:

run:

start:

可以看到不同了吧,这是因为star之后,这里有两个线程(t 线程和主线程),他们都是各自调度各自的线程,就有两个循环在执行了,但是如果使用 run 的话就只有一个线程了,只在一个循环里执行,所以之后在当前方法里面循环,不会执行下面的循环。


都看到这了,点个赞再走吧,谢谢谢谢谢!!!

猜你喜欢

转载自blog.csdn.net/cool_tao6/article/details/134483484
今日推荐