018线程(java基础)

目录

一、程序,进程,线程

二、创建线程

1、继承Thread类

2、实现Runnable接口

3、实现Callable接口

4、线程池

5、Springboot中使用线程池

三、线程的生命周期

1、介绍五种状态

2、五种状态之间的转换


一、程序,进程,线程

  • 程序:为完成特定任务,用某种语言编写的一组指令的集合,指静态的代码
  • 进程:程序的一次执行过程,或是正在运行的一个程序
  • 线程:程序内部的执行路径,是操作系统能够进行运算调度的最小单位

一个进程可以由多个线程组成,至少得包含一个线程

当进程内的多个线程同时运行,这种运行方式称为并发运行

二、创建线程

有四种方式

1、继承Thread类

  • 继承Thread类需要重写run()方法
  • 直接调用start()方法来启动线程
public class PrintThread extends Thread{
	@Override
	public void run() {
		//当前线程的名称
		String threadName = Thread.currentThread().getName();
		System.out.println("线程"+threadName+"开始计时:");
		for(int i = 1;i<=5;i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("第"+i+"秒");
		}
		System.out.println("线程"+threadName+"结束计时");
	}
}
public static void main(String[] args) {
		PrintThread pt = new PrintThread();
        pt.setName("《计时器》");
		pt.start();
	}

线程《计时器》开始计时:
第1秒
第2秒
第3秒
第4秒
第5秒
线程《计时器》结束计时

2、实现Runnable接口

  • 实现Runnable接口
  • 重写run方法
  • 需要借助Thread类
  • 将对象作为Thread构造方法的参数,调用start
public class PrintRunThread implements Runnable{

	@Override
	public void run() {
		String threadName = Thread.currentThread().getName();
		System.out.println("线程"+threadName+"开始计时:");
		for(int i = 1;i<=5;i++) {
			System.out.println("第"+i+"秒");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("线程"+threadName+"结束计时");
	}
}
    static void test() {
		PrintRunThread pr = new PrintRunThread();
		Thread t = new Thread(pr);
		t.setName("《计时器2》");
		t.start();
	}

3、实现Callable接口

与使用runnable方式相比,callable功能更强大些:

  • runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
  • 方法可以抛出异常
  • 支持泛型的返回值

注意事项:

  • 实现Callable接口
  • 重写call()方法
  • 需要借助FutureTask类
  • 对象作为Future构造方法的参数
  • 还需要借助Thread类
  • FutureTask类的对象作为Thread类构造方法的参数
  • FutureTask对象.get()可以获取call方法的返回结果
public class PrintCallThread implements Callable{
	@Override
	public Object call() throws Exception {
		int sum = 0;
		String threadName = Thread.currentThread().getName();
		System.out.println("线程"+threadName+"开始计数:");
		for(int i = 1;i<=5;i++) {
			System.out.println("加"+(i*10));
			sum+=i*10;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("线程"+threadName+"结束计时");
		return sum;
	}
}
    void thread3() {
		PrintCallThread pc = new PrintCallThread();
		FutureTask ft = new FutureTask(pc);
		Thread t = new Thread(ft);
		t.setName("《求和计算器》");
		t.start();
		try {
			System.out.println("最后结果为"+ft.get());//FutureTask对象.get()可以获取重写的call方法的返回值
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
	}

输出结果:

线程《求和计算器》开始计数:
加10
加20
加30
加40
加50
线程《求和计算器》结束计时
最后结果为150

4、线程池

不建议单纯使用继承Thread或者实现Runnable接口的方式来创建线程,那样势必有创建及销毁线程耗费资源、线程上下文切换问题。同时创建过多的线程也可能引发资源耗尽的风险,这个时候引入线程池比较合理,方便线程任务的管理。所以线程池的优势就是不用反复创建和销毁线程,能提高响应速度,减少资源消耗。

两个重要的API:ExecutorService Executors,用来创建线程池的

ExecutorService有个重要的子类ThreadPoolExecutor,也可以直接new ThreadPoolExecutor()来创建线程池,我们这里先只讲用Executors来创建

(1)Executors有四个创建线程池的静态方法:

newFixedThreadPool(线程个数);创建固定大小的线程池
newSingleThreadExecutor();创建只有一个线程的线程池
newCachedThreadPool()创建一个不限线程数上限的线程池,任何提交的任务都将立即执行
newScheduledThreadPool()创建一个不限线程数上限的线程池,提交的任务将定时周期性执行

newScheduledThreadPool

返回值都是ExecutorService

ExecutorService executorService =Executors.newFixedThreadPool(10);
//创建一个容量为10的线程池,固定容量

(2)executorService有两个重要的方法

execute(线程对象);参数是实现了Runnable接口的线程
submit(线程对象);参数是实现了Callable接口的线程

(3)executorService关闭线程池的方法  

shutdownNow()立即关闭线程池(暴力),正在执行中的及队列中的任务会被中断
shutdown()平滑关闭线程池,正在执行中的及队列中的任务能执行完成,后续进来的任务会被执行拒绝策略
isTerminated()当正在执行的任务及对列中的任务全部都执行(清空)完就会返回true

利用上述的两个线程类来测试创建的线程池:

    static void usePool() {
		ExecutorService excutor = Executors.newFixedThreadPool(10);
		PrintRunThread run = new PrintRunThread();
		PrintCallThread call = new PrintCallThread();
		
		//执行线程
		excutor.execute(run);
		excutor.submit(call);
		
		//关闭线程
		excutor.shutdown();
	}

5、Springboot中使用线程池

(1)配置类中构建线程池实例,方便调用

@Configuration
public class ThreadPoolConfig {
    @Bean(value = "threadPoolInstance")
    public ExecutorService createThreadPoolInstance() {
        //通过guava类库的ThreadFactoryBuilder来实现线程工厂类并设置线程名称
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build();
        ExecutorService threadPool = new ThreadPoolExecutor(10, 16, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy());
        return threadPool;
    }
}

(2) 引用线程池实例

(3)executorService.execute(new Runnable(){});

  @Resource(name = "threadPoolInstance")
  private ExecutorService executorService;
 
  @Override
  public void spikeConsumer() {
    //TODO
    executorService.execute(new Runnable() {
    @Override
    public void run() {
      //TODO
     }});
}

三、线程的生命周期

新建、就绪、运行、阻塞、死亡

1、介绍五种状态

(1)新建:

仅仅分配了内存,还不能运行

(2)就绪:

具备了运行的条件,在等待CPU

(3)运行:

获得了CPU的使用权,并开始执行run()方法中的线程执行体。

(4)阻塞:

被迫(同步阻塞)或者主动(sleep)让出CPU的使用权并暂时中止自己的执行,进人阻塞状态。

线程进人阻塞状态后,就不能进入排队队列。只有当引起阻塞的原因被消除后,线程才可以转入就绪状态,是不能从阻塞直接转为运行的

当sleep结束或者因为wait()进入阻塞状态,但是被notify()唤醒的时候,会从阻塞状态进入就绪

(5)死亡:

一旦进入死亡状态,线程将不再拥有运行的资格,也不能再转换到其他状态。也就是说线程的生命周期结束了

2、五种状态之间的转换

(1)新建-->就绪

new出线程对象之后,通过start()方法启动线程

(2)就绪-->运行

获得CPU

因为就绪状态表示,除了CPU以外的其他资源都已经备好了

注意:只有处于就绪状态的线程才能转为运行状态

(3)运行-->死亡

run()方法正常执行完毕、stop()方法强行终止线程或者捕获到某种异常的时候线程就进入死亡状态。

(4)运行-->就绪

如果被迫失去CPU的使用权时,会进入就绪状态,如果是因为其他资源被剥夺,通过wait(),sleep()等方式,将会进入阻塞状态

(5)运行-->阻塞

有三种情况:

  • 同步阻塞:当前线程所需的某种资源被其他线程占用的时候,也称IO阻塞
  • 等待阻塞:线程调用wait()方法,使本线程进入到等待状态;调用join()方法,使本线程进入到另一种等待状态:等待线程终止或者超时的状态
  • 其他阻塞:执行sleep(时间)方法的时候,线程主动放弃对CPU的使用权,停止执行

(6)阻塞-->就绪

根据(5)的三种阻塞情况可知,对应以下三种情况可让线程从阻塞装成就绪

  • 当别的线程释放了正在阻塞的线程需要的资源的时候,这个阻塞的线程将能转成就绪状态
  • 使用notify()方法唤醒线程
  • sleep()时间结束

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值