线程

java中线程的介绍与用法

1.线程的创建和启动

java.lang.Thread 是java中的线程类,所有的线程对象都必须是Thread类或其子类的实例。
每个线程的作用,就是完成我们给它指定的任务,实际上就是执行一段我们指定的代码。我们只需要在Thread 类的子类中重写 run 方法,把执行的代码写入到run方法中即可,这就是线程的执行任务!
Java中通过继承Thread类来创建并启动一个新的线程的步骤如下:

  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();
			}
		}
	}
}

根据结果,可以看出,t线程启动开始执行的时候,每输出一次hello world都会睡眠1秒钟

在此过程中,main线程和t线程之间的关系是:
在这里插入图片描述

可以看出,main线程在执行main方法的过程中,创建并启动了t线程,并且t线程启动后,和main线程就没有关系了,这时候main线程和t线程都是自己独立的运行,并且他们俩个是要争夺CUP的时间片(使用权)的。

以上代码在内存中的情况:
在这里插入图片描述

注意1,之前所提到的栈区,又被称为方法调用栈,是线程专门执行方法中代码的地方,并且每一个线程,都有自己独立的栈空间,和别的线程相互不影响
注意2,最先启动的线程是主线程(main线程),因为它要执行程序的入口main方法,在主线程中,创建并且启动了t线程,启动之后main线程和t线程将各自独立运行,并且争夺CPU的时间片
注意3,线程启动之后(调用start方法),会开始争夺CPU的时间片,然后自动执行run方法,如果子类对象重写了,那么就调用到重写后的run方法
注意4,堆区是对所以线程共享的,每个线程中如果创建了对象,那么对象就会存放到堆区中
注意5,线程对象t被创建出来的时候,它还只是一个普通的对象,但是当调用了t.start()方法之后,线程对象t可以说才真正的“现出原形”:开辟了单独的栈空间,供线程t调用方法使用

2.Runnable接口

给一个线程对象指定要执行的任务,除了继承Thread类后重写run方法之外,还可以利于Runnable接口来完成线程任务的指定

java.lang.Runnable ,该接口中只有一个抽象方法 run

其实 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方法,这个run方法其实也来自于Runnable接口

通过以上的代码结构,可以知道,我们还可以直接创建 Thread 对象,在调用构造器的时候,传一个Runnable 接口的实现类对象进来,然后调用线程的对象 run 方法,那么默认就会调用到Runnable接口实现类重写的run方法!
例如,使用 Runnable 接口的匿名内部类,来指定线程的执行任务(重写接口中的run方法)

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();
					}
				}
			}
		};
		//创建线程对象,指定执行任务 
		Thread t = new Thread(run);
		t.start();
	}
}

可以看出,这种方式也可以完成之前相同的功能

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

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

3.线程的名字

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

注意,这里说的当前线程,指定是执行当前方法的线程,因为获取线程名字的代码肯定是写在某个方法中的,并且这个方法一定是由某个线程调用执行的,它们的关系如下:
在这里插入图片描述例如、

public class test1 {

	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

注意,一定要记得,start方法启动线程后,线程会自动执行run方法千万不要直接调用run方法,这样就不是启动线程执行任务,而是普通的方法调用,和调用sayHello没区别

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

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

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

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

Thread t = new Thread("t线程"); 
//或者 
Thread t = new Thread(new Runnable(){ 
	public void run(){ 
	//执行任务 
} 
},"t线程"); 

//或者 
Thread t = new Thread(); 
t.setName("t线程");

4.线程的分类

java中,线程可以分为:
前台线程,又叫做执行线程、用户线程
后台线程,又叫做守护线程、精灵线程

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

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();
	}
}

5.线程优先级

线程类Thread中,有一个属性,表示线程的优先级,代码结果大致为:

public class Thread implements Runnable {
	private int priority;
	/**
	 * 
	 * \* 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();
		}
		if ((g = getThreadGroup()) != null) {
			if (newPriority > g.getMaxPriority()) {
				newPriority = g.getMaxPriority();
			}
			setPriority0(priority = newPriority);
		}
	}
	private native void setPriority0(int newPriority);
}

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

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

当俩个线程争夺CPU时间片的时候:
优先级相同,获得CPU使用权的概率相同
优先级不同,那么高优先级的线程有更高的概率获取到CPU的使用权

例如,t1和t2线程各自运行10000次循环,看哪个线程先运行完

public class Test {
	public static void main(String[] args) {
		Thread t1 = new Thread("t1线程") {
			@Override
			public void run() {
				String name = Thread.currentThread().getName();
				for (int i = 0; i < 10000; i++) {
				}
				System.out.println(name + "线程执行完毕");
			}
		};
		Thread t2 = new Thread("t2线程") {
			@Override
			public void run() {
				String name = Thread.currentThread().getName();
				for (int i = 0; i < 10000; i++) {
				}
				System.out.println(name + "线程执行完毕");
			}
		};
		// t1.setPriority(Thread.MAX_PRIORITY); 
		// t2.setPriority(Thread.MIN_PRIORITY); 
		System.out.println("t1线程的优先级:" + t1.getPriority());
		System.out.println("t2线程的优先级:" + t2.getPriority());
		t1.start();
		t2.start();
	}
}

注意1,默认情况下,俩个线程的优先级都是5,那个俩个线程争夺到CPU的使用权的概率一样,那么基本上俩个线程都有相同的概率先执行完10000次循环
注意2,其实t1先稍微占了那么一点点的优势,因为毕竟在主线程的代码中,先启动了t1先,然后又启动了t2线程
注意3,设置t1和t2优先级的之后,在运行查看结果,会明显看到优先级高的线程,有更高的概率先执行完代码

6.线程组

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

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]

可以看出,当前线程组的名字为main,并且线程组中的线程最大优先级可以设置为10

例如,用户在主线程中创建的线程,属于默认线程组(名字叫"main"的线程组)

public class Test { 
	public static void main(String[] args) {
	Thread t = new Thread(); 
	ThreadGroup threadGroup =t.getThreadGroup(); 		
	System.out.println(threadGroup); 
	}
}

//运行结果: 
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] 

例如,

public class Test { 
	public static void main(String[] args) { 
		ThreadGroup group = new ThreadGroup("我的线程组"); 
		Runnable run = new Runnable() { 
		@Override 
		public void run() { 
		try {
			//让线程休眠一会,否则运行太快,死亡太快了 
			Thread.sleep(10000); 
		} catch (InterruptedException e) { 
		e.printStackTrace(); 
		} 
	} 
};
	Thread t1 = new Thread(group,run,"t1线程"); 
	Thread t2 = new Thread(group,run,"t2线程"); 
	Thread t3 = new Thread(group,run,"t3线程"); 
	//注意,启动后,三个线程都会进行休眠,等run方法运行完就“死亡”了 
	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("arr数组中的内容为:"+Arrays.toString(arr)); 
	} 
}

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

注意,只有在创建线程对象的时候,才能指定其所在的线程组,线程运行中途不能改变它所属的线程组

7.线程状态

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

public class Thread{ 
	/* Java thread status for tools, 
	 * initialized to indicate thread 'not yet started'  
	 */
	private volatile int threadStatus = 0;
	public enum State { 
		/**
		* Thread state for a thread which has not yet started. 
		*/ 
		NEW, 
		/**
		\* Thread state for a runnable thread. A thread in the 		runnable 
		\* state is executing in the Java virtual machine but it may 
		\* be waiting for other resources from the operating system 
		\* such as processor. 
		*/ 
		RUNNABLE, 
		/**
		 * Thread state for a thread blocked waiting for a monitor lock. 
		* A thread in the blocked state is waiting for a monitor lock 
		* to enter a synchronized block/method or 
		* reenter a synchronized block/method after calling 
		* {@link Object#wait() Object.wait}. 
		*/ 
		BLOCKED, 
		/**
		* Thread state for a waiting thread. 
		* A thread is in the waiting state due to calling one of the 
		* following methods: 
		* <ul> 
		* <li>{@link Object#wait() Object.wait} with no timeout</li> 
		* <li>{@link #join() Thread.join} with no timeout</li> 
		* <li>{@link LockSupport#park() LockSupport.park}</li> 
		* </ul> 
		*
		* <p>A thread in the waiting state is waiting for another thread to 
		* perform a particular action. 
		*
		* For example, a thread that has called <tt>Object.wait()</tt> 
		* on an object is waiting for another thread to call 
		* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on 
		* that object. A thread that has called 	<tt>Thread.join()</tt> 
		* is waiting for a specified thread to terminate. 
		*/ 
		WAITING, 
		/**
		* Thread state for a waiting thread with a specified waiting time. 
		* A thread is in the timed waiting state due to calling one of 
		* the following methods with a specified positive waiting time: 
		* <ul> 
		* <li>{@link #sleep Thread.sleep}</li> 
		* <li>{@link Object#wait(long) Object.wait} with timeout</li> 
		* <li>{@link #join(long) Thread.join} with timeout</li> 
		* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> 
		* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> 
		* </ul> 
		*/ 
		TIMED_WAITING, 
		/*** Thread state for a terminated thread. 
		* The thread has completed execution. 
		*/ TERMINATED; 
		}
		//返回这个线程当前所处的状态 
		public State getState() { 
			// get current thread state
			 return sun.misc.VM.toThreadState(threadStatus); 
		} 
	}

状态描述和解释如下:
在这里插入图片描述

注意,其实 BLOCKED,WAITING,TIMED_WAITING 这三种都属于线程阻塞,只是触发的条件不同,以及从阻塞状态中恢复过来的条件也不同而已。

线程在这三种情况的阻塞下,都具备相同的特点:
线程不执行代码
线程也不参与CPU时间片的争夺

一个线程,经历的最普通的过程如下:

public class Test { 
	public static void main(String[] args) { 
		Thread t1 = new Thread("t1线程"){ 
		@Override 
		public void run() {
			for (int i = 0; i < 10; i++) { 
			} 
		} 
	};
	System.out.println(t1.getState()); 
	//启动t1线程 
	t1.start(); 
	System.out.println(t1.getState()); 
	System.out.println(t1.getState()); 
	System.out.println(t1.getState()); 
	System.out.println(t1.getState()); 
	System.out.println(t1.getState()); 
	System.out.println(t1.getState()); 
	System.out.println(t1.getState()); 
	System.out.println(t1.getState()); 
	} 
}

//运行结果: 注意需要多运行几次,因为可能每次运行的情况不一样 
NEW
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
TERMINATED
TERMINATED

注意1,刚创建好的线程对象,就是出于NEW的状态
注意2,线程启动后,会出于RUNNABLE状态
注意3,其实这个RUNNABLE状态包含俩种情况:
就绪状态,此时这个线程没有运行,因为没有抢到CPU的执行权
运行状态,此时这个线程正在运行中,因为抢到CPU的执行权
注意4,JavaAPI中并没有定义就绪状态和运行状态,而是把这俩情况统一叫做RUNNABLE(可运行状态),但是一般我们为了能更加清楚的描述问题,会用上就绪状态和运行状态
注意5,在线程多次抢到CPU执行权,“断断续续”把run方法执行完之后,就变成了TERMINATED状态(死亡),之所以是“断断续续”的运行,是因为每次抢到CPU执行权的时候,只是运行很小的一个时间片,完了之后还要重新抢夺下一个时间片,并且中间还有可能抢不到的情况
注意6,死亡后的线程,不能重新启动

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值