多线程基础(一)

您所在的位置:网站首页 并发与并行的概念是什么意思 多线程基础(一)

多线程基础(一)

2023-11-03 11:50| 来源: 网络整理| 查看: 265

多线程基础(一)| 什么是线程、串行并发并行、定义线程单元 1.程序、进程与线程线程和进程的区别与联系 2.线程帮我们解决了哪些事情start方法线程的父子关系 3.串行?并发?并行?4.线程常用方法(Thread类中提供的方法)5.定义线程执行单元的方式

1.程序、进程与线程

程序分为可执行程序 和不可执行 程序

进程: 简单的讲进程就是运行起来的程序,我们编写的代码运行起来,或者我们电脑上的可执行程序例如.exe后缀的文件运行起来就变成了程序。 如: 当我们写下这些代码:

public static void main(String[] args) { while (true) { } } }

在这串代码开始运行之前,可以看到我们电脑的任务管理器是没有这个 Java进程的(会有别的正在执行的Java程序) 在这里插入图片描述 当这串代码(即这个程序)开始运行,任务管理器上便有了这个Java进程,可以看到java.exe的数量变多了一个 在这里插入图片描述 线程: 线程(英语: thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。 在这里插入图片描述

线程存在的意义是:让一个进程能够同时处理多件事情。比如我们平时所用的通讯软件至少都会有接收线程和发送线程在同时进行,这样我们在给别人发送信息的同时也不会错过别人发送过来的信息。

线程和进程的区别与联系

两者的联系:

一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。处理机分给线程,即真正在处理机上运行的是线程。

注:处理机包括中央处理器(cpu),主存储器,输入-输出接口。处理机加接外围设备就构成完整的计算机系统。

进程和线程的区别体现在以下几个方面:

地址空间和其他资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其他进程内不可见。 线程间通信:同一个进程中的线程通信:进程间通信IPC(管道,信号量,共享内存,消息队列,网络),线程间可以直接读写进程数据段(如全局变量)来进程通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。调度和切换:线程上下文切换比进程上下文切换快得多。进程具有独立的空间地址,一个进程崩溃后,在保护模式下不会对其它进程产生影响。保护模式下,程序地址为虚拟地址,然后由OS系统管理内存访问权限,这样每个进程只能访问分配给自己的物理线程只是一个进程的不同执行路径,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。 2.线程帮我们解决了哪些事情

让多个功能可以同时进行

start方法

启动一个子线程需要在main方法中调用start()方法,而不能直接调用run()方法,如果直接调用run方法那仅仅是一种在main线程中普通的方法调用而已,并不是真正的启动了一个线程。

在JDK源码中可以发现,start()方法中又调用了start0()方法,然后started标记为被设置为true,线程开始启动。

public synchronized void start() { if (this.threadStatus != 0) { throw new IllegalThreadStateException(); } else { this.group.add(this); boolean started = false; try { this.start0(); started = true; } finally { try { if (!started) { this.group.threadStartFailed(this); } } catch (Throwable var8) { } } } }

start0方法是一个native方法,native方法都是用C语言实现的,start0方法是通过C语言启动了一个线程,在这个方法中会调用run方法中的代码,run方法是子线程的执行单元。

所以我们可以看到,我们调用的是子线程的start方法,但是执行的逻辑是run方法中定义的代码逻辑。

线程的父子关系

main线程是程序里所有线程的起点,所有的子线程都是在main线程中启动的,如果我们定义了一个A线程,在A线程中也可以启动一个B线程。在每个子线程中都可启动别的新线程,像树一样,树中对应的父节点就是父线程。 在这里插入图片描述

3.串行?并发?并行?

通俗的讲:

串行:程序从main方法的第一行执行到最后一行,所有的业务逻辑都是按顺序执行的并发:程序不一定按照代码顺序执行,不同的线程同时执行并行:拿单核CPU来说,就不存在并行的问题,cpu可以说是运行线程的工具,单核cpu在同一个时刻只能有一个线程在执行,以肉眼不可观察的速度在不同的线程中切换,如果把cpu比作画笔的话,一根笔肯定是不能同时画出多根线的。而并行指的是有多个cpu的电脑,代表你有了多根笔,那么就可以同时画出多条线,这就是并行的过程。

总结:串行是按照指令顺序依次执行,并行是真正的同时执行,并发知识cpu的快速切换给我们产生的同时执行的错觉。并发和并行都是充分利用CPU的手段。

4.线程常用方法(Thread类中提供的方法)

在这里插入图片描述 Thread类中的常用方法很多,这里先简述两个,还有别的常用方法在后面的学习中会提到。

让线程睡眠我们一般使用sleep()方法,括号中是想让这个线程睡眠的毫秒数,但是这种方法可读性太差,可以使用另一种方法:

TimeUnit.XXXX.sleep()

XXXX可被替代为以下字符,分别代表秒、分、时、天等单位

在这里插入图片描述 setName()方法,可以给线程设置名字、getName()方法可以获取线程的名字。还有一种方法是把线程名字的字符串当成参数传给类中,重写构造方法。

5.定义线程执行单元的方式

创建线程的方式只有一种,就是new thread对象。定义线程执行单元的方式有三种:

继承Thread类,重写run方法实现Runnable接口,重写run方法实现Callable接口,重写call方法

继承Thread类: 因为已经继承了Thread类如果要访问当前线程,直接使用this即可获得当前线程

class ThreadA extends Thread { String name; public ThreadA(String name) { this.name = name; } @Override public void run() { System.out.println(this.name); } } public class TestApp { public static void main(String[] args) { ThreadA t = new ThreadA("A"); t.start(); } }

实现接口: 如果要访问当前线程必须使用Thread.currentThread()方法 实现runnable接口: 创建线程时需要先创建实现该接口类的对象,并将此对象作为Thread的参数来创建Thread对象

class ThreadA implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()); } } public class TestApp { public static void main(String[] args) { ThreadA runnable = new ThreadA(); Thread t = new Thread(runnable); t.setName("A"); t.start(); } }

实现callable接口: 创建线程时需要先创建实现该接口类的对象,并将此对象作为FutureTask的参数,然后将futuretask对象作为thread的参数创建Thread对象

class CallableThread implements Callable { @Override public String call() throws Exception { System.out.println("Thread name is " + Thread.currentThread().getName()); return "abc"; } } public class CallableTest { public static void main(String[] args) { CallableThread callable = new CallableThread(); // FutureTask对象封装了Callable对象的call()方法的返回值 FutureTask task = new FutureTask(callable); // 获取未来数据的方式 // 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口) Thread thread = new Thread(task); thread.setName("子线程"); thread.start(); try { System.out.println(task.get()); // 运行在主线程中 // get方法调用之后如果子线程运行结束,正常返回返回值 // 如果子线程未结束那么将会阻塞在这里直到子线程执行完返回返回值 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }

继承类与实现接口的区别

线程类集成字Thread则不能再继承其他类,而Runnable/Callable接口可以。线程类集成字Thread对于Runnable/callable来说,使用线程类内部的方法方便一些实现Runnable/Callable接口的线程类的多个线程可以更加方便的访问同一变量,而Thread类则需要内部类进行替代。

Callable 和 Runnable接口的区别

Callable规定的方法是call(),而Runnable规定的方法是run()Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。call()方法可抛出异常,而run()方法是不能抛出异常的。运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3