java线程(一)——进程与线程

一.程序与进程

程序是为实现特定目标或解决特定问题而用计算机语言(比如Java语言)编写的命令序列的集合。

进程指的就是一个程序的一次执行过程。

以下面这段代码为例,我们在程序中使用Thread.sleep方法让程序暂停十秒,然后打开任务管理器

package thr;

public class Test {

	public static void main(String[] args) {
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(1111);
	}
}

可以看到任务管理器中多出了这样一条任务,而十秒过去之后,程序运行结束,该条信息便消失了,这便是一条进程,程序在运行期间它便出现,程序运行结束进程便消失。

二.线程

线程是运行在进程内的独立的一个执行单元,又称为轻量级进程,是一个程序中实现单一功能的一个指令序列,是一个程序的单个执行流,存在于进程中,是一个进程的一部分。

线程的创建有如下两种方式:

1.继承java.lang.Thread抽象类,重写run方法

为了便于观察该进程中线程数的变化,我们在重写的run方法中也加上sleep暂停十秒:

package thr;

public class Test {

	public static void main(String[] args) {
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new TimeThread().start();
	}
}


class TimeThread extends Thread{

	@Override
	public void run() {
		for(int i=0;i<5;i++) {
			System.out.println("计数2:"+i);
		}
		try {
			sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

这样创建的类在启动时直接创建对象然后调用start即可启动该线程,启动后发现任务管理器中该进程的线程数从刚才的14变成了如下的15,说明该线程已开启。

2.实现java.lang.Runnable接口,重写run方法

使用该方法创建线程时,由于实现的是Runnable接口,所以无法调用start方法,所以必须通过Thread类对象的构造方法传入线程类对象,然后用Thread类对象再调用start方法开启线程。

package thr;

public class Test {

	public static void main(String[] args) {
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new Thread(new CountThread()).start();
	}
}


class CountThread implements Runnable{

	@Override
	public void run() {
		for(int i=0;i<5;i++) {
			System.out.println("计数1:"+i);
		}
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

三.线程生命周期

以下是线程的生命周期示意图:

1.新建

刚才说到,继承java.lang.Thread抽象类重写run方法,和实现java.lang.Runnable接口重写run方法都可以新创建一个线程,而在Test类中调用start方法其实是线程就绪的过程,那么就绪之后何时开始运行阶段呢?

2.就绪与运行

我们在刚才创建的两个线程的run方法中加入每遍历一次暂停一秒的机制,然后一起运行一次:

package thr;

public class Test {

	public static void main(String[] args) {
		new TimeThread().start();
		new Thread(new CountThread()).start();
	}
}


class TimeThread extends Thread{

	@Override
	public void run() {
		for(int i=0;i<5;i++) {
			System.out.println("计数2:"+i);
			try {
				sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}


class CountThread implements Runnable{

	@Override
	public void run() {
		for(int i=0;i<5;i++) {
			System.out.println("计数1:"+i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

发现得到的结果是两个线程交替执行,其原因是同一进程中的多个线程之间采用抢占式独立运行,因为处理器同一时间只能运行一个线程,所以当其中一个线程抢占到运行机会的时候,另一个线程便处于就绪状态,当就绪状态的线程抢到了运行机会时,进入了运行状态,原来处于运行状态的线程便处于就绪状态。

3.死亡

线程死亡的方式有三种:

①run方法执行完成

run方法执行完成则线程执行结束,线程自然死亡

②出现error错误

error错误属于程序本身无法恢复的严重错误

③出现Exception异常

例如在线程的run方法中加入一条,这时该异常后面的代码便无法执行,线程直接死亡。

    System.out.println(1/0);

4.阻塞

阻塞通俗的说就是在线程存在的时间内,本该运行代码但是却在一处停留,最简单的阻塞方式就是上面示例使用的sleep方法,当sleep执行完成,线程也就结束了阻塞,继续回到就绪状态与运行状态之间转换。

四.线程池

当进程中有多个线程时,一个线程在运行状态其他的线程就处于就绪状态,那是否意味着一个进程中可以开启无数了线程呢?答案当然是不能,因为处理器的内存毕竟是有限的,所以为了防止开启的线程过多导致处理器崩溃,我们可以使用线程池。

线程池可以指定最多存在的线程数量,这样便可以有效避免线程过多导致内存溢出。

以下是线程池的使用示例:

第10行:在JDK5开始新增了一个Executors工厂类,该类调用newFixedThreadPool()方法即可创建一个固定大小的线程池,该方法返回值类型是ExecutorService类型的对象。这里为了便于说明,所以设置固定大小为2。

第11行:用executorService对象调用submit方法创建一个线程,注意,这里使用的是匿名内部类的Lambda表达式

第12行:for循环执行五次循环体。

第13行:每次打印一行数据。

第15行:为了便于观察多个线程之间的执行顺序,这里让线程每次执行循环体时停留五秒。

第22到42行以此类推

第44行:该处用executorService对象调用shutDown方法,当线程池中的线程全部结束以后关闭线程池。

package thr;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {


	public static void main(String[] args) {
		ExecutorService executorService=Executors.newFixedThreadPool(2);
		executorService.submit(()->{
			for(int i=0;i<5;i++) {
				System.out.println("计数1!!!:"+i);
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		
		executorService.submit(()->{
			for(int i=0;i<5;i++) {
				System.out.println("计数2%%%:"+i);
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		
		executorService.submit(()->{
			for(int i=0;i<5;i++) {
				System.out.println("计数3@@@:"+i);
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		
		executorService.shutdown();
	}
}

执行结果如下,由于线程池的固定大小为2,所以里面只能存在两个线程,也就是说这三个线程中最后一个开启的线程会在线程池外等待,只有当线程池中的其中一个先执行完出来以后,该等待的线程才能进入线程池执行。

发布了99 篇原创文章 · 获赞 93 · 访问量 5241

猜你喜欢

转载自blog.csdn.net/DangerousMc/article/details/100033044