Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下:

1)继承Thread类创建线程;

2)实现Runnable接口创建线程;

3)实现Callable接口通过FutureTask包装器来创建Thread线程;

4)线程池

继承Thread类

通过继承Thread类来创建并启动多线程的一般步骤如下:

1】定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体;

2】创建Thread子类的实例,也就是创建了线程对象;

3】启动线程,即调用线程的start()方法。

public class MyThread extends Thread{
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread()+":"+i);
		}
	}
    
	public static void main(String[] args) {
		MyThread thread1=new MyThread();
		MyThread thread2=new MyThread();
		MyThread thread3=new MyThread();
		thread1.start();
		thread2.start();
		thread3.start();
	}
}

实现Runnable接口

通过实现Runnable接口创建并启动线程一般步骤如下:

1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体;

2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象;

3】第三步依然是通过调用线程对象的start()方法来启动线程。

public class MyThread implements Runnable{
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
	
    
	public static void main(String[] args) {
		MyThread myThread1=new MyThread();
		MyThread myThread2=new MyThread();
		MyThread myThread3=new MyThread();
		Thread thread1=new Thread(myThread1);
		Thread thread2=new Thread(myThread2);
		Thread thread3=new Thread(myThread3);
		thread1.start();
		thread2.start();
		thread3.start();
	}

}

通过Callble和future Task

和Runnable接口不一样,Callable接口提供了一个call() 方法作为线程执行体,call() 方法比run() 方法功能要强大:

  • call() 方法可以有返回值;
  • call() 方法可以声明抛出异常。

Java5提供了Future接口来代表Callable接口里call() 方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

  • boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务;
  • get():返回Callable里call() 方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值;
  • get(long timeout,TimeUnit unit):返回Callable里call() 方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException;
  • boolean isDone():若Callable任务完成,返回True;
  • boolean isCancelled():如果在Callable任务正常完成前被取消,返回True。

介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:
1】创建Callable接口的实现类,并实现call() 方法,然后创建该实现类的实例(从Java8开始可以直接使用Lambda表达式创建Callable对象);
2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call() 方法的返回值;
3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口);
4】调用FutureTask对象的get() 方法来获得子线程执行结束后的返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyThread implements Callable<String>{
	private int count=20;

	@Override
	public String call() throws Exception {
		for (int i = 20; i >0; i--) {
			System.out.println(Thread.currentThread().getName()+"当前票数"+i);
		}
		return "sale out";
	}
    
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		Callable<String> callable  =new MyThread();
		FutureTask <String>futureTask=new FutureTask<>(callable);
		Thread thread1=new Thread(futureTask);
		Thread thread2=new Thread(futureTask);
		Thread thread3=new Thread(futureTask);
		thread1.start();
		thread2.start();
		thread3.start();
		System.out.println(futureTask.get());
	}
}

使用线程池

Executor

Java通过Executors提供四种线程池,分别为:

  • FixedThreadPool:返回固定线程数量的线程池。
  • SingleThreadExecutor:返回一个只有一个线程的线程池。
  • CachedThreadPool:该方法返回一个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但 若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新 的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用。
  • ScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

ThreadPoolExecutor

**Executors是JAVA并发包中提供的,用来快速创建不同类型的线程池。***但在一些大型项目中,这种做法一般是禁止的。因为用Executors创建的线程池存在性能隐患,我们看一下源码就知道,用Executors创建线程池时,使用的队列是new LinkedBlockingQueue<Runnable>(),这是一个无边界队列,如果不断的往里加任务时,最终会导致内存问题,也就是说在项目中由于使用了无边界队列,导致的内存占用的不可控性。故一般采用自己实现ThreadPoolExecutor的方式来自定义线程池。

ThreadPoolExecutor(
	int corePoolSize,//指定线程池中线程的数量
	int maximumPoolSize,//最大线程数量
	long keepAliveTime,//超过核心线程数的线程超过corePoolSize时,多余线程多久被销毁
	TimeUnit unit,//KeepAliveTime的单位
	BlockingQueue<Runnable> workQueue,//任务队列
	ThreadFactory threadFactory,//线程工厂
	RejectedExecutionHandler handler//拒绝策略
)

设线程池中的线程数量为n,,当一个任务通过execute方法添加到线程池时:

  • 若n<corePoolSize,创建新线程来处理;
  • 若n=corePoolSize,但缓冲队列workQueue未满,那么任务放进队列;
  • 若n>corePoolSize,缓冲队列workQueue满,且n<maximumPoolSize,新建线程处理任务;
  • 若n>corePoolSize,缓冲队列workQueue满,且n=maximumPoolSize,通过handle指定的拒绝策略来处理任务。

任务处理优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。

拒绝策略

在分析ThreadPoolExecutor的构造参数时,有一个RejectedExecutionHandler参数。RejectedExecutionHandler是一个接口:

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

里面只有一个方法。当要创建的线程数量大于线程池的最大线程数的时候,新的任务就会被拒绝,就会调用这个接口里的这个方法。可以自己实现这个接口,实现对这些超出数量的任务的处理。

ThreadPoolExecutor自己已经提供了四个拒绝策略,分别是AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy

  • AbortPolicy:ThreadPoolExecutor中默认的拒绝策略就是AbortPolicy。直接抛出异常。
  • CallerRunsPolicy:在任务被拒绝添加后,会调用当前线程池的所在的线程去执行被拒绝的任务。
  • DiscardPolicy:采用这个拒绝策略,会让被线程池拒绝的任务直接抛弃,不会抛异常也不会执行。
  • DiscardOldestPolicy:当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的,再把这个新任务添加进去。