首页 > 其他分享 >10.线程

10.线程

时间:2024-04-14 23:55:06浏览次数:21  
标签:10 run Thread 线程 new 方法 CPU

第十章【线程】

一、进程和线程

1、进程:

代表了内存中正在运行的应用程序,计算机中的资源(cpu 内存 磁盘 网络等),会按照需求分配给每个进程,从而这个进程对应的应用程序就可以使用这些资源了。进程就是在系统中运行一个应用程序的基本单位。

2、线程:

是进程中的一个代码执行单元,负责当前进程中代码程序的执行,一个进程中有一个或多个线程。当一个进程中启动了多个线程去分别执行代码的时候,这个程序就是多线程程序。

image-20210303143648761

​ 通过JDK自带的jconsole工具,可以检测到当前运行Hello这个类的时候,JVM的运行情况,包含内存的使用、线程的运行状态、类的加载等信息

二、并发和并行

1、并发:

是指在一个时间段内,俩个或多个线程,使用一个CPU,进行交替运行。

2、并行:

是指在同一时刻,俩个或多个线程,各自使用一个CPU,同时进行运行。

三、时间片

1、概念

时间片,当前一个线程要使用CPU的时候,CPU会分配给这个线程一小段时间(毫秒级别),这段时间就叫做时间片,也就是该线程允许使用CPU运行的时间,在这个期间,线程拥有CPU的使用权。

2、调度

当俩个或多个线程使用一个CPU来运行代码的时候,在操作系统的内核中,就会有相应的算法来控制线程获取CPU时间片的方式,从而使得这些线程可以按照某种顺序来使用cpu运行代码,这种情况被称为线程调用。

常见的调度方式:

  • 时间片轮转

    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    系统会让优先级高的线程优先使用 CPU(提高抢占到的概率),但是如果线程的优先级相同,那么会随机选择一个线程获取当前CPU的时间片。 【JVM中的线程,使用的为抢占式调度】

四、线程的相关操作

1、main线程

使用java命令来运行一个类的时候,首先会启动JVM(进程),JVM会在创建一个名字叫做main的线程,来执行类中的程序入口(main方法),我们写在main方法中的代码,其实都是由名字叫做main的线程去执行的。

Thread.currentThread(); 可以写在任意方法中,返回就是执行这个方法的线程对象

image-20210303144713963

2、线程的创建和启动
  1. Java中通过继承Thread类来创建并启动一个新的线程
  • 定义 Thread 类的子类(可以是匿名内部类),并重写 Thread 类中的 run 方法, run 方法中的代码就是线程的执行任务

  • 创建 Thread 子类的对象,这个对象就代表了一个要独立运行的新线程

  • 调用线程对象的 start 方法来启动该线程

    class MyThread extends Thread{
        @Override 
        public void run() {
            for (int i = 0; i < 10; i++) { 
                System.out.println("hello world"); 
                try {
                    Thread.sleep(1000); 
                } catch (InterruptedException e) { 
                    e.printStackTrace(); 
                } 
            } 
        } 
    }
    
    public static void main(String[] args) { 
    	Thread t = new MyThread(); 
        t.start();
    }
    
    匿名内部类方法
    public static void main(String[] args) { 
        Thread t = new MyThread(){ 
            @Override 
            public void run() {
                for (int i = 0; i < 10; i++) { 
                    System.out.println("hello world"); 
                    try {
                        Thread.sleep(1000); 
                    } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                    } 
                } 
            } 
        };
        t.start();
    }
    
  1. 利用Runnable接口来完成线程创建和启动【推荐】

    public class Thread implements Runnable { 
        private Runnable target;
        public Thread() {
            //... 
        }
        public Thread(Runnable target) { 
            this.target = target; 
            //.. 
        }
        @Override 
        public void run() { 
            if (target != null) { 
                target.run(); 
            } 
        } 
    }
    
    使用 Runnable 接口的匿名内部类,来指定线程的执行任务(重写接口中的run方法)
    public static void main(String[] args) { 
        Runnable run = new Runnable() { 
            @Override 
            public void run() {
    			for (int i = 0; i < 10; i++) { 
                    System.out.println("hello world"); 
                    try {
                        Thread.sleep(1000); 
                    } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                    } 
                } 
            } 
        };
    	Thread t = new Thread(run); 
        t.start(); 
    }
    
3、线程的名字

​ 通过Thread类中的currentThread方法,可以获取当前线程的对象,然后调用线程对象的getName方法,可以获取当前线程的名字。String name = Thread.currentThread().getName();

​ 主线程中,创建出的线程,它们的都会有一个默认的名字:Thread-" + nextThreadNum()

​ 可以创建线程对象的时候,给它设置一个指定的名字:

//方法一:
Thread t = new Thread("t线程"); 
//方法二:
Thread t = new Thread(new Runnable(){ 
    @Override
    public void run(){ 
        //执行任务 
    } 
},"t线程"); 
//方法三:
Thread t = new Thread(); 
t.setName("t线程");
4、线程的分类
  • 前台线程,又叫做执行线程、用户线程
  • 后台线程,又叫做守护线程、精灵线程

前台线程:

​ 这种线程专门用来执行用户编写的代码,地位比较高,JVM是否会停止运行,就是要看当前是否还有前台线程没有执行完,如果还剩下任意一个前台线程没有“死亡”,那么JVM就不能停止!

后台线程:

​ 这种线程是用来给前台线程服务的,给前台线程提供一个良好的运行环境,地位比较低,JVM是否停止运行,根本不关心后台线程的运行情况和状态。

在主线程中,创建出来的线程对象,默认就是前台线程,在它启动之前,我们还可以给它设置为后台线程:

public static void main(String[] args) { 
    Thread t = new Thread("t线程"){ 
        @Override 
        public void run() { 
            String name = Thread.currentThread().getName();
				for (int i = 0; i < 10; i++) { 
                    System.out.println(name+": hello "+i); 
                } 
        } 
    };
    //在启动线程之前,可以将其设置为后台线程,否则默认是前台线程 
    t.setDaemon(true); 
    t.start(); 
}
5、线程的优先级

线程的优先级使用int类型数字表示,最大是10,最小是1,默认的优先级是5。

最终设置线程优先级的方法,是一个native方法,并不是java语言实现的。

当俩个线程争夺CPU时间片的时候:

  • 优先级相同,获得CPU使用权的概率相同
  • 优先级不同,那么高优先级的线程有更高的概率获取到CPU的使用权
6、线程组

Java中使用java.lang.ThreadGroup类来表示线程组,它可以对一批线程进行管理,对线程组进行操作,同时也会对线程组里面的这一批线程操作。

	@Test
	public void testThreadGroup1() {
		/*
		 * JVM会构建一个默认的线程组,管理JVM构建的main线程组
		 */
		Thread currentThread = Thread.currentThread();
		ThreadGroup threadGroup = currentThread.getThreadGroup();
		System.out.println(threadGroup);
		//创建一个新线程,没有放到线程组
		Thread th = new Thread("myThread");
		System.out.println(th.getThreadGroup());
		
		//自己构建一个线程组
		ThreadGroup tg = new ThreadGroup("myThreadGroup");
		tg.setMaxPriority(9);
		Thread th1 = new Thread(tg,"线程2");
		System.out.println(th1.getThreadGroup());
	}	
@Test
	public void testThreadGroup2() {
		/*
		 * 线程放在线程组,可以查看和管理线程组中的线程
		 */
		Runnable run = new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		
		ThreadGroup group = new ThreadGroup("myThreadGroup");
		
		Thread t1 = new Thread(group,run,"线程1");
		Thread t2 = new Thread(group,run,"线程2");
		Thread t3 = new Thread(group,run,"线程3");
		
		t1.start();
		t2.start();
		t3.start();
		
		System.out.println("线程组中还在存活的线程个数为:"+group.activeCount());
		
		Thread[] arr = new Thread[group.activeCount()];
		System.out.println("arr数组中存放的线程个数为:"+group.enumerate(arr));
		System.out.println(Arrays.toString(arr));
	}
7、线程状态

java.lang.Thread.State枚举类型中(内部类形式),定义了线程的几种状态:

线程状态 名称 描述
NEW 新建 线程刚被创建,还没调用start方法,或者刚刚调用了start方法,调用start方法不一定"立即"改变线程状态,中间可能需要一些步骤才完成一个线程的启动。
RUNNABLE 可运行 start方法调用结束,线程由NEW变成RUNNABLE,线程存活着,并尝试抢占CPU资源,或者已经抢占到CPU资源正在运行,这俩种情况的状态都显示为RUNNABLE
BLOCKED 锁阻塞 线程A和线程B都要执行方法test,而且方法test被加了锁,线程A先拿到了锁去执行test方法,线程B这时候需要等待线程A把锁释放。这时候线程B就是处理BLOCKED
WAITING 无限期等待 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TIMED_WAITING 有限期等待 和WAITING状态类似,但是有一个时间期限,时间到了,自己也会主动醒来
TERMINATED 终止(死亡) run方法执行结束的线程处于这种状态。

线程在BLOCKED,WAITING,TIMED_WAITING这三种情况的阻塞下,都具备相同的特点:

  • 线程不执行代码

  • 线程也不参与CPU时间片的争夺

image-20210304094955171

注意:

  1. 刚创建好的线程对象,就是出于NEW的状态

  2. 线程启动后,会出于RUNNABLE状态,从就绪状态到运行状态,之间会经过多次反复的CPU执行权的争夺

    RUNNABLE状态包含俩种情况:

    • 就绪状态:此时这个线程没有运行,因为没有抢到CPU的执行权
    • 运行状态:此时这个线程正在运行,因为抢到CPU的执行权
  3. 线程执行了sleep(long million)方法后,会从RUNNABLE状态进入到TIMED_WAITING状态

    TIMED_WAITING状态阻塞结束后,线程会自动回到RUNNABLE状态

  4. 线程执行了join()方法后,会从RUNNABLE状态进入到WAITING状态,调用线程的interrupt()方法,可以从WAITING状态进入到RUNNABLE状态

    线程执行了join(long million)方法后,会从RUNNABLE状态进入到TIMED_WAITING状态

  5. interrupt()方法是通过改变线程对象中的一个标识的值(true|false),来达到打断阻塞状态的效果

    查看就绪/运行状态下的线程对象中“打断标识”值的俩个方法:

    1. isInterrupted()方法

      调用了interrupt()方法后,线程中的“打断标识”值设置为true,可以通过线程对象中的isInterrupted方法返回这个标识的值,并且不会修改这个值,所以输出显示的一直是ture。

    2. interrupted()方法

      调用了interrupt()方法后,线程中的“打断标识”值设置为true,可以通过线程对象中的interrupted方法,第一次返回true之后,后面在调用方法查看这个“打断标识”值,都是false。interrupted返回true后,会直接把这个值给清除掉。

五、线程安全

​ 当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

六、线程同步

Java中实现线程同步的方式,是给需要同步的代码进行synchronized关键字加锁。

七、Synchronized

使用格式为:

synchronized (锁对象){ 
    //操作共享变量的代码,这些代码需要线程同步,否则会有线程安全问题 
    //... 
}

image-20210305142202132

八、wait和notify

Object类中有三个方法: wait()notify()notifyAll()

三个核心点:

  • 任何对象中都一定有这三个方法

  • 只有对象作为锁对象的时候,才可以调用

  • 只有在同步的代码块中,才可以调用

​ 当前调用锁对象的wait方法后,当前线程释放锁,然后进入到阻塞状态,并且等待其他线程先唤醒自己,如果没有其他线程唤醒自己,那么就一直等着。所以现在的情况是,俩个线程t1和t2都是在处于阻塞状态,等待别人唤醒自己,所以程序不运行了,但是也没结束!

​ 如果有线程调用了notify()方法进行了唤醒,或者interrupt方法进行了打断,那么这个线程就会从等待池进入到锁池,而进入到锁池的线程,会时刻关注锁对象是否可用,一旦可用,这个线程就会立刻自动恢复到RUNNABLE状态。

​ 锁对象.notify(),该方法可以在等待池中,随机唤醒一个等待指定锁对象的线程,使得这个线程进入到锁池中,而进入到锁池的线程, 一旦发现锁可用,就可以自动恢复到RUNNABLE状态了

​ 锁对象.notifyAll(),该方法可以在等待池中,唤醒所有等待指定锁对象的线程,使得这个线程进入到锁池中,而进入到锁池的线程, 一旦发现锁可用,就可以自动恢复到RUNNABLE状态了

image-20210305142851687

由图可知,TIMED_WAITINGWAITINGBLOCKED都属于线程阻塞,他们共同的特点是就是线程不执行代码,也不参与CPU的争夺,除此之外,它们还有各自的特点:(重要)

  • 阻塞1,线程运行时,调用sleep或者join方法后,进入这种阻塞,该阻塞状态可以恢复到RUNNABLE状态,条件是线程被打断了、或者指定的时间到了,或者join的线程结束了

  • 阻塞2,线程运行时,发现锁不可用后,进入这种阻塞,该阻塞状态可以恢复到RUNNABLE状态,条件是线程需要争夺的锁对象变为可用了(别的线程把锁释放了)

  • 阻塞3,线程运行时,调用了wait方法后,线程先释放锁后,再进入这种阻塞,该阻塞状态可以恢复到BLOCKED状态(也就是阻塞2的情况),条件是线程被打断了、或者是被别的线程唤醒了(notify方法)

九、死锁

在程序中要尽量避免出现死锁情况,一旦发生那么只能手动停止JVM的运行,然后查找并修改产生死锁的问题代码

​ 简单的描述死锁就是:俩个线程t1和t2,t1拿着t2需要等待的锁不释放,而t2又拿着t1需要等待的锁不释放,俩个线程就这样一直僵持下去。

标签:10,run,Thread,线程,new,方法,CPU
From: https://www.cnblogs.com/pengdali/p/18134948

相关文章

  • SQL Prompt 10安装与破解方法
    SQLPrompt10安装与破解方法Cooper写文章只是为了日常记录 4人赞同了该文章SQLPrompt10是一款SQLServer数据库开发工具,通过自动完成、代码补全等功能提高开发效率。但是,该软件需要付费购买,对于一些学生和个人开发者来说,价格较为昂贵。本文将......
  • 1014 福尔摩斯的约会
    我感觉是这题出的有问题,第二个只说了字母,并没有说第二个大写字母...为啥就得从第一个大写字母后面开始检索呢。#include<bits/stdc++.h>usingnamespacestd;map<int,string>mp={{1,"MON"},{2,"TUE"},{3,"WED"},{4,"THU"},{5,"FRI"},{6,"SAT"}......
  • games101_Homework7
    实现完整的PathTracing算法需要修改这一个函数:•castRay(constRayray,intdepth)inScene.cpp:在其中实现PathTracing算法//ImplementationofPathTracingVector3fScene::castRay(constRay&ray,intdepth)const{//TODOImplementPathTracing......
  • games101_Homework6
    实现Ray-BoundingVolume求交与BVH查找在本次编程练习中,你需要实现以下函数:•IntersectP(constRay&ray,constVector3f&invDir,conststd::array<int,3="">&dirIsNeg)intheBounds3.hpp:这个函数的作用是判断包围盒BoundingBox与光线是否相交,你需要按照课程介......
  • 闫忠奥202383310064
    实验1#include<stdio.h>#include<stdlib.h>#include<time.h>#defineN5intmain(){ intnumber; inti; srand(time(0)); for(i=0;i<N;++i) { number=rand()%65+1; printf("20238331%04d\n",number); } return0;}......
  • 1013 数素数
    #include<bits/stdc++.h>usingnamespacestd;boolisPrime(intx){ if(x==1)returnfalse; for(inti=2;i<=sqrt(x);i++){ if(x%i==0)returnfalse; } returntrue;}intmain(){ inta,b; cin>>a>>b; //第5个素数和第27个素数 intcount=0; ......
  • P10289 [GESP样题 八级] 小杨的旅游 题解
    题目简述给定一棵树,节点之间的距离为$1$,树上有$k$个传送门,可以从一个传送门瞬间传送到另一个传送门,有$q$此询问,问$u$和$v$之间的最短距离是多少。题目分析首先考虑没有传送门,我们可以直接求两个点的LCA,再用高度容斥计算即可。如果有传送门,那么有用与不用两种选择,如......
  • 解决编译redis报错zmalloc.h:50:10: fatal error: jemalloc/jemalloc.h: No such file
    编译redis时报错:zmalloc.h:50:10:fatalerror:jemalloc/jemalloc.h:Nosuchfileordirectory,执行:#sudomakeMALLOC=libc1即可成功 需要先运行“makedistclean”,它设置删除所有早期的编译文件,然后运行“make”,这样就得到了redis服务器程序的新编译。执行后成功编......
  • 通用的上传下载(线程)
    packagecom.duxiang.backgroundmanagement.controller;importcn.hutool.core.io.FileUtil;importcn.hutool.core.util.StrUtil;importcom.duxiang.backgroundmanagement.common.Result;importorg.springframework.web.bind.annotation.*;importorg.springframework.web.......
  • 线程
    什么是线程:进程里面的一条执行流程为什么要引入线程这就不得说说进程的缺点了:进程间的切换,会导致TLB、CPU的Cache失效进程之间是隔离的,进程间的通信需要打破隔离的壁障而相较于进程而言,线程的创建和销毁是轻量级的。同一进程的线程之间的切换,不会导致TLB失效、也不......