进程是表示资源分配的基本单位,线程是进程中执行运算的最小单位,亦是调度运行的基本单位。
线程的状态变化
创建状态
在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread 类的构造方法来实现,例如 “Thread thread=new Thread()”。
就绪状态
新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。
运行状态
当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run() 方法定义该线程的操作和功能。
阻塞状态
一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait() 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
死亡状态
线程调用stop()方法时或run()方法执行结束后,即处于死亡状态。
实现多线程编程两种方式
继承Thread类
class MyThread extends Thread{ // 继承Thread类,作为线程的实现类
private String name; // 表示线程名称
public MyThread(String name){ // 通过构造方法初始化name
this.name = name;
}
public void run(){
for(int i=0;i<10;i++){
System.out.println(name + "运行,i=" + i);
}
}
}
public class ThreadDemo01{
public static void main(String arg[]){
MyTread p1 = new MyTread("线程A");
MyTread p2 = new MyTread("线程B"); //实例化对象
p1.start();
p2.start(); //启动多线程
}
}
实现Runnable接口
class MyTread1 implements Runnable{ // 实现Runnable接口,作为线程的实现类
private String name; // 表示线程名称
public MyTread1(String name){ // 通过构造方法初始化name
this.name = name;
}
public void run(){
System.out.println(name + "运行"); // 重写run方法,作为线程的操作主体
}
}
public class ThreadDemo02 {
public static void main(String[] args) {
MyTread1 p1 = new MyTread1("线程A"); // 实例化对象
MyTread1 p2 = new MyTread1("线程B"); // 实例化对象
MyTread1 p3 = new MyTread1("线程C"); // 实例化对象
Thread t1 = new Thread(p1) ; //实例化Tread对象
Thread t2 = new Thread(p2) ; //实例化Tread对象
Thread t3 = new Thread(p3) ; //实例化Tread对象
t1.start(); // 启动多线程
t2.start(); // 启动多线程
t3.start(); // 启动多线程
}
}
Thread.java 类中的start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用 Thread 中的 run() 方法,也就是使线程得到运行,多线程是异步的,线程在代码中启动的顺序不是线程被调用的顺序。
Thread是Runnable的子类**,但在Thread类中并没有完全实现 Runnable 接口中的 run() 方法。在 Thread 类中的 run() 方法调用的是 Runnable 接口中的 run() 方法,也就是说此方法是由 Runnable 子类完成的,所以如果要通过继承 Thread 类实现多线程,则必须覆写 run()。
实际上 Thread 类和 Runnable 接口之间在使用上也是有区别的,如果一个类继承 Thread类,则不适合于多个线程共享资源,而实现了 Runnable 接口,就可以方便的实现资源的共享。
多线程方法
currentThread()
currentThread()可以返回当前代码段正在被哪个线程调用。
Thread.currentThread.getname()
//得到当前线程名
Thread.currentThread().yield()
// 线程礼让
isAlive()
isAliv方法 判断当前线程是否处于活动状态
Thread.isAlive()
sleep( )
sleep()的作用是指在指定的毫秒数内,让当前‘正在执行的线程’休眠。正在执行的线程指的是this.currentTread()返回的线程。
Thread.sleep()
getId( )
getId( ) 的作用是取得线程的唯一标识。
Thread.getId()
线性池
- 重用线性池中的线程
- 有效控制线性池的最大并发数,避免大量线程之间因互相争夺资源二导致阻塞现象
- 能对线程进行简单的管理,并提供定时执行以及指向间隔循环执行等功能
线程池的创建
创建线程->调用Executors
中的对应方法,比如Executors.newFixedThreadPool(int nThreads)
ThreadPoolExecutor是我们线程池核心类,首先看看线程池类的主要参数有哪些。
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory.
*/
public ThreadPoolExecutor(int corePoolSize, // 线程池的核心大小(最小线程池大小)
int maximumPoolSize, // 最大线程池大小
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 销毁时间单位
BlockingQueue<Runnable> workQueue, // 存储等待执行线程的工作队列
ThreadFactory threadFactory, // 创建线程的工厂
RejectedExecutionHandler handler) { // 拒绝策略(当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常。)
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
线程池工作流程
- 如果线程池中的线程小于corePoolSize时就会创建新线程直接执行任务
- 如果线程池中的线程大于corePoolSize时就会暂时把任务存储到工作队列workQueue中等待执行。
- 如果工作队列workQueue也满时,当线程数小于最大线程池数maximumPoolSize时就会创建新线程来处理,而线程数大于等于最大线程池数maximumPoolSize时就会执行拒绝策略。
newSingleThreadExecutor
创建一个单线程化的线程池(只有一个线程的线程池)
newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。(创建固定大小的线程池)
ExecutorService es = Executors.newFixedThreadPool(3);
newSheduledThreadPool
创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。 (创建一个可调度的线程池)
newCachedThreadPoo
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(创建不限线程数上限的线程池,任何提交的任务都会立即执行)
拒绝策略
AbortPolicy
简单粗暴,直接抛出拒绝异常,这也是默认的拒绝策略。
CallerRunsPolicy
如果线程池未关闭,则会在调用者线程中直接执行新任务,这会导致主线程提交线程性能变慢。
DiscardPolicy
从方法看没做任务操作,即表示不处理新任务,即丢弃。
DiscardOldestPolicy
抛弃最老的任务,就是从队列取出最老的任务然后放入新的任务进行执行。
提交线程
es.submit(xxRunnble); // submit返回一个Future对象,如果想知道线程结果就使用submit提交
es.execute(xxRunnble); //execute没有返回值,如果不需要知道线程的结果就使用execute方法,性能会好很多。
关闭线程
es.shutdown(); //不再接受新的任务,之前提交的任务等执行结束再关闭线程池。
es.shutdownNow(); //不再接受新的任务,试图停止池中的任务再关闭线程池,返回所有未处理的线程list列表。
总结
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到多个任务上,而且由于在请求到达时线程已经存在,所以消除线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足。