java 高级 – 线程,多线程,线程池概念。


前言

线程: 进程中负责程序执行的执行单元。一个进程中至少有一个线程。

多线程:解决多任务同时执行的需求,合理使用CPU资源(时间片)。多线程的运行是根据CPU切换(计数器,寄存器)完成,如何切换由CPU决定,因此多线程运行具有不确定性。

线程池:基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由线程池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。


定义线程

继承Thread自身变成一个线程;

class DemoThread extends Thread {

    @Override
    public void run() {
        super.run();
    }
}

DemoThread t = new DemoThread();
t.start();

通过继承一个线程Thrad,本身变成一个线程。重写run方法,定义线程执行方法;调用start()方法执行;


实现Runnable接口

public class Demo1 implements Runnable {

  
    @Override
    public void run() {

    }


}

class Demo2{
public static void mian(String [] args){

		Thread thread = new Thread(Demo1 );
		thred.start();

}
}

通过实现Runnable接口,复写Run方法,来定义线程。


用 Runnable 还是 Thread

Runnable是通过实现接口来实现,而Thread是通过自身变成线程来实现。
由于java不支持多继承,所以实现接口,更加轻量化。故使用Runnable比较合适;


线程的生命周期

java多线程int_多线程


java多线程int_多线程_02


创建一个线程,执行一个线程。线程进入就绪状态,被cpu选中执行,执行一半,进入等待,又被选中,准备就绪,执行。到最后执行结束,回归线程池或者执行结束失望;

等待状态可能会遇到,阻塞,死锁等状态;

线程池里面的线程,只有重启项目所有线程被kill清空;


多线程

java多线程int_java多线程int_03


synchronized

加上 synchronized 的方法和块语句,在多线程访问的时候,同一时刻只能有一个线程能够访问,可以在工具类创建一个OneByOneTemplate。

wait()、notify()、notifyAll()

这三个方法是 java.lang.Object 的 final native 方法,任何继承 java.lang.Object 的类都有这三个方法。

wait():

线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒,该方法只能在同步方法中调用。

notify():

随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态,该方法只能在同步方法或同步块内部调用。

notifyAll():

解除所有那些在该对象上调用wait方法的线程的阻塞状态,同样该方法只能在同步方法或同步块内部调用。

调用这三个方法中任意一个,当前线程必须是锁的持有者,如果不是会抛出一个 IllegalMonitorStateException 异常。


wait() 与 Thread.sleep(long time) 的区别

sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),该线程不丢失任何监视器的所属权,sleep() 是 Thread 类专属的静态方法,针对一个特定的线程。
wait() 方法使实体所处线程暂停执行,从而使对象进入等待状态,直到被 notify() 方法通知或者 wait() 的等待的时间到。sleep() 方法使持有的线程暂停运行,从而使线程进入休眠状态,直到用 interrupt 方法来打断他的休眠或者 sleep 的休眠的时间到。

wait() 方法进入等待状态时会释放同步锁,而 sleep() 方法不会释放同步锁。所以,当一个线程无限 sleep 时又没有任何人去 interrupt 它的时候,程序就产生大麻烦了,notify() 是用来通知线程,但在 notify() 之前线程是需要获得 lock 的。另个意思就是必须写在 synchronized(lockobj) {…} 之中。wait() 也是这个样子,一个线程需要释放某个 lock,也是在其获得 lock 情况下才能够释放,所以 wait() 也需要放在 synchronized(lockobj) {…} 之中。

ThreadLocal 变量

ThreadLocal 是Java里一种特殊的变量。拷贝,每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。

join() 方法

join() 方法定义在 Thread 类中,所以调用者必须是一个线程,join() 方法主要是让调用该方法的 Thread 完成 run() 方法里面的东西后,再执行 join() 方法后面的代码

Thread.sleep(long time): 线程暂时终止执行(睡眠)一定的时间。
Thread.yield(): 线程放弃运行,将CPU的控制权让出。


Executor

Java里面线程池的顶级接口是 Executor,不过真正的线程池接口是 ExecutorService, ExecutorService 的默认实现是 ThreadPoolExecutor;普通类 Executors 里面调用的就是 ThreadPoolExecutor。

照例看一下各个接口的源码:

public interface Executor {
    void execute(Runnable command);
}

public interface ExecutorService extends Executor {
    void shutdown();
    List<Runnable> shutdownNow();
    
    boolean isShutdown();
    boolean isTerminated();
    
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    ...
}

public class Executors {
    public static ExecutorService newCachedThreadPool() {
            return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, 
                            new SynchronousQueue<Runnable>());
    }
    ...
}

创建一个线程池

ExecutorService pool = Executors.newCachedThreadPool();

Executors 提供四种线程池:

newCachedThreadPool 是一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute() 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。

newSingleThreadExecutor 创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newFixedThreadPool 创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

newScheduledThreadPool 创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 
        threadFactory, defaultHandler);
}

corePoolSize:线程池的核心线程数,一般情况下不管有没有任务都会一直在线程池中一直存活,只有在 ThreadPoolExecutor 中的方法
allowCoreThreadTimeOut(boolean value) 设置为 true 时,闲置的核心线程会存在超时机制,如果在指定时间没有新任务来时,核心线程也会被终止,而这个时间间隔由第3个属性 keepAliveTime 指定。

maximumPoolSize:线程池所能容纳的最大线程数,当活动的线程数达到这个值后,后续的新任务将会被阻塞。

keepAliveTime:控制线程闲置时的超时时长,超过则终止该线程。一般情况下用于非核心线程,只有在 ThreadPoolExecutor 中的方法 allowCoreThreadTimeOut(boolean value) 设置为 true时,也作用于核心线程。

unit:用于指定 keepAliveTime 参数的时间单位,TimeUnit 是个 enum 枚举类型,常用的有:TimeUnit.HOURS(小时)、TimeUnit.MINUTES(分钟)、TimeUnit.SECONDS(秒) 和 TimeUnit.MILLISECONDS(毫秒)等。

workQueue:线程池的任务队列,通过线程池的 execute(Runnable command) 方法会将任务 Runnable 存储在队列中。

threadFactory:线程工厂,它是一个接口,用来为线程池创建新线程的。


线程池的关闭

ThreadPoolExecutor 提供了两个方法,用于线程池的关闭,分别是 shutdown()shutdownNow()。

shutdown():不会立即的终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。


Executor 框架

java多线程int_java多线程int_04


Executor框架在Java 5中被引入,Executor 框架是一个根据一组执行策略调用、调度、执行和控制的异步任务的框架。

无限制的创建线程会引起应用程序内存溢出,所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用 Executor 框架可以非常方便的创建一个线程池。