Java多线程的创建与线程状态的转换

一、线程创建

  • 第一种方式:继承Thread类,覆写run( )
  1. run( )是线程操作的核心方法,是每个线程的入口
  2. run()是由JVM创建完本地操作系统级线程后回调的方法,不可手工调用,否则就是普通方法
  3. 通过调用Thread类的start()启动线程,一个线程只能调用一次start( ),否则会抛出线程状态异常
  4. 通过start( )调用start0():private native void start0();native声明的方法没有方法体,只有方法声明,但本地方法不是抽象方法,而是Java调用c语言
  5. registerNatives():包含所有与线程有关的操作系统级方法
  6. 代码实现: 
package one;

class MyThread1 extends Thread{

	@Override
	public void run() {
		for(int i = 0; i < 5; i++) {
			System.out.println("线程1 "+i);
		}
	}
}

class MyThread2 extends Thread{

	@Override
	public void run() {
		for(int i = 0; i < 5; i++) {
			System.out.println("线程2 "+i);
		}
	}
}

public class Test {

	public static void main(String[] args) {
		MyThread1 myThread1 = new MyThread1();
		MyThread2 myThread2 = new MyThread2();
		
		myThread1.start();
		myThread2.start();
	}
}

运行结果

  • 第二种方式:实现Runnable接口,覆写run()(推荐)
  1. Thread类提供了构造方法接收Runnable接口的对象
  2. Runnable接口中仅有一个方法,run()
  3. 用此方法可以避免单继承局限
  4. 当子类实现Runnable接口,此时子类与Thread类就是典型的代理设计模式,其中,子类负责真实线程业务操作,Thread类负责资源调度与创建线程来辅助
  5. 代码实现:方式1
package one;

class MyThread3 implements Runnable{

	@Override
	public void run() {
		for(int i = 0; i < 5; i++) {
			System.out.println("线程1 "+i);
		}
	}
}

class MyThread4 implements Runnable{

	@Override
	public void run() {
		for(int i = 0; i < 5; i++) {
			System.out.println("线程2 "+i);
		}
	}
	
}

public class Test1 {

	public static void main(String[] args) {
		//方式1
		MyThread3 myThread1 = new MyThread3();
		MyThread4 myThread2 = new MyThread4();
		new Thread(myThread1).start();
		new Thread(myThread2).start();	
	}
}

方式1运行结果

5.  代码实现:方式2:Runnable接口中仅有一个函数,所以可以使用函数式编程

public class Test1 {

	public static void main(String[] args) {		
		//方式2:Runnable接口中仅有一个函数,函数式编程
		Runnable runnable = () -> {
			System.out.println("hello");
		};
		new Thread(runnable).start();
        }
}

输出"hello"。

6.  代码实现:方式3:匿名内部类

public class Test1 {

	public static void main(String[] args) {
		//方式3:匿名内部类
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.out.println("hello");	
			}
		}).start();
	}
}

输出"hello"。

前两种方式(继承Thread类和实现Runnable接口)的区别

  1. 实现Runnable接口可以避免单继承局限;
  2. 实现Runnable接口可以更好的体现程序共享的概念
  • 第三种方式:覆写Callable接口(JDK1.5新增的接口)
  1. 核心方法:call(),有返回值
  2. 该方式的特点:call()有返回值,可以查看线程执行完的一些返回结果
  3. 由于run( )是启动线程的核心,而Thread类仅提供了接收Runnable接口对象的构造方法,所以需要将Callable接口的对象与Runnable接口联系起来
  4. FutureTask类就是连接两个接口的关键,FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnabe接口,至此连接起了Runnable接口;而FutureTask类又提供了可以接收Callable接口对象的方法,至此连接起了Callable接口;综上所诉,即用FutureTask类接收Callable接口对象,再用Thread类接收FutureTask类对象,启动线程
  5. 实现:模拟卖票过程
package one;

//覆写Callable
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyThread5 implements Callable<String>{
	private int ticket = 20;

	@Override
	public String call() throws Exception {
		while(ticket > 0) {
			System.out.println(ticket--);
		}
		return "ticket:0";
	}
	
}

public class Test2 {

	public static void main(String[] args) throws InterruptedException, ExecutionException {
		Callable<String> callable = new MyThread5();
		
		//联系
		//FutureTask类实现了RunnableFuture接口,而RunnableFuture接口继承了Runnable接口
		//FutureTask类可以接收Callable接口对象
		FutureTask<String> futureTask = new FutureTask<>(callable);
		
		new Thread(futureTask).start();
		System.out.println(futureTask.get());//获取call()返回信息
		new Thread(futureTask).start();
	}
}

运行结果

在线程执行完返回ticket还有0张的情况 

  • 第四种方式:线程池(JDK1.5)

1、线程池就是多个线程封装在一起操作

2、三大优点:

1)降低资源消耗,通过重复利用与创建的线程降低线程创建与销毁带来的消耗

2)提高响应速度,当任务到达时,不需要等待线程创建就可立即执行

3)提高线程的可管理性,使用线程池可以统一进行线程分配、调度与监控

3、线程池分类

1)普通线程池:通过Executor(线程的执行机制)的工具类Exectors,可以取得3种普通线程池

  1. FixedThreadPool(int nThread):固定大小线程池;适用于为了满足资源管理需求而需要限制当前线程数量的场合;适用于负载比较重的服务器;
  2. SingleThreadPoolExecutor:单线程池;适用于需要保证顺序执行各个任务的场景;
  3. CachedThreadPool:缓存线程池;当提交任务速度 >= 线程池中任务处理速度时,缓冲线程池会不断创建线程;适用于执行很多短期的异步小程序以及负载较轻的服务器

2)调度线程池:ScheduleExecutorService

  •     newScheduledThreadPool(int nThread)
  •     scheduleAtFixedRate(command, initialDelay, period, unit)
  1.     command-要执行的任务
  2.     initialDelay-任务延迟多少单元后执行
  3.     period-执行周期
  4.     unit-周期单元单位

4、线程池组成:

1)corePool:核心线程池

2)BlockingQueue:阻塞队列

3)MaxPool:线程池容纳的最大线程数量

5、工作方式:

1)如果当前运行的线程数 < corePoolSize;则创建新的线程执行任务,而后将此线程放入corePool

2)如果当前线程数 >= corePoolSize;则将任务放入阻塞队列等待调度执行(95%:大多都是此类情况)

3)如果阻塞队列已满,则试图创建新的线程来执行(需要全局锁)

4)如果创建线程后,总线程数 > maxPoolSize;则任务被拒接,调用拒绝策略返回给用户

6、工作线程:线程池创建线程时,将线程封装为Worker,Worker在执行完后,还会循环从工作队列中取得任务来执行

7、手工创建普通线程池

ThreadPoolExecutor tpe = new ThreadPoolExecutor(int corePoolSize,
	                    int maximumPoolSize,
	                    long keepAliveTime,
	                    BlockingQueue<Runnable> workQueue,
	                    RejectedExecutionHandler handler
	                    )

1)corePoolSize

线程池的基本大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务
即使其他空闲的基本线程能够执行任务也会创建新线程,
直到当前线程池中的线程数量 > 基本大小才不会创建
2)BlockingQueue
任务队列,用于保存等待执行任务的阻塞队列
a、ArrayBlockingQueue:基于数组的有界阻塞队列,按照FIFO对元素进行排序       
b、LinkedBlockingQueue:基于链表结构的无界阻塞队列,吞吐量高于ArrayBlockingQueue
      FixedThreadPool()、singleThreadPool()都采用此队列
c、synchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待另一个线程移除操作,否则插入操作一直处于阻塞状态,吞吐量 > LinkedBlockingQueue;CachedThreadPool()采用此队列
d、priorityBlockingQueue:具有优先级的无界阻塞队列
3)keepAliveTime(线程活动时间)
      线程池的工作线程空闲后,保持存活的时间。
4)TimeUnit:keepAliveTime的时间单位
5)RejectedExecutionHandler(饱和策略):线程池满时无法处理新任务的执行策略,
      线程池默认采用AbortPolicy(抛出异常)-可以省略此参数

8、线程池操作

1)向线程池提交任务

  • execute(Runnable run):用于提交不需要返回值的任务,所以无法判断处任务是否被线程池执行成功;
  • submit(Runnable/Callable):返回Future对象,用于提交需要返回值的任务,可以通过future对象判断任务是否被执行成功

future.get()会阻塞当前线程,知道任务全部执行完毕

2)手工关闭线程池

  • shotdown():停止所有没有正在执行任务的线程(推荐)
  • shotdownNow():停止所有正在执行以及空闲线程

二、线程状态的转换

猜你喜欢

转载自blog.csdn.net/tec_1535/article/details/81270827