线程的定义
概念:线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
Java中线程的四种创建方式
1. 继承 Thread 类实现多线程
示例:
public class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println("通过继承 Thread 类创建线程");
}
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(threadDemo);
thread.start();
}
}
2. 实现 Runnable 接口
Runnable接口源码如下,它只有一个run()方法。
public interface Runnable {
public abstract void run();
}
示例:
public class ThreadDemo implements Runnable {
@Override
public void run() {
System.out.println("通过实现 Runnable 接口创建线程");
}
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread thread = new Thread(threadDemo);
thread.start();
}
}
3. 实现 Callable 接口
通过实现 Runnable 接口与继承 Thread 类的方式创建的线程是没有返回值的,然而在有些情况下,往往需要通过某个线程计算得到的结果供给另一个线程使用,这个时候采用Runnable 与 Thread 创建线程并通过自行编写代码实现结果返回的方式会不可避免的出现许多错误或性能上的问题。基于此问题,JUC并发包(java.util.concurrent)提供了解决方案,实现 Callable 的 call() 方法(这个类似Runnable 接口),使用 Future 的 get() 方法进行获取。
先来看一下Callable接口:
public interface Callable<V> {
V call() throws Exception;
}
创建过程为:
- 自定义一个类实现Callable接口,重写call()方法;
- 使用JUC包下的 ExecutorService 生成一个对象,使用 submit() 方法得到 Future 对象;
- 采用 Future 的 get() 方法获取返回值。
示例:
import java.util.concurrent.*;
/**
* 计算1+2+...+20的结果,开启三个线程,主线程获取两个子线程计算的结果,一个子线程计算1+...+10,一个子线程计算11+...+20。
*/
public class ThreadDemo implements Callable {
//子线程1,用来计算1+...+10
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 1; i <= 10; i++)
count = count + i;
return count;
}
public static void main(String[] args) throws Exception {
//生成具有两个线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(2);
//调用 executorService.submit() 方法获取 Future 对象
Future<Integer> result1 = executorService.submit(new ThreadDemo());
Future<Integer> result2 = executorService.submit(new SubThread());
//使用 Future的get() 方法等待子线程计算完成返回的结果
int result = result1.get() + result2.get();
//关闭线程池
executorService.shutdown();
System.out.println(result);
}
}
class SubThread implements Callable {
//子线程2,用来计算11+...+20
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 11; i <= 20; i++)
count = count + i;
return count;
}
}
4. 通过 JUC 里面的线程池
Executors 的工厂方法提供了 5 种不同的线程池,具体可以看JDK API,如下图。
使用线程池是比前几种相对少见的创建线程做法。从JAVA SE5开始,java.util.concurrent包中的执行器(Executor)将为你管理Thread对象,从而简化了并发编程。如果我们的程序需要用到很多生命周期比较短的线程,那么应该使用线程池,线程池中包含了很多空闲线程,而且这些线程的生命周期不需要我们操心。另一个使用线程池的原因是:如果你的代码需要大量的线程,那么最好使用一个线程池来规定总线程数的上线,防止虚拟机崩溃。这样可以限制最大的并发数量。
静态方法创建线程池实例
正如Collection类的静态方法都在Collections中一样,执行器Executor创建线程池的静态方法全部在Executors类中:
public static ExecutorService newCachedThreadPool()
创建一个新的线程池。如果需要线程而线程池中无空闲线程时,创建一个新的线程。空闲线程会被保留60秒。
public static ExecutorService newFixedThreadPool(int nThreads)
根据参数值创建一个固定数量线程的线程池。如果所需线程超过池中线程数则会发生等待。空闲线程会被一直保留。
public static ExecutorService newSingleThreadExecutor()
创建一个仅有一个线程的线程池,顺序执行每一个提交的任务。(和第二种方法参数为1时效果相同)
提交Runnable任务到线程池中
Executor作为一个祖先接口,提供了一个也仅有一个提交线程的方法:
void execute(Runnable command)
在Executor的子类中,有很多子类提供了具有返回值的提交方法,返回提交的结果。
public Future<?> submit(Runnable task)
比如这种submit方法,提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功 完成时将会返回 null。调用get方法就能得到提交的结果。
关闭线程池
在线程使用结束后,为了保证程序的安全,我们有必要手动调用关闭线程池的方法:
public void shutdown()
ThreadPool类:
package AllThread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*
* @author QuinnNorris
*
* 创建线程池
*/
public class ThreadPool {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService es = Executors.newFixedThreadPool(5);
// 我们调用静态方法创建了包含五个线程的线程池
for (int i = 0; i < 5; i++)
es.submit(new ImplRunnable());
es.shutdown();
// 在使用结束之后,一定要关闭线程池
}
}
ImplRunnable类:
package AllThread;
/**
*
* @author QuinnNorris
*
* Runnable的一个实现类
*/
public class ImplRunnable implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("new thread");
}
}
线程池的工作原理
线程池可以减少创建和销毁线程的次数,从而减少系统资源
的消耗,当一个任务提交到线程池时
- 首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线
程执行任务,否则进入下一步 - 判断工作队列是否已满,没有满则加入工作队列,否则执行下一步
- 判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否
则执行饱和策略,默认抛出异常