多线程
- 一、线程
- 1.什么是线程
- 2.为什么要有线程
- 3.==进程与线程的关系与区别==
- 4.Thread类及创建方法
- 4.1创建线程
- 4.2线程构造方法与属性
- 4.3.启动一个线程start()方法
- 4.4.中断一个线程isInterrupted()
- 4.5.等待一个线程join()
- 4.6.休眠当前线程sleep()
- 5.线程状态
································································
一、线程
1.什么是线程
一个线程是一个执行流,每个线程都可以按顺序执行自己的代码,多个线程“同时”执行自己的代码。
2.为什么要有线程
创建销毁进程的开销大,尤其是频繁操作,引入线程,创建销毁开销更加小更加快
3.进程与线程的关系与区别
1.进程包含线程,每个进程都有一个或者多个线程
2.进程与进程之间不共享资源,进程是操作系统资源分配的基本单位,而线程与线程之间共享资源,所以当一个进程崩溃不会影响其他进程,但是一个线程挂了可能其他线程(整个进程)都崩溃(这就是线程安全相关问题及时捕获可能不会崩溃)
3.线程是CPU上调度执行的最小单位,一个进程可以包含多个线程,同一个进程内的多个线程,共享进程资源,在CPU上调度执行。
4.一个进程中的线程共用一个文件描述符表和内存指针,但是每个线程都有自己的调度相关的(状态、优先级、上下文、记账信息),一个进程并不是越多越好,过多线程产生的调度开销也很大,会拖慢程序的性能
4.Thread类及创建方法
4.1创建线程
对于线程,操作系统提供一些api(应用程序编程接口)给程序员使用,操作系统提供的原生线程api是c语言的,而且不同操作系统的线程api不同,java对上述内容进行统一封装,如Thread类(标准库中提供的类)。
方法1:继承Thread类创建一个线程类
classMythreadextendsThread{@Overridepublicvoidrun(){while(true){System.out.println("hello thread");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}}}publicclassdemo1{publicstaticvoidmain(String[]args){Mythreadmythread=newMythread();//start方法创建一个线程,多一个执行流,像是多一个人干活mythread.start();while(true){try{//sleep静态方法让当前线程暂时放弃CPU,过1000毫秒后继续执行Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println("hello main");}}}运行结果显示有时候先输出main有时先输出thread,这是因为多个线程的调度顺序是随机的,无法预测(唯一可以做的就是设置优先级,但是对于操作系统也不一定严格执行。
方法2:实现Runnable接口
classMyRunnableimplementsRunnable{@Overridepublicvoidrun(){while(true){System.out.println("hello thread");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}}}publicclassdemo2{publicstaticvoidmain(String[]args){MyRunnablerunnable=newMyRunnable();Threadthread=newThread(runnable);thread.start();while(true){System.out.println("hello main");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}}}方法3:使用内部类操作方法1
publicclassdemo3{publicstaticvoidmain(String[]args){Threadthread=newThread(){@Overridepublicvoidrun(){while(true){System.out.println("hello thread");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}}};thread.start();while(true){System.out.println("hello main");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}}}对于匿名内部类:
Thread thread=new Thread(){};
1.创建一个Thread的子类,子类匿名
2.{}中写子类的属性,方法
3.创建匿名内部类的实例,并且把实例赋值给thread
方法4:使用内部类操作方法2
publicclassdemo4{publicstaticvoidmain(String[]args){Runnablerunnable=newRunnable(){@Overridepublicvoidrun(){while(true){System.out.println("hello thread");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}}};Threadt=newThread(runnable);t.start();while(true){System.out.println("hello main"+"");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}}}方法5:使用lambda表达式创建Runnable子类对象
publicclassdemo5{publicstaticvoidmain(String[]args){Threadthread=newThread(()->{while(true){System.out.println("hello thread");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}},"t1");thread.start();while(true){System.out.println("hello main");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}}}lambda(“匿名函数”)(主要需要方法但必须依托类存在使用“函数式接口”简化代码)
()-> 创建一个匿名的函数式接口的子类,并且创建出对应实例重写方法
- lambda访问外部变量的限制
如果把在lambda中使用的变量,定义成局部变量是否可以?
如果要把变量定义成局部变量,则这个变量不能修改,或者直接设置成final。这是由于变量捕获,因为lambda是回调函数,系统创建线程后才会执行,可能当main主线程执行完局部变量都销毁了,而lambda还需要使用,所以java会把变量拷贝一份到lambda里面,这样即使外面变量销毁也不影响lambda里面使用,而拷贝也就意味着变量不能进行修改。如果这个局部变量是个引用类型,那么这个引用本身不可以修改,不能指向其它对象,而对象本体是可以修改的,因为对象本体是JVM垃圾回收机制管理,不会随着main函数结束而销毁。
如果是成员变量呢?
将变量改为成员变量,就不会报错,因为此时不再是“变量捕获”语法,而是“内部类访问外部类的成员”语法。
lambda本质是一个函数式接口,相当于一个内部类,isFinished就是外部类的成员,内部类本身就可以访问外部类的成员,成员变量生命周期是由垃圾回收GC管理的,所以lambda不用担心销毁,就不必拷贝,就可以修改了。
4.2线程构造方法与属性
Java Thread 常见构造方法
| 构造方法签名 | 方法说明 | 核心参数解释 |
|---|---|---|
Thread()(方法1) | 创建一个新的线程对象,线程名称为默认格式(如Thread-0),无目标任务,需重写run()方法 | 无参数 |
Thread(Runnable target)(方法2) | 创建一个新线程,将Runnable接口的实现类作为线程执行的目标任务 | target:实现了Runnable接口的对象,线程启动后会执行该对象的run()方法 |
Thread(String name) | 创建指定名称的线程对象,便于调试和日志排查,无目标任务 | name:自定义的线程名称(如"业务处理线程-1") |
Thread(Runnable target, String name)(方法5) | 创建指定目标任务且指定名称的线程(最常用的构造方法之一) | target:线程执行的任务;name:线程自定义名称 |
Thread(ThreadGroup group, Runnable target)(了解) | 将线程归属到指定的线程组,并指定执行任务 | group:线程所属的ThreadGroup(用于批量管理线程);target:执行任务 |
常见属性
| 属性(获取方法) | 数据类型 | 属性说明 |
|---|---|---|
getId() | long | 获取线程的唯一ID |
getName()/setName(String name) | String | 获取/设置线程名称 |
getPriority()/setPriority(int newPriority) | int | 获取/设置线程优先级 |
getState() | Thread.State | 获取线程状态 |
isDaemon()/setDaemon(boolean on) | boolean | 判断/设置是否为守护线程 |
isAlive() | boolean | 判断线程是否存活 |
isInterrupted() | boolean | 判断线程是否被中断 |
interrupted()(静态方法) | boolean | 判断当前线程是否被中断 |
重点属性
1)后台线程
|isDaemon()/setDaemon(boolean on)|boolean|
判断/设置是否为守护线程(后台进程)
前台进程例如我们自己创建的线程包括main主线程,但我们可以通过setDaemon方法将它改为后台线程,后台进程例如JVM自带的具有特殊功能的线程,跟随整个进程持续执行。JVM会在一个进程的所有前台线程结束后才结束运行,而后台线程对结束无影响
2)是否存活(简单理解为run方法运行结束)
|isAlive()|boolean| 判断线程是否存活 |
java中创建的Thread对象与系统的线程一一对应,但是两者的生命周期并不相同,可能Thread对象依然存在但是对应的系统的线程已经销毁,
上面for循环执行完对象thread可能依然存在
4.3.启动一个线程start()方法
调用start方法,才真正创建一个线程。每个Thread对象,start是一次性的,进行调用系统的api,每次创建新线程,都得创建一个新的Thread对象。
run与start方法的区别:
作用功能不同:
- run方法的作用是描述线程具体要执行的任务;
- start方法的作用是真正的去申请系统线程
运行结果不同:
- run方法是一个类中的普通方法,主动调用和调用普通方法一样,会顺序执行一次;
- start调用方法后, start方法内部会调用Java 本地方法(封装了对系统底层的调用)真正的启动线程,并执行run方法中的代码,run 方法执行完成后线程进入销毁阶段。
4.4.中断一个线程isInterrupted()
中断即线程停止不会再恢复
lambda的定义在new Tread之前,所以不能直接在里面使用,但是java的Thread对象中提供了现成的变量,我们可以使用Thread.currentThread()静态方法,哪个线程中调用,获取的就是哪个线程的Thread引用
java的线程终止不是一个”强制性“措施
运行下面的程序,结果发现虽然写了终止操作,但是仍然循环输出,是因为正常情况下interrupet方法把isinterrupted方法内部的标志位设置为true,由于上述代码把sleep唤醒了,把isinterrupted方法内部的标志位设置回false,因此循环继续执行。
当在catch中写throw new RuntimeException(e)会抛出异常不会继续执行,写break会直接终止。所以对于线程立即结束,等会结束,还是不结束直接忽视终止信号,程序员可以自行选择。
publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(()->{while(!Thread.currentThread().isInterrupted()){System.out.println("hello thread");try{Thread.sleep(1000);}catch(InterruptedExceptione){// throw new RuntimeException(e);//break;}}});thread.start();Thread.sleep(1000);thread.interrupt();//将sleep提前唤醒把isinterruptted标志位设置为false,所以catch中不写就会继续循环执行}4.5.等待一个线程join()
在主线程中调用t.jion就是让主线程等待t线程先结束,在join中提供参数,可以设置一个时间上限当超过这个参数t还没结束,主线程就不会继续等待。
4.6.休眠当前线程sleep()
代码执行到sleep()相当于让当前线程让出cpu的资源,后续时间到了的时候,需要操作系统内核,把这个线程重新调到cpu上,才能继续执行
5.线程状态
- NEW:安排工作,还未开始行动,new了Thread对象,还未start()
- TERMINATED:工作完成了 线程的run方法执行完毕或抛出未捕获的异常,内核中线程结束,但Thread对象还在
- RUNNABLE:工作中或者即将开始工作,线程在cpu上运行,或者随时可以去cpu上运行
- TIMED_WAITING有时间上限的阻塞,不参与cpu调度,thread.join(1000),Thread.sleep(1000)
- WAITING无时间上限阻塞,不参与cpu调度,thread.join()
publicstaticvoidmain(String[]args)throwsInterruptedException{Threadthread=newThread(()->{for(inti=0;i<4;i++){System.out.println("hello thread");try{Thread.sleep(1000);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}});Threadthread2=newThread(()->{try{Thread.sleep(500);thread.join();}catch(InterruptedExceptione){thrownewRuntimeException(e);}System.out.println("线程thread2开始执行");});System.out.println("thread2初始状态:"+thread2.getState());//NEW安排了工作还未行动thread.start();thread2.start();//设置一个专门监控thread2状态的线程newThread(()->{while(thread2.isAlive()){System.out.println("thread2状态:"+thread2.getState());try{Thread.sleep(100);}catch(InterruptedExceptione){thrownewRuntimeException(e);}}System.out.println("thread2最终状态:"+thread2.getState());}).start();}}