ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java基础:线程(一)

2021-10-19 15:05:50  阅读:113  来源: 互联网

标签:java Thread void 基础 线程 run main public


线程

1、进程与线程

进程

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

线程

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

进程与线程之间的关系图

在这里插入图片描述

2、并发与并行

1、程序的并发执行是指,在一个时间段内,两个或多个线程,使用一个CPU进行交替执行
2、程序的并行执行是指,在同一时刻,两个或多个线程各自使用一个CPU同时进行运行

3、时间片

概述

1、线程要使用一个CPU时,CPU会分配给这个线程一小段时间(毫秒级别),这个一小段时间叫做时间片,在这段时间内该线程有CPU的使用权。
2、一个时间片结束,而线程还未运行完,那这个时候该线程就要停止运行,交出CPU的使用权,然后等待下一个CPU时间片的分配
3、宏观上一小段时间内两个线程看似是在同时运行代码,其实在微观上看这两个线程是在使用同一个CPU,他们是交替执行的,只是这个交替执行的时间片很小,交替速度快,给人一种好像两个线程同时在运行的感觉。

调度

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

常见调度方式

  1. 时间片调度
    所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
  2. 抢占式调度
    系统让优先级高的线程优先使用CPU,优先级一样时,则随机选择一个线程获取当前CPU的时间片

JVM中的线程使用的是抢占式调度

具体表现看下面代码

public class Demo01 {
	public static void main(String[] args) {
		Thread t1 = new Thread("线程1") {

			@Override
			public void run() {
				for(int i = 0; i<100; i++) {
					System.out.println("hello");
				}
			}
		};
		Thread t2 = new Thread("线程2") {
			
			@Override
			public void run() {
				for(int i = 0; i<100; i++) {
					System.out.println("java");
				}
			}
		};
		
		//启动线程
		t1.start();
		t2.start();
	}
}

程t1中循环输出100次hello,线程t2中循环输出100次world,启动t1和t2后,它们就开始公平的竞争(默认情况下优先级相等),抢占CPU的时间片(使用权),抢到之后就可以使用cpu运行代码了,所以可以看到最后的运行结果,hello和world是交替运行的,但是每次的运行结果都会有所不同。

4、关于main线程

使用java命令来运行一个类的时候,首先会启动JVM(进程),JVM会在创建一个名字叫做main的线程,来执行类中的程序入口(main方法)
如下:

public class Test {    
	public static void main(String[] args) {        
	//获取执行当前方法的线程对象        
	Thread currentThread = Thread.currentThread();        
	System.out.println("执行当前方法的线程名字为:"+currentThread.getName());   
	}
}

//运行结果:执行当前方法的线程名字为:main

上面代码执行java命令的过程是:
在这里插入图片描述

  1. 使用java命令运行Test类,会先启动JVM
  2. 应用类加载器通过CLASSPATH环境变量配置的路径,找到Test.class文件,并加载到方法区。注意,这里会同时生产一个Class类型对象,来代表这个Test类型,并且会优先处理类中的静态代码(静态属性、静态方法、静态代码块)
  3. JVM创建并启动一个名字叫做main的线程
  4. main线程将Test中的main方法加载到栈区中
  5. 在栈里面,main线程就可以一行行的执行方法中的代码了
  6. 如果在执行代码中,遇到了方法调用,那么线程会继续把被调用的方法,加载到栈中(压栈操作),然后执行栈顶这个最新添加进来的方法,栈顶方法执行完,就释放(出栈操作),然后在进行执行当前最新的栈顶方法(之前我们画过栈里面的方法调用图,例如在异常的学习过程中)
  7. 代码执行过程输出执行结果
  8. 当前是单线程程序,main线程结束了,JVM就停止了,如果是多线程程序,那么JVM要等所有线程都结束了才会停止

所以在main方法中的代码其实都是有名字叫做main的线程去执行的
Thread.currentThread();可以写在任意方法中,返回的是执行这个方法的线程对象

5、线程的创建与启动

定义
java.lang.Thread是java中的线程类,所有线程对象必须是Thread类获其子类的实例。每个线程的作用,就是完成我们给它指定的任务,实际上就是执行一段我们指定的代码。我们只需要在Thread类的子类中重写run方法,把执行的代码写入到run方法中即可,这就是线程的执行任务

创建并启动线程的步骤
1、定义Thread类的子类(可以是匿名内部类),重写Thread类中的run方法,run方法中的代码就是线程的执行任务
2、创建Thread子类对象,这个对象代表了要独立运行的新线程
3、调用线程对象的start方法来启动线程

例如:

public class Test {    
	public static void main(String[] args) {         		
	//2.创建线程类对象        
	Thread t = new MyThread();        
	//3.调用start方法启动线程        
	t.start();   
	}
}
	
	//1.子类继承父类Thread,并重写run方法(指定线程的执行任务)
	class MyThread extends Thread{    	
		@Override    
		public void run() {        	
		for (int i = 0; i < 10; i++) {            		
			System.out.println("hello world");  
    		try {                
   			//可以让当前执行代码的线程睡眠1000毫秒                	
  		  	Thread.sleep(1000);           
   			} catch (InterruptedException e) {                	
			e.printStackTrace();           
   		 	}       
   		}  
 	}
 }

或者使用匿名内部类的形式

public class Test{
	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();   
	}
}

6、Runnable接口

给一个线程对象指定要执行的任务,除了继承Thread类后重写run方法之外,还可以利于Runnable接口来完成线程任务的指定。
(1)java.lang.Runnable接口中只有一个抽象方法run()

punlic interface Runnable{
	public abstract void run();
}

(2)Thread类其实也是Runnable接口的实现类
代码大致结构如下

public class Thread implements Runnable {    
	/* What will be run. */    
	private Runnable target;        
	public Thread() {
	//...   
	}        
	public Thread(Runnable target) {        
		this.target = target;        
	//..   
	}        
	@Override    
	public void run() {        
		if (target != null) {            
			target.run();       
		}   
	}
}

可以看出子类重写的Thread中的run方法也是来自于Runnable接口的

(3)使用Runnable接口的匿名内部类,来指定线程的执行任务

public class Test{
	public static void main(String[] args) {         
	//Runnable接口的实现类中,重写了run方法,指定线程的执行任务        
	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();                   
				 }               
			 }          
	  }       
  };        
  
   //创建线程对象,将run对象传进       
	Thread t = new Thread(run);        
	t.start();   
		}
	}
}

(4)实现Runnable接口比继承Thread类所具有的优势:

  1. 可以把相同的一个执行任务(Runnable接口的实现),交给不同的线程对象去执行
  2. 可以避免java中的单继承的局限性。
  3. 线程和执行代码各自独立,实现代码解耦

7、获取和命名线程的名字

获取线程的名字

String name = Thread.currentThread().getName();

Thread.currentThread()方法获取当前线程对象,
getName()方法获取当前线程对象的名字
这里说的当前线程值的是当前方法中的线程

线程的默认名字

默认情况下,主线程中创建出的线程,它们都会有一个默认的名字

public Thread() {    
	init(null, null, "Thread-" + nextThreadNum(), 0);
}

“Thread-” + nextThreadNum() 就是在拼接出这个线程默认的名字,Thread-0 Thread-1 Thread-2等等

例如:

public class Test {    
	public static void main(String[] args) {       
	String name = Thread.currentThread().getName();        
	System.out.println("执行当前main方法的线程是:"+name);        
	Runnable run = new Runnable() {            	
		@Override            	
		public void run() {                
			String name = Thread.currentThread().getName();                
			System.out.println("执行当前run方法的线程是:"+name);           
	}       
};
	Thread t = new Thread(run);        
	t.start();   
		}
	}
//运行结果为:
//执行当前main方法的线程是:main
//执行当前run方法的线程是:Thread-0

给线程命名

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

	//方式1:创建对象时给线程命名
	Thread t = new Thread("t线程");
	//或者
	Thread t = new Thread(new Runnable(){    
		public void run(){        
		//执行任务   
		}
	},"t线程");

	//方式2:通过对象调用setName方法给线程命名
	Thread t = new Thread();
	t.setName("t线程");

8、线程分类

前台线程
又叫执行线程,用户线程。专门用来执行用户编写的代码,如果当前还有前台线程未执行完,则JVM虚拟机就不会停止运行。
执行程序入口的主线程(main线程)就是一个前台线程,还有在主线程中创建并启动的新线程默认情况下就是一个前台线程。

后台线程
又叫守护线程,精灵线程。专门用来给前台线程服务的,给前台线程提供一个良好的运行环境,JVM是否停止运行并不惯性后台线程的运行情况和状态。如垃圾回收器就是一个后台线程。

将线程设置为后台线程

setDaemon(true)方法

public class Test {    
	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();   
	}
}

9、线程优先级

表示线程优先级的源代码

public class Thread implements Runnable {    
	private int priority;        
	//线程的优先级使用int类型数字表示,最大是10,最小是1,默认的优先级是5。
	/**     * The minimum priority that a thread can have.     */    
	public final static int MIN_PRIORITY = 1;   
	/**     * The default priority that is assigned to a thread.     */    
	public final static int NORM_PRIORITY = 5;    
	/**     * The maximum priority that a thread can have.     */    
	public final static int MAX_PRIORITY = 10;        
	
	public final int getPriority() {        
		return priority;   
	}        
	public final void setPriority(int newPriority) {        
		ThreadGroup g;        
		checkAccess();        
		if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {            
			throw new IllegalArgumentException(); //优先级小于1或者大于10会报参数不合法的异常       
		}        
		
		if((g = getThreadGroup()) != null) {            
			if (newPriority > g.getMaxPriority()) {               
				 newPriority = g.getMaxPriority();    //比较优先级       
			 }            
	 		setPriority0(priority = newPriority);       
		}   
	}        
	//设置线程优先级的方法,是一个native方法,并不是java语言实现的
	private native void setPriority0(int newPriority);   
}

特点:

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

  1. 优先级相同,获得CPU使用权的概率相同
  2. 优先级不同,那么高优先级的线程有更高的概率获取到CPU的使用权

10、线程组

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

public class ThreadGroup{
   	public ThreadGroup(String name){   
   	//..   
   	} 
   	public ThreadGroup(ThreadGroup parent, String name){
   	        //..   
   	}
}

创建线程组的时候,需要指定该线程组的名字。也可以指定其父线程组,如果没有指定,那么这个新创建的线程组的父线程组就是当前线程组。只有在创建线程对象的时候,才能指定其所在的线程组,线程运行中途不能改变它所属的线程组

样例一:

public class Test {
	public static void main(String[] args) {
		//获取当前线程对象
		Thread currentThread = Thread.currentThread();
		//获取当前所属的线程组
		ThreadGroup currentThreadGroup = currentThread.getThreadGroup();
		System.out.println(currentThreadGroup);
	}
}

结果:

java.lang.ThreadGroup[name=main,maxpri=10]

样例二:

public class Test {    
	public static void main(String[] args) {        
		ThreadGroup group = new ThreadGroup("我的线程组");         
		//指定线程所属的线程组       
		 Thread t = new Thread(group,"t线程");        
		 ThreadGroup threadGroup = t.getThreadGroup();        
	 	System.out.println(threadGroup);   
	 }
}

java.lang.ThreadGroup[name=我的线程组,maxpri=10]

样例三:

import java.util.Arrays;

public class Test {
	public static void main(String[] args) {
		ThreadGroup group = new ThreadGroup("我的线程组");
		
		Runnable run = new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				try {
					Thread.sleep(10000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		};
	
		Thread t1 = new Thread(group,run,"t1线程");
		Thread t2 = new Thread(group,run,"t2线程");
		Thread t3 = new Thread(group,run,"t3线程");
		
		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));
	}
}

输出结果

线程组中还在存活的线程个数为:3
arr数组中存放的线程个数是:3
数组中的内容是:[Thread[t1线程,5,我的线程组], Thread[t2线程,5,我的线程组], Thread[t3线程,5,我的线程组]]

11、线程状态

编程状态名称描述
NEW新建线程刚被创建,
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 这三种都属于线程阻塞,只是触发的条件不同,以及从阻塞状态中恢复过来的条件也不同而已

getState()方法获取线程状态

注意

  1. 刚创建好的线程对象就处于NEW的状态
  2. 线程启动后,会处于RUNNABLE状态
  3. RUNNABLE可以细分为两种情况
    就绪状态:线程还未执行,因为没有抢到CPU执行权
    运行状态:线程正在运行中,抢到了执行权
  4. javaAPI中没有定义就绪状态和运行状态,而是把他们统一称为RUNNABLE状态(可运行状态)
  5. 当线程多次抢到CPU执行权,断断续续把run方法执行完成后,就变成了TERMINATED状态
  6. 死亡后的线程不能重新启动

标签:java,Thread,void,基础,线程,run,main,public
来源: https://blog.csdn.net/lalala_dxf/article/details/120826468

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有