线程(一) 线程的理解

什么是程序?

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

什么是进程?

进程(process)指一个程序的一次执行过程。任务管理器的PID指的就是进程的ID。
在这里插入图片描述

什么是线程?

线程(thread)又称为轻量级进程(只是说在消耗资源方面,进程比线程多。),线程是一个程序中实现单一功能的一个指令序列,是一个程序的单个执行流,存在于进程中,是一个进程的一部分。

public class Work {

	public static void main(String[] args) {
		try {
			Thread.sleep(10000); //延时10s执行。
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		new TimesThread().start(); //启动该线程,任务管理器可以看到线程数量加一。
	}
}

class TimesThread extends Thread{  //这是一个单线程。

	@Override
	public void run() {
		try {
			sleep(8000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(111);
	}
	
}

程序刚运行时,出现javaw.exe的进程,该进程的线程数量为13:
在这里插入图片描述
10s后,线程数量加1变为14。
然后8s后,javaw.exe进程消失,代码输出111。
在这里插入图片描述

进程与线程:

1、一个进程可以包含多个线程,而一个线程必须在一个进程之内运行;同一进程中的多个线程之间采用抢占式独立运行;进程结束,则线程跟着结束,如果一个进程没有可执行的线程,进程也结束。

2、线程有独立的执行堆栈、程序计数器和局部变量;但是没有独立的存储空间,而是和所属进程中的其它线程共享存储空间。

3、操作系统将进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位,由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统多个程序间并发执行的速度。

注:一个进程就好比一个家庭,进程中的线程好比家庭中的每个成员,每个家庭成员共享存储空间——房子!

如何创建线程对象?

有两种方法创建线程对象:
1、继承java.lang.Thread类,重写run方法。

class 类名 extends Thread{
	//属性
	public void run(){
		//线程需要执行的核心代码
	}
	//其他方法
}

2、实现java.lang.Runnable接口,实现run抽象方法。

class 类名 implements Runnable{
	//属性
	public void run(){
		//线程需要执行的核心代码 
    }
    //其他方法
}

例如:

public class Work {

	public static void main(String[] args) {
		new TimesThread().start(); //第一种方式启动线程,因为继承,所以直接调用start方法。
		new Thread(new CountsThread()).start(); //第二种方式启动线程。
	}
}

class TimesThread extends Thread { //第一种方式。

	@Override
	public void run() {
		try {
			sleep(1000); //继承了Thread类,可以直接调用sleep方法。
			System.out.println(111);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class CountsThread implements Runnable { //第二种方式。

	@Override
	public void run() {
		try {
			Thread.sleep(1000); //不能直接调用,需要加类名。
			System.out.println(222);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
线程生命周期?

Java中,线程有5种不同状态,分别是:新建(New)、就绪(Runable)、运行(Running)、阻塞(Blocked)和死亡(Dead)。它们之间的转换图如下:
在这里插入图片描述
新建状态(New):新创建了一个线程对象。

就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

运行状态(Running):就绪状态的线程获取了CPU使用权,执行程序代码。

阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
1、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
2、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
3、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁);

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

① start()方法的调用就是从新建到就绪,并不代表进入运行状态。
② 线程是抢占式独立运行,在没有阻塞时,多个线程各自循环时会抢占CPU使用权,所以没有固定的执行循序。
③ 线程获得CPU使用权:就绪—>运行;线程失去CPU使用权:运行—>就绪。
④ 从运行到死亡的方式:
1.run方法执行完成。
2.Error,程序本身无法恢复的严重错误。
3.Exception,程序出现异常。
⑤ 从运行到阻塞的方式:
1.sleep()
2.IO阻塞(比如让线程拷贝大的文件)
3.等待同步锁
4.wait()
5.suspend()
⑥ 从阻塞到就绪的方式:
1.sleep时间到
2.IO方法结束(比如文件拷贝完成)
3.获得同步锁
4.notify()
5.resume()

线程池:

1、为什么要用线程池?
提高程序的执行效率:
如果程序中有大量短时间任务的线程任务,由于创建和销毁线程需要和底层操作系统交互,大量时间都耗费在创建和销毁线程上,因而比较浪费时间,系统效率很低;线程池里的每一个线程任务结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用,因而借助线程池可以提高程序的执行效率。
控制线程的数量,防止程序崩溃:
如果不加限制地创建和启动线程很容易造成程序崩溃,比如高并发1000W个线程,JVM就需要有保存1000W个线程的空间,这样极易出现内存溢出;线程池中线程数量是一定的,可以有效避免出现内存溢出。

2、怎么用?

在JDK5之前,必须手动才能实现线程池,从JDK5开始新增了一个Executors工厂类,通过该工厂类可以实现线程池,该类有如下常用方法:

① public static ExecutorService newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中;

② public static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池;

③ public static ExecutorService newSingleThreadExecutor():创建一个只有单线程的线程池,相当于调用newFixedThreadPool(int nThreads)方法时传入参数为1。

④ public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。 corePoolSize指池中所保存的线程数,即使线程时空闲的也被保存在线程池内。

⑤ public static ScheduledExecutorService newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。

⑥ public static ExecutorService newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别,该方法还会使用多个队列来减少竞争。

⑦ public static ExecutorService newWorkStealingPool():该方法是前一个方法的简化版本。如果当前机器有4个CPU,则目标并行级别被设置为4,也就是相当于为前一个方法传入4作为参数。

ExecutorService 表示一个线程池,该接口常用方法:
① Future<?> submit(Runnable task) :提交Runable接口实现类实例。
② <泛型> Future <泛型> submit(Runnable task, 泛型 result) :提交Runable接口实现类实例。
③ void shutdown() :关闭线程池。

步骤:
① 创建线程池对象。
② 创建Runable接口实现类实例。
③ 提交Runable接口实现类实例。
④ 关闭线程池。

例如:

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

public class Test {

	public static void main(String[] args) throws InterruptedException {
		Thread.sleep(2000);
		//创建了一个容量为2的线程池。当执行的多出两个时,必须等待前面两个执行完。
		ExecutorService executorService = Executors.newFixedThreadPool(2); 
		executorService.submit(() -> {
			for (int i = 1; i < 5; i++) {
				System.out.println("&&&" + i);
				try {
					Thread.sleep(8000); //阻塞8S。
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		executorService.submit(() -> {

			for (int i = 1; i < 5; i++) {
				System.out.println("###" + i);
				try {
					Thread.sleep(8000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}

		});
		executorService.submit(() -> { //该线程需要在前两个中有一个结束之后才能进行。
			for (int i = 0; i < 5; i++) {
				System.out.println("^^^" + i);
				try {
					Thread.sleep(8000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		executorService.shutdown(); //关闭线程池,如果没有,程序会一直显示运行状态。
	}
}

输出:
&&&1
###1
&&&2
###2
###3
&&&3
###4
&&&4
^^^0
^^^1
^^^2
^^^3
^^^4

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值