Java教程

java基础:线程(一)

本文主要是介绍java基础:线程(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

线程

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基础:线程(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!