流行网站开发框架,做团购网站有什么难处,全国建设网站,网址域名大全2345网址目录
#x1f33e; 前言
#x1f33e; 了解线程
#x1f308;1.1 线程是什么#xff1f;
#x1f308;1.2 一些基本问题
#x1f33e;2、创建线程的方式
#x1f308; 2.1 继承Thread类
#x1f308; 2.2 实现Runnable接口并重写run()方法
#x1f308; 注意…目录 前言 了解线程
1.1 线程是什么
1.2 一些基本问题
2、创建线程的方式 2.1 继承Thread类 2.2 实现Runnable接口并重写run()方法 注意多个线程的写法
3、多线程编程的意义
4、线程的类与常用方法 4.1 构造方法 4.2 Thread类的几个常见属性 5、线程的状态 前言 在学习之前我们先简单的了解一下计算机中的常见的概念。 1、冯诺依曼结构体系 现代的计算机大多遵循冯诺依曼体系结构。主要由运算器控制器存储器输入设备输出设备组成。 2、操作系统 操作系统是一组做计算机资源管理系统的统称。常见的操作系统由Windows系列Linux系列OSX系列ISO系列Android系列。操作系统的作用主要有1向上为应用程序提供一个稳定的运行环境2向下管理所有的硬件设备3为用户提供一个人机交互的界面。 3、进程 每一个运行起来的程序(软件)操作系统都会以进程的形式将他们管理起来。在操作系统中每一个进程被描述为一个PCB进程控制块抽象。一个PCB中主要包括PID内存指针文件描述符表进程的优先级进程的状态上下文信息和记账信息。 CPU分配 - 进程调度假设大部分场景下都是单CPU单核的计算机。操作系统对于CPU资源的分配采用的是时间模式不同的进程在不同的时间段去使用CPU资源。 内存分配 -内存管理操作系统对内存资源的分配采用的是空间模式不同的进程使用内存中的不同区域互相之间不会干扰。 4、虚拟内存空间 进程启动之后会申请内存空间正常情况下是没有问题的。但是可能会出现一些特殊情况比如C中的野指针等。因此实际中操作系统中有一个模块叫做MMUMMU会给每个进程分配一个虚拟内存地址这个地址和真实的物理内存之间建立映射关系。当进程越界访问内存空间时MMU直接会进行拦截起到了一个校验的作用不会对真实的物理内存造成影响。 存在的问题但是由于MMU的限制导致不同进程之间不能够互相访问内存所以如何实现进程之间的数据共享从而达到“信息交换”的需求针对这一问题进程间通信的需求应运而生。目前主流操作系统提供的进程通信机制主要有管道共享内存文件网络信号量信号。 问题并发与并行的区别 在一个处理器上不停横向的轮动叫做并发在多个处理器上执行叫做并行。通俗解释就是并发是一会干这件事一会干那件事但其实同一时刻只能干一件事并行是一边干这件事一边干那件事在同一时刻干多件事真正意义上的同时进行在编程方面我们统一称为并发编程。 了解线程 1.1 线程是什么 一般多进程用来处理一个很大或者很复杂的任务。当进程启动的时候需要申请内存申请文件资源将PCB加入到链表中当进程结束的时候需要释放文件释放内存从链表中删除PCB。但是在这个过程中申请和释放资源的操作是十分耗时的。因此为了解决资源消耗的问题提出了一个轻量级进程的概念线程。在创建线程的时候使用的是进程创建时申请到的所有资源线程只需要关注要处理的任务即可。 举个例子比如张三设计了一些皮包他要去开一个工厂。他需要做以下一些事情、
1去工业园区申请地皮水电仓库等(花了一个月的时间和几十万的钱)
2修建厂房和基础设施花了几个月和几十万的钱
3购买生产设备和原材料招工花了一周和几万块钱
4开始生产。 主要关注两者之间的对应关系 1.2 一些基本问题 ❓ 问题1进程与线程之间的区别面试题 1对于一个进程而言必然会有一个线程主线程 2进程是申请系统资源的最小单位线程是CPU调度的最小单位 3进程之间互不影响线程使用的是进程统一申请来的资源线程之间可以相互影响。 ❓ 问题2 使用多线程编程的原因 1为了充分利用CPU的资源 2利用轻量级进程的特性来减少系统性能的开销: 线程创建的效率比进程高; 线程销毁的效率比进程高 线程调度的效率比进程高 ❓ 问题3使用多线程存在的问题 当线程数量增大到一定程度之后可能会给CPU调度带来负担出现资源抢夺的问题就会出现一些线程不安全的现象。 2、创建线程的方式 2.1 继承Thread类 三部曲继承Thread来创建一个线程类 - 创建MyThread的实例 - 使用start()方法启动线程 (1)正常写法
public class Demo1_createThread {public static void main(String[] args) {Thread t new MyThread();t.start();while (true){System.out.println(main thread);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
class MyThread extends Thread{Overridepublic void run() {while (true){System.out.println(这里是线程运行的代码);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
} 2使用匿名内部类的写法 public static void main(String[] args) {//匿名内部类的形式Thread t new Thread(){Overridepublic void run() {while (true){System.out.println(生产生产生产...金币金币金币...111);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};//启动进程t.start();} 2.2 实现Runnable接口并重写run()方法 三部曲实现Runnable接口 - 创建Thread类实例将Runnable对象作为其目标参数 - 使用start()方法启动线程
注意:在调用start()方法后JVM会调用系统API并在系统中生成一个PCB来执行run()方法中的代码。 1正常写法
public class Demo2_createRunnable {public static void main(String[] args) {MyRunnable myRunnable new MyRunnable();Thread t new Thread(myRunnable);t.start();while (true){System.out.println(main thread);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
class MyRunnable implements Runnable{Overridepublic void run() {while (true){System.out.println(这是线程运行的代码);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}
注意两个线程并不是有序的交替运行因为CPU的调度是随机的。线程的执行是“抢占式执行”。 2使用匿名内部类的写法 public static void main(String[] args) {Thread t new Thread(new Runnable() {Overridepublic void run() {while (true){System.out.println(生产皮包金币1);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});//开启线程t.start();}
3使用Lambda写法
public static void main(String[] args) {//Lambda表达式的使用条件就是该接口必须是一个函数式接口//比如Runnable接口就是一个 FunctionalInterface所谓的函数式接口就是里面只有一个方法。比如(Runnable接口)中只有一个run();//怎么用Lambda表达式呢()里写run方法的所有参数名不需要参数类型然后一个-即可。后面是正常的函数的功能实现Thread t new Thread(()-{while (true){System.out.println(生产皮包金币1);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//开启线程t.start();} ❓ 问题使用Runnable 定义任务的好处 1解耦将定义线程与定义任务分开。其中解耦的意思就是将不同的功能都给分开如果要修改或者查找相应的功能的时候可以在指定的位置找 2将创建线程与定义任务分开以便修改代码时可以统一修改。 注意多个线程的写法
public class Demo3_multiRunnable {public static void main(String[] args) {//创建任务的对象MyRunnable1 myrunnable1 new MyRunnable1();MyRunnable2 myrunnable2 new MyRunnable2();//创建生产皮包的线程Thread t1 new Thread(myrunnable1);Thread t2 new Thread(myrunnable1);//创建生产皮鞋的线程Thread t3 new Thread(myrunnable2);//启动线程t1.start();t2.start();t3.start();}
}
//描述生产皮包的任务
class MyRunnable1 implements Runnable{Overridepublic void run() {System.out.println(生产皮包金币1);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
//描述生产皮鞋的任务
class MyRunnable2 implements Runnable{Overridepublic void run() {System.out.println(生产皮鞋金币1);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
} 3、多线程编程的意义 使用多线程编程主要是为了充分利用CPU资源提高程序的运行效率。 现在我们分别对两个变量进行10亿次的自增分别使用两种方式串行和并行。观察一下它们之间的时间差异。理论上来讲并行应该是串行时间的一半。接下来我们俩看一下实际上是不是这样~ 结论从上可以看出并行时间确实小于串行时间但并不是严格意义上的一半这主要是因为每创建一个线程都是要消耗时间和资源的。同样的我们将计算量变小一些可以看到反而串行时间小于并行时间。因此我们要知道并不是所有的场景下使用多线程第一可以提升效率具体是否需要使用多线程主要根据计算量来决定。 4、线程的类与常用方法 4.1 构造方法 Thread类是JVM用来管理线程的一个类。 4.2 Thread类的几个常见属性
1 获取线程名Thread.currentThread().getName(); 2是否后台线程isDaemon() isDaemon() 默认为false前台线程如果要创建后台线程则传入true手动设置为后台线程。其中在main方法结束之后线程会自动结束。 ❓ 问题前台线程与后台线程的区别 应用程度的主线程以及使用new Thread方式构造的线程都默认是前台线程。通过BeginXXX方法运行的线程都是后台线程托管线程池中的线程都是后台线程。 前台线程与后台线程的主要区别就是进程会等待所有的前台线程完成后再结束工作但是如果只剩下后台线程则会直接结束工作。如果程序定义了一个不会完成的前台线程主程序不会正常结束。 比如银行转账的业务一定要用前台线程统计微信步数这种处理容错率比较高的业务或者辅助功能等就可以使用后台线程。 后台线程演示 public static void main(String[] args) throws InterruptedException {Thread thread new Thread(()-{while (true){System.out.println(hello thread);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//第一次在线程启动之前 设置线程为后台,观察线程的存活状态thread.setDaemon(true);System.out.println(在线程start之前设置为后台线程此时的存活状态是 thread.isAlive());//启动线程thread.start();//此时休眠是因为start之后系统要去创建PCB给系统一点时间能够创建成功Thread.sleep(2000);//第二次在start线程之后观察线程的存活状态System.out.println(在线程start之后观察线程的存活状态thread.isAlive());System.out.println(main线程执行完成);System.out.println(此时线程是否存活:thread.isAlive());} 3是否存活 isAlive()指的是系统的线程PCB是否存活并不是说我们new出来的Thread对象。
4是否被中断停止或者终止当前现成的任务 方式1通过设置中断标识:主要注意代码中的while(!isQuit) 和 isQuit true这两点。 方式2通过调用Thread类提供的interruped()方法来中断线程。isInterrupted()默认为false不中断。 注意看下面的写法有两处改动但是会抛出异常。 这主要是因为上面的两处改动都针对的是runnable状态中的异常处理所以我们还要对 Sleep状态做处理在catch中处理有三种方式 5等待一个线程 join()等待线程结束
6获取当前线程引用Thread thread Thread.currentThread();
7start()方法 ❓ 问题start()方法与run()方法的区别面试题 start()申请一个真正的系统线程 run() :定义线程要执行的任务。 直接调用run方法并不会去申请一个真正的系统线程(PCB)只是一个普通的方法调用是单独的调用对象的方法。调用start方法JVM会调用本地方法去系统中真正的申请一个线程(PCB),并执行run方法中 的逻辑。 5、线程的状态
注意线程的状态指的是Thread对象有自己的生命周期的状态并不是PCB的状态。主要有以下几种 1、NEW java对象创建好了但是还没有调用start方法创建PCB。 2、RUNNABLE : 就绪和运行状态。可以随时调度到CPU中执行。 3、TERMINATED: 线程任务执行完成PCB在操作系统中已经销毁但是java对象还存在。 4、TIMED_WATING 等待一段时间这个时间是有时间限制的等待过时不候)。比如sleep()常见的访问超时。 5、WAITING没有时间限制的等待。 6、BLOCK等待锁的时候进入的阻塞状态。 最后知道 在系统中针对线程的两个调度是用两个队列来实现的。准备执行的线程在就绪队列中随时等待CPU调度。当调用sleep,wait,join方法时就会被移动到阻塞队列中。当休眠时间到了之后就会被移动回就绪队列重新参与CPU调度。