Java之线程总结

线程vs进程

进程是资源分布的基本单位,一个进程中可以有多个线程,多个线程可以共享进程资源,在Java进程中,堆是共享的,因此需要保证线程安全,可通过-Xms指定Heap初始化大小,-Xmx指定Heap的最大值。

线程是调度的基本单位,直接理解为可获取CPU时间片执行的代码,每个线程也有私有栈,默认大小为1M,太小会报StackOverFlow, 可通过-Xss指定栈大小,也可以new Thread是通过参数指定大小。

jinfo pid可查看进程启动信息、环境变量、classpath的信息。

jmap -heap pid可查看堆内部分布情况。

线程产生

定义线程,也就是定义获取CPU时间片时执行的代码,java中实现线程又两种方式:

  • 继承Thread类
  • 实现Runabe接口

继承Thread类

首先看看Thread类主要方法:

//构造函数,指定Thread初始化属性
public Thread();
public Thread(Runnable target);
public Thread(Runnable target, String name);
public Thread(String name);
public Thread(ThreadGroup group, Runnable target);
......


//获取当前正在运行线程对应的Thread对象
public static native Thread currentThread();

//打印堆栈
public static void dumpStack();

public ClassLoader getContextClassLoader();

public final void setPriority(int newPriority);

    //设置内部未被捕获的异常处理类
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh);

//调用sleep一定是当前正在运行的线程,所以为staic,与具有实例无关,yeid方法同理
public static native void sleep(long millis) throws InterruptedException;
//释放CPU
public static native void yield();

public void interrupt();

public boolean isInterrupted();

//等待线程结束
public final void join() throws InterruptedException;

//线程执行体,target为Runnable
public void run() {
    if (target != null) {
        target.run();
    }
}

//启动线程方法,os内部会创建一个线程,与当前Thread实例关联,并且将此线程放入可调度执行队列中
public synchronized void start();

//设置守护线程
public final void setDaemon(boolean on);

Thread类提供了获取线程属性、ClassLoader、控制线程生命周期内各状态相互转换以及跟OS交互获取正在运行线程信息的相关函数。

new Thread Object时可指定线程名、线程执行逻辑、stacksize、线程组信息,也可采用默认值,通过set方法进行熟悉设置,同样可通过get方法获取这些属性,进程内每个线程都会分配一个id.

继承Thread类时,需重写run方法,然后调用start方法。

class AThread extends Thread {
     public void run() {
         // thread logic
          . . .
     }
 }

 Thread p = new AThread();
 p.start();

实现Runabe接口

class ARun implements Runnable {
     public void run() {
         // thread logic
          . . .
     }
}

 Runnable p = new ARun();
 new Thread(p).start();

两种方式其实都是重载run()方法,在run方法内部定义执行逻辑,两种方式最终都需要生成一个Thread实例,然后调用start()方法让OS启动线程,最终调用Thread实例的run方法,start方法是线程启动的唯一入口。

一般采用实现Runnable方式,这样可以继承其他类,更加灵活。

FutureTask

FutureTask实现了Runnable接口,Thread.start方法最终会调用FutureTask.run()方法,因此也属于继承Runnable方式。

FutureTask同时还实现了Future接口,因此可以更多线程执行信息。

线程阻塞

阻塞后的线程不能被调度执行,java中有几种阻塞线程的方式:

  • synchronized同步阻塞
  • Object的wait方法等待阻塞
  • Thread sleep方法睡眠阻塞
  • join方法产生join阻塞
  • IO阻塞

wait后会释放锁对象,sleep不会释放线程持有的锁对象。

阻塞中的线程退出阻塞有两种方式:

  • 条件满足后退出阻塞比如sleep时间到,synchronized获取到锁等
  • 线程被中断退出阻塞,但是synchronized阻塞是无法被中断的,reentrantLock.lock()也无法被中断,reentrantLock.lockInterruptibly()可被中断

线程中断

通过Thread实例的interrupt()方法可中断线程,设置线程中断标志位为true,线程检测到被中断后,如何响应取决与其自身

根据JavaDoc对interrupt方法的解释:

  • 如果线程处于wait、sleep以及jion三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,这些方法抛出一个InterruptedException
  • 如果是java.nio.channels.InterruptibleChannel进行的io操作引起的阻塞,则线程会收到一个ClosedByInterruptedException,但不会改变中断标志
  • 如果是轮询(java.nio.channels.Selectors)引起的线程阻塞,则立即返回,不会抛出异常

如果运行中的线程被interrupt()则仅仅是将标志位设为true,当线程再次处于阻塞状态后发现标志为true时会按上面三种情况进行处理,当然如果一直没有被阻塞,则不会有任何影响,但一般情况下会通过isInterrupted()判断线程是否被中断,如果中断则结束线程。

关于中断更详细参考:

https://www.cnblogs.com/onlywujun/p/3565082.html

主动放弃cpu时间片

Thread.yield()静态方法,是否执行权只有对当前正在执行的线程有效,因此是静态方法才有意义。

public static native void yield()

线程结束

Thread本身有一个stop方法,但是强制关闭,无任何清理动作,已Deprecated,目前有两种正确关闭方式:

  • volatile共享变量,线程中检查共享变量状态决定是否退出
  • 共享变量的方式在线程阻塞时无法检查到变量值,但阻塞时可检测到interrupt标志,因此可通过检测interrupt标志或是否捕获到InterruptException决定是否退出线程

线程同步

线程同步基本都是基于锁或Queue,可参考锁总结

Daemon线程

守护线程,后台服务线程,为非Daemon线程服务,当非守护线程结束后才退出,比如gc线程

线程组

Thread构造函数中可以指定线程组,同一个线程组具有一些相同线程属性,构造Thread时可以从线程组继承这些属性,比如Priority,isDaemon,ClassLoader等。

线程池

Executor

抽象任务的执行,解耦任务的提交与调度执行,因此常用于替代new Thread(Runnable).start().

public interface Executor {
    //执行一个Runnable对象,并没表名是如何执行,一个线程or线程池,甚至同步执行
    void execute(Runnable command);
}

ExecuteService

相对Executor,提供了提交任务以及关闭任务的接口

public interface ExecutorService extends Executor {

    void shutdown();
    List<Runnable> shutdownNow();

    boolean isShutdown();
    boolean isTerminated();


    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;

    //通过submit获取Future
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);


    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;

    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

ThreadPoolExecutor

实现了ExecuteService的线程池,因此可以真正起线程执行提交的task.

//corePoolSize最少线程数,maximumPoolSize最大线程数,keepAliveTime超过corePoolSize的线程空闲时间,workQueue执行线程存放的队列
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                          BlockingQueue<Runnable> workQueue);

ScheduledExecutorService

增加了定期和延时调度的接口

public interface ScheduledExecutorService extends ExecutorService {

    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);


    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);


    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);


    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
}

ScheduledThreadPoolExecutor

实现了ScheduledExecutorService,可以周期性执行任务。

public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
          new DelayedWorkQueue());
}

Executors

Executors是线程池工厂,提供了创建线程池的接口,利用Exectors创建各种类型线程池,我们负责向线程池提交任务即可。

//线程池大小为1
public static ExecutorService newSingleThreadExecutor();
//线程池大小为nThreads
public static ExecutorService newFixedThreadPool(int nThreads);
//不限定线程池大小
public static ExecutorService newCachedThreadPool();
//指定scheduledPoll的corePoolSize, 不限定poll上线
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);

ForkJoinPool

实现了ExecutorService,用于将一个任务切分成多个小任务执行,然后将执行结果汇总,用于执行可切分的任务。

Callable

Runnable无返回值,Callable是提供返回值类型任务,Runnable其实是返回值为null的Callable

public interface Callable<V> {
   //返回V类型值
    V call() throws Exception;
}

Callable是如何被执行?其实提交后内部将包装成FutureTask, 而FutureTask是Runnable,可以被线程执行,FutureTask的run()实现中调用了Callable.call()方法。

Future

提供控制任务状态,获取任务结果接口。

public interface Future<V> {

    boolean cancel(boolean mayInterruptIfRunning);

    boolean isCancelled();

    boolean isDone();

    V get() throws InterruptedException, ExecutionException;

    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

FutureTask

实现了RunnableFuture接口,即同时实现了Runnable和Future接口,因此可以提交到线程池中执行,其实不管是Runnable还是Callable,最终都被封装成FutureTask通过ExecuteService.submit方法提交执行。因为实现了Future接口,因此可以通过FutureTask获取线程池中任务的执行状态,获取返回值等。

java.util.concurrent.atomic.Atomic类

为方便多线程下数据安全,JDK对一些基本类型提供了Atomic类,可原子性操作Atomic类型变量, 内部是volatile变量加CAS操作。

AtomicBoolean

public final boolean get();
public final void set(boolean newValue);
public final void lazySet(boolean newValue);
public final boolean getAndSet(boolean newValue);
public final boolean compareAndSet(boolean expect, boolean update);
public boolean weakCompareAndSet(boolean expect, boolean update);

AtomicInteger

除了AtomicBoolean提供了方法外,还增加了可加减相关的原子操作。

public final int incrementAndGet()
public final int decrementAndGet()
public final int getAndIncrement()
public final int getAndDecrement() 

public final int addAndGet(int delta)
public final int getAndAdd(int delta)

AtomicLong

与AtomicInteger类似

AtomicIntegerArray

对数组中的某个元素实现了原子操作,接口与AtomicInteger类似,只是多了个参数index.

public final int getAndSet(int i, int newValue);
public final boolean compareAndSet(int i, int expect, int update);
public final int getAndIncrement(int i);

AtomicLongArray

与AtomicIntegerArray类似

AtomicReference

包装了volatile 引用类型变量,对引用原子操作

AtomicReferenceArray

同理,AtomicReferenceArray接口中带有参数index

AtomicMarkableReference

维持一个引用和对应的boolean型标志作为一个整体的原子操作

AtomicStampedReference

与AtomicMarkableReference类似,只是与Reference作为整体的类型为integer

ThreadLocal

线程私有的变量,即每个线程都有一份数据,一般这种数据初次赋值后就不会再改变,比如userid等。

public class ThreadLocal<T> {

        public ThreadLocal();
        //构造是重写此方法
        protected T initialValue() {
            return null;
        }

        public T get();

        public void set(T value);

        public void remove();
}

通过源码很容量理解ThreadLocal实现原理,在每个Thread对象中有个成员变量ThreadLocal.ThreadLocalMap,此Map中存储了ThreadLocal class变量到到线程持有私有数据的映射,因此,对每个Thread Object,ThreadLocal.ThreadLocalMap是唯一的,不会共享,并且可存储多个不同类型私有数据。

一般ThreadLocal变量声明为private static,ThreadLocal 实例所有线程共享,只是使用其hashKey到每个线程私有的Map中索引到具体位置,然后存放私有数据。

java.util.SimpleDateFormat内部使用了成员变量做缓冲,是非线程安全的,多线程时作为成员变量需变成ThreadLocal保证线程安全,要不然会出现时间顺序错乱。

并发编程指南

http://www.importnew.com/26461.html

猜你喜欢

转载自blog.csdn.net/jinjiating/article/details/78688241
今日推荐