JavaSE基础(十 一 )---<线程>线程概述,创建线程,以及线程的方法,优先级,状态,用户线程,守护线程
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaSE基础(十 一 )---<线程>线程概述,创建线程,以及线程的方法,优先级,状态,用户线程,守护线程相关的知识,希望对你有一定的参考价值。
文章目录
1.线程知识概述
1.1什么是线程
程序(program) : 是为完成特定任务、用某种语言编写的一组指令的集合。即指静态的代码
进程((process) : 就是正在执行的程序,从Windows角度讲,进程是含有内存和资源并安置线程的地方.运行中的程序,加载到内存中执行
线程(thread) : 进程可进一步细化为线程,是一个进程内部的最小执行单元
1.2线程与进程的关系
- 一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程而独立运行;
- 每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序, java程序的入口main( )方法就是在主线程中被执行的。
- 在主线程中可以创建并启动其它的线程;
- 一个进程内的所有线程都会共享该进程的内存资源。(或者说是内存空间的资源)
1.3关于多线程
多线程是指程序(进程)中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务.即在一个进程内,同时拥有多个线程,可以去执行多个任务(都是独立的)
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机 、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序 中,这些独立运行的程序片段叫作“ 线程 ”(Thread),利用它编程的概念就叫作“多线程处理”
多线程的应用场景
(1)当程序需要同时执行两个或多个任务。
(2)程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
(3)需要一些后台运行的程序时。
多线程的优缺点
优点
- 可以同时执行多个任务,功能强大,可提高程序的响应.
- 提高CPU的利用率.CPU的执行是以线程为单位的.
CPU使用率其实就是你运行的程序占用的CPU资源,表示你的机器在某个时间点的运行程序的情况。使用率越高,说明你的机器在这个时间上运行了很多程序,反之较少。使用率的高低与你的CPU强弱有直接关系。现代分时多任务操作系统对 CPU 都是分时间片使用的:比如A进程占用10ms,然后B进程占用30ms,然后空闲60ms,再又是A进程占10ms,B进程占30ms,空闲60ms;如果在一段时间内都是如此,那么这段时间内的占用率为40%。CPU对线程的响应并不是连续的,通常会在一段时间后自动中断线程。未响应的线程增加,就会不断加大CPU的占用。cpu使用率高的原因有很多,但是一般都是由于病毒木马或开机启动项过多所致。高CPU使用率也可能表明应用程序的调整或设计不良。优化应用程序可以降低CPU的使用率。
- 改善程序结构,将复杂任务分为多个线程,独立运行.
缺点
- 线程也是程序的一部分,执行时线程需要占用内存,线程越多占用内存也越多,对于CPU的占用也高.
- 多线程需要协调和管理,所以需要CPU时间跟踪线程;
- 多个线程对于同一个共享的资源进行访问时,会出现各种不正常的情况,(解决办法:线程同步)
例子:
public class DXCDemo {
public static void main(String[] args) {
DXCDemo d = new DXCDemo();
//这里调用方法2;
d.method2("hello!");
System.out.println("main方法结束");
}
public void method1(String str){
System.out.println(str);
System.out.println("方法1结束");
}
public void method2(String str){
//方法2调用方法1
method1(str);
System.out.println("方法2结束");
}
}
输出
hello!
方法1结束
方法2结束
main方法结束
虽然看似在main方法中调用了两个方法,但这是按照单线程模式进行的.
2.创建线程,以及线程的方法,优先级,状态,分类
2.1创建线程
这里首先是学习两种创建线程的方式
- 继承Thread类
- 实现Runnable接口
方式1:继承Thread类
创建Demo01;继承了Thread类;要重写Thread类的run方法;将需要执行的任务写到run方法中.
//创建线程的方式1:继承Thread类
public class Demo01 extends Thread {
//需要重写run方法;将需要执行的任务写到run方法中;
@Override
public void run() {
//例如:写个for循环;
for (int i = 0; i <10000 ; i++) {
System.out.println("runner"+i);
}
}
}
测试类Test:
main方法实际上作为主线程;在测试类中创建线程并且启动线程;注意启动线程是使用Thread类中的start方法;而不是run方法.
//创建方式1的测试类;
public class Test {
//main方法就是一个主线程;
public static void main(String[] args) {
//在这里创建线程并且启动线程;
//创建线程;
Demo01 demo01=new Demo01();
//注意这里使用的是Thread类中的start方法;
demo01.start();
//如果使用的是run方法;仅仅是普通的调用run方法;而不是启动线程
//在main方法中写个for循环,作为主线程的任务;
for (int i = 0; i < 1000; i++) {
System.out.println("main:"+i);
}
}
}
执行时:
运行时发现是交替执行的,没有规律;这里也体现了线程的执行特点
线程是交替执行的.
每个线程都是独立的,是由CPU加载执行的;对于单核的CPU来说,CPU在一个时间点上只能执行处理一个线程;CPU的加载速度很快,是可以交替执行线程的.
方式2:实现Runnable接口
创建Demo01;实现Runnable接口,且重写run方法,将需要执行的任务写入run方法.
//创建线程方式2:实现Runnable接口
public class Demo01 implements Runnable {
//这里需要重写run方法;
@Override
public void run() {
//这里写个for循环作为任务
for (int i = 0; i < 10000; i++) {
System.out.println("run:"+i);
}
}
}
测试类Test:
注意在创建方式上有区别;需要创建Thread对象;将需要执行的任务添加到线程中.
//线程创建方式2的测试类
public class Test {
//main方法作为主线程
public static void main(String[] args) {
//注意;和方式一的创建方法有区别;
//这里只是创建了一个线程执行的任务;
Demo01 demo01=new Demo01();
//创建线程;并将线程中需要执行的任务添加到线程中;
Thread thread=new Thread(demo01);
//启动线程;使用start方法;
thread.start();
//主线程的任务;
for (int i = 0; i < 1000; i++) {
System.out.println("main:"+i);
}
}
}
运行:交替执行的.
继承继承Thread类和实现Runnable接口的区别
区别:
- 继承Thread类: 线程代码存放Thread子类run方法中。
- 实现Runnable接口:线程代码存在接口的子类的run方法。
使用实现Runnable接口的好处
- 避免了单继承的缺陷;
(在继承一个类后,不能再继承别的类;而实现接口后,不但可以进行继承,可以实现多个接口)
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
2.2关于Thread类
Thread类的构造方法
- (1) Thread( ) ; 创建一个新的线程
- (2)Thread(String name) ; 创建一个指定名称的线程
- (3)Thread(Runnable target) ; 利用Runnable对象创建一个线程,启动时将执行该对象的run方法.
- (4)Thread(Runnable target, String name)||利用Runnable对象创建一个线程,并指定该线程的名称.
关于创建线程时指定名称
对于实现Runnable接口的Demo01;
public class Demo01 implements Runnable {
//这里需要重写run方法;
@Override
public void run() {
//这里写个for循环作为任务
for (int i = 0; i < 1000; i++) {
//public static Thread currentThread();获取当前正在执行的线程引用;
//public final String getName();获取线程的名称
System.out.println(Thread.currentThread().getName()+i);
}
}
}
在测试类Test中,创建两个线程执行同一个任务:
//测试类;
//创建两个线程执行通一个任务;
public class Test {
public static void main(String[] args) {
//只是创建一个线程执行的任务;
Demo01 demo01=new Demo01();
//创建线程1号;
Thread thread1=new Thread(demo01,"线程1号");
//启动线程1;
thread1.start();
//创建线程2号;
Thread thread2=new Thread(demo01,"线程2号");
//启动线程2;
thread2.start();
}
}
效果:两个线程交替执行.
除了用构造方法对线程进行命名;还可以使用 public final void setName(String name)方法进行设置命名.
当没有对线程命名时,线程有默认的名字Thread-0… …
Thread类的常用方法
返回值 | 方法 |
---|---|
void | start( ) ;导致此线程开始执行(启动线程); Java虚拟机调用此线程的run方法。 |
static Thread | currentThread( ) ;返回对当前正在执行的线程对象的引用。 |
void | setName(String name) ;将此线程的名称更改为指定的名称. |
String getName( ) | 返回此线程的名称。 |
void | run( ); 如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回. (编写线程需要执行的任务代码) |
int | getPriority( ) ;返回此线程的优先级。 |
void | setPriority(int newPriority) 更改此线程的优先级 |
void | join( ) 等待这个线程死亡。 final void join()throws InterruptedException |
static void | sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 |
void | stop( ) 这种方法本质上是不安全的 使用Thread.stop停止线程可以解锁所有已锁定的监视器(由于未ThreadDeath ThreadDeath异常在堆栈中ThreadDeath的自然结果。 |
static void | yield( ) 线程主动让步(让出CPU的执行权). |
String | toString( ) 返回此线程的字符串表示,包括线程的名称,优先级和线程组。 |
2.3线程的优先级
假设有个单核的CPU;计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务;优先级较高的线程有更多获得CPU的机会,优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但也可以通过setPriority( int newPriority)和getPriority( )方法来设置或返回优先级;
关于优先级的调度
调度策略
- 时间片: 就是排队,先来的先执行;
- 抢占式:优先级越高的先执行,优先抢占CPU.
优先级的等级由1-10逐渐提高
在实际的使用中,由于线程交替执行的特点,优先级低的不一定是最后才会执行的
Thread类有如下3个静态常量来表示优先级
- MAX_PRIORITY:取值为10,表示最高优先级。
- MIN_PRIORITY:取值为1,表示最底优先级。
- NORM_PRIORITY:取值为5,表示默认的优先级。
2.4线程的状态
线程的声明周期:由创建到死亡.
新建,就绪,运行,阻塞,死亡,5种状态
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态.注意这时线程并不能执行
就绪:处于新建状态的线程被start( )启动后,进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没有获得CPU的执行权
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run( )方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态,阻塞状态结束后,又进入到就绪状态
死亡:线程完成了它的全部工作或者线程被提前强制性地中止
或者出现异常导致结束
在线程执行的任务中使用public static void yield( )来让线程主动让步(让出CPU的执行权),使得在线程在运行期到就绪状态,可能会出现刚回退到就绪状态,又再次获得CPU的执行权机会,再次进入运行状态.
代码:
//继承了Thread类的Demo01;
public class Demo01 extends Thread{
//在重写的run方法中写入需要执行的任务;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//如果循环到的数除以5余数为0;
//则线程主动让步;由运行状态到就绪状态;
//可能有种极端状况,退回到就绪状态后,又获得了CPU的执行权,再次进入运行状态;
if(i%5==0){
Thread.yield();
}
//这里currentThread方法获得正在执行的线程的引用;getName获取线程名;
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
测试类:
public class Test1 {
public static void main(String[] args) {
//这里建两个线程,来执行同一个任务;
Demo01 d1=new Demo01();//新建状态,并不能执行;
d1.setName("线程1号");//为线程设置名称;(注意:设置的内容要在启动之前);
d1.start();//启动线程,进入就绪状态;
Demo01 d2=new Demo01();
d2.setName("线程2号");
d2.start();
}
}
线程让步效果;当线程1号执行到5时,退回到就绪状态,让出位置给线程2号执行,线程2号到5时,退回就绪状态,让线程1号执行.
在线程执行时使用方法public static void sleep(long millis)throws InterruptedException;使线程由运行状态进入阻塞状态,停滞指定的毫秒值时间;然后再进入就绪状态,等待获得CPU的执行权再进入运行状态;注意需要处理异常
代码:
public class Demo01 extends Thread{
//在重写的run方法中写入需要执行的任务;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//使用sleep方法;使线程由运行状态进入阻塞状态,停滞指定的毫秒值时间;
//然后再进入就绪状态,等待获得CPU的执行权再进入运行状态;
//由于这里重写的run方法的上一级并不存在这个异常,所以这里不能throws声明异常或者throw抛出异常;
//注意:这里需要try catch处理异常;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这里currentThread方法获得正在执行的线程的引用;getName获取线程名;
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
执行时:大概是停滞2秒,执行一下;(实际上,由阻塞状态到就绪状态后,还需要等待获得CPU的执行权,才进入运行状态)
使用 public final void join( )throws InterruptedException;是被线程对象调用的;进行线程合并,调用join方法的线程执行完后,才开始执行其他线程.
测试类:
public class Test1 {
public static void main(String[] args) {
//这里建两个线程,来执行同一个任务;
Demo01 d1=new Demo01();//新建状态,并不能执行;
d1.setName("线程1号");//为线程设置名称;(注意:设置的内容要在启动之前);
d1.start();//启动线程,进入就绪状态;
//join方法;是被线程对象调用的;这里需要处理异常;
try {
//线程1执行结束后,才执行线程2;
d1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
Demo01 d2=new Demo01();
d2.setName("线程2号");
d2.start();
}
}
效果:线程1执行结束,线程2开始执行.
在线程执行时使用方法public final void stop( ),强制让线程销毁,进而到达死亡状态.
2.5线程的分类
Java中的线程分为两类:用户线程和守护线程
用户线程(user-level threads)指不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。这种线程甚至在象 DOS 这样的操作系统中也可实现,但线程的调度需要用户程序完成,这有些类似 Windows 3.x 的协作式多任务。
优点:1.线程位于用户空间(即不需要模式切换)。
2.完全控制线程调度器(例如:网站服务器)。
3.独立于操作系统(线程可以在不支持它们的操作系统上运行)。
4.运行时系统(run time system)可以切换用户空间中的本地阻塞线程(例如:等待另一个线程完成).
缺点:1.系统调度中,对一个线程的阻塞将会导致整个进程阻塞;(例如:当一个线程因 I/O 而处于等待状态时,整个进程就会被调度程序切换为等待状态,其他线程得不到运行的机会)
2.网站服务器中,一个页面的错误将导致整个进程阻塞。
3.非真正意义的线程并行(一个进程安排在单个CPU上)。
4.不存在时钟中断(例如,如果用户线程是非抢占式(non-preemptive)的,将无法被“进程调度”(schedulers)以round-robin的调度算法调用,因为round-robin调度算法中限制了cpu时间片)。
用户线程不需要额外的内核开支,并且用户态线程的实现方式可以被定制或修改以适应特殊应用的要求,但是;而内核线程则没有这个限制,有利于发挥多处理器的并发优势,但却占用了更多的系统开支。
守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
- 任何一个守护线程都是整个JVM中所有非守护线程的保姆;(当把一个线程设置为守护线程时,守护线程需要等待所有非守护线程执行完毕后,才会销毁)
- 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;
- 只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
- 守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器).
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了.
注意:
- 设置线程为守护线程必须在启动线程之前,否则会跑出一个IllegalThreadStateException异常。
- 在守护线程中产生的新线程也是属于守护线程的。
- 守护线程建议不去访问固有资源,例如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
使用setDaemon(true) 将一个线程设置为守护线程.必须在线程启动之前.
模拟演示:
Demo01;将要设置为守护线程;
public class Demo01 extends Thread {
//重写run方法;
@Override
public void run() {
//这里用while死循环作为执行的任务
while(true){
//使用sleep方法,使得1秒停滞一次;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("定义守护线程--一直存在");
}
}
}
Demo02:模拟用户线程:
public class Demo02 extends Thread {
//重写run方法
@Override
public void run() {
//这里使用for循环;
for (int i = 0; i < 25; i++) {
//使用sleep方法,使得1秒停滞一次;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("用户线程:"+i);
}
}
}
测试类Test:
public class Test {
public static void main(String[] args) {
//创建线程1;
Demo01 demo01=new Demo01();
//设置为demo01为守护线程;
demo01.setDaemon(true);
//启动守护线程demo01;
demo01.start();
//创建线程2;即用户线程;
Demo02 demo02=new Demo02();
//启动用户线程demo02;
demo02.start();
}
}
效果:当用户线程执行结束后,守护线程才会准备销毁关闭
以上是关于JavaSE基础(十 一 )---<线程>线程概述,创建线程,以及线程的方法,优先级,状态,用户线程,守护线程的主要内容,如果未能解决你的问题,请参考以下文章
JavaSE基础(十 二 )---<GUI>GUI概述,Swing,容器组件,窗口,面板,布局管理器