java基础-线程知识相关

该文章是参考很多文档总结得出,不喜勿喷,如有问题希望大神指出,多谢!

1>线程的生命周期:
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
尤其是当线程启动以后,它不能一直“霸占”着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)五种状态。


新建(new Thread)
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
如:Thread  t1=new Thread();
就绪(runnable)
线程已经被启动,正在等待被分配给CPU时间片,线程处于等待CPU分配资源阶段,也就是说此时线程正在就绪队列中排队等候得到CPU资源
如:t1.start();
运行(running)
线程获得CPU资源正在执行任务(run方法定义了线程的操作和功能),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
阻塞(blocked)
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;或者调用suspend()方法被另一个线程所阻塞(调用resume()方法进行恢复)。
死亡/销毁(dead)
如果线程正常执行run()完毕后或线程被提前强制性的终止(调用stop()方法)或出现异常导致结束,那么线程就要被销毁,释放资源;
2>线程状态的转变 
 1.处于Runnable状态的线程能发生哪些状态转变? 
Runnable状态的线程无法直接进入Blocked状态和Terminated状态的。只有处在Running状态的线程,换句话说,只有获得CPU调度执行权的线程才有资格进入Blocked状态和Terminated状态,Runnable状态的线程要么能被转换成Running状态,要么被意外终止。
 2.Running状态线程能发生哪些状态转变?
被转换成Terminated状态,比如调用 stop() 方法;
被转换成Blocked状态,比如调用了sleep, wait 方法被加入 waitSet 中;
被转换成Blocked状态,如进行 IO 阻塞操作,如查询数据库进入阻塞状态;
被转换成Blocked状态,比如获取某个锁的释放,而被加入该锁的阻塞队列中;
该线程的时间片用完,CPU 再次调度,进入Runnable状态;
线程主动调用 yield 方法,让出 CPU 资源,进入Runnable状态
 3.Blocked状态的线程能够发生哪些状态改变?
被转换成Terminated状态,比如调用 stop() 方法,或者是 JVM 意外 Crash;
被转换成Runnable状态,阻塞时间结束,比如读取到了数据库的数据后;
完成了指定时间的休眠,进入到Runnable状态;
正在wait中的线程,被其他线程调用notify/notifyAll方法唤醒,进入到Runnable状态;
线程获取到了想要的锁资源,进入Runnable状态;
线程在阻塞状态下被打断,如其他线程调用了interrupt方法,进入到Runnable状态;
3>线程执行过程中方法调用的区别?
    1)run()和start()方法的区别:
run()相当于线程的任务处理逻辑的入口方法,它由Java虚拟机在运行相应线程时直接调用,而不是由应用代码进行调用,run()是定义线程的执行体,调用run()会在当前线程中执行方法。
start()是开启一条新线程来执行方法,启动一个线程实际是请求Java虚拟机运行相应的线程,而这个线程何时能够运行是由线程调度器决定的。start()调用结束并不表示相应线程已经开始运行,这个线程可能稍后运行,也可能永远也不会运行。
    2)sleep() 和wait()
sleep是Thread类的静态方法,wait是Object类中定义的方法
Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep不会让线程释放锁,而wait 会释放当前线程锁,使得其他线程可以使用同步控制块或者同步方法(锁代码块和方法锁)。
Thread.sleep需要设定休眠时间,必须捕获异常。
Object.wait也可以设定wait时间,或者通过线程执行notify/notifyAll进行唤醒,wait,notify和notifyAll不需要捕获异常。

    3)notify和notifyAll
notify唤醒一个线程,如果有多线程在wait,可能是随机一个
notifyAll唤醒所有在等待的线程,
使用中尽量使用notifyAll,因为notify容易导致线程死锁(nofity只唤醒一个,其他等待的线程没有唤醒)
    4)yield() 和join()方法
yield():作用是放弃当前CPU资源,让给其他线程去使用,但是放弃时间不确定
join():在A线程中调用B线程的join()方法,表示当执行到此方法,A线程停止执行,直至B线程执行完毕,A线程再接着join()之后的代码执行。

public class MyThread extends Thread {

    @Override
    public void run(){

        for (int i = 0; i <50; i++) {
            System.out.println("************"+Thread.currentThread().getName()+":"+i);
        }
    }



    public static void main(String[] args) {
        MyThread st1 = new MyThread();
        st1.setName("MyThread");
        st1.start();
        Thread.currentThread().setName("==========Main");
        for(int i=1;i<=50 ;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            if(i == 20){
                try {
                    st1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

3> java中的多线程 
Java可以用三种方式来创建线程,如下所示:
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
1.通过继承Thread类来创建并启动多线程的一般步骤如下
1】定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2】创建Thread子类的实例,也就是创建了线程对象
3】启动线程,即调用线程的start()方法

代码实例:
public class MyThread extends Thread {
    @Override
    public void run(){
        for (int i = 0; i <50; i++) {
            System.out.println("************"+Thread.currentThread().getName()+":"+i);
        }
    }
}
public class Main {
  public static void main(String[] args){
    new MyThread().start();//创建并启动线程
  }
}
2.实现Runnable接口创建线程
通过实现Runnable接口创建并启动线程一般步骤如下:
1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3】通过调用线程对象的start()方法来启动线程
代码实例:
public class MyThread2 implements Runnable {
  @Override
    public void run(){
        for (int i = 0; i <50; i++) {
            System.out.println("************"+Thread.currentThread().getName()+":"+i);
        }
    }
}
public class Main {
  public static void main(String[] args){
    new Thread(new MyThread2()).start();
  }
}
3.使用Callable和Future创建线程
和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任务
>V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
>V 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()方法来获得子线程执行结束后的返回值
代码实例:
public class Main {
  public static void main(String[] args){
        CallableDemo callableDemo = new CallableDemo();
        //执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callableDemo);
        //开启线程
        new Thread(futureTask).start();
        System.out.println("当前线程ID 1:" + Thread.currentThread().getId());
        //接收线程运算后的结果
        try {
            //FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的
            Integer sum = futureTask.get();//通过futureTask可以得到CallableDemo的call()的运行结果
            System.out.println("当前线程ID 2:" + Thread.currentThread().getId());
            System.out.println(sum);
            System.out.println("------------------------------------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } 
  }
}
三种创建线程方法对比:
1、线程只是实现Runnable或实现Callable接口,还可以继承其他类;继承Thread类的线程类不能再继承其他父类。
2、使用继承Thread类的方式创建多线程,如果需要访问当前线程,直接使用this即可获得当前线程,而线程类只是实现了Runnable接口或Callable接口,则必须使用Thread.currentThread()方法。
3、Callable 和Runnable 实现的区别:
(1)Callable规定(重写)的方法是call(),而Runnable规定(重写)的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3)call()方法可抛出异常,而run()方法是不能抛出异常的。
(4)运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
通过Future对象可了解任务执行情况,可取消任务的执行,还可使用future.get()获取任务执行的结果。
Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。
注:一般推荐采用实现接口的方式来创建多线程。
4>线程池 
1》java创建线程池的四种方式:
newCachedThreadPool()创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool(int nThreads) 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()。
newScheduledThreadPool(int corePoolSize) 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor() 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
2》线程池的作用:
根据系统的环境情况,限制系统中执行线程的数量,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
3》为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
4》Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
1.线程池接口:ExecutorService为线程池接口,提供了线程池生命周期方法,继承自Executor接口,ThreadPoolExecutor为线程池实现类,提供了线程池的维护操作等相关方法,继承自AbstractExecutorService,AbstractExecutorService实现了ExecutorService接口。
2.线程池的体系结构:
java.util.concurrent.Executor 负责线程的使用和调度的根接口
        |--ExecutorService 子接口: 线程池的主要接口
                |--ThreadPoolExecutor 线程池的实现类
                |--ScheduledExceutorService 子接口: 负责线程的调度
                    |--ScheduledThreadPoolExecutor : 继承ThreadPoolExecutor,实现了ScheduledExecutorService
3.工具类:Executors
Executors为线程池工具类,相当于一个工厂类,用来创建合适的线程池,返回ExecutorService类型的线程池。如下方法:
ExecutorService newFixedThreadPool(int nThreads) : 创建固定大小的线程池
ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
ExecutorService newSingleThreadExecutor() : 创建单个线程池。 线程池中只有一个线程
ScheduledExecutorService newScheduledThreadPool(int corePoolSize) : 创建固定大小的线程,可以延迟或定时的执行任务
其中AbstractExecutorService是他的抽象父类,继承自ExecutorService,ExecutorService 接口扩展Executor接口,增加了生命周期方法。
实际应用中我一般都比较喜欢使用Exectuors工厂类来创建线程池,里面有五个方法,分别创建不同的线程池,如上,创建一个制定大小的线程池,Exectuors工厂实际上就是调用的ExectuorPoolService的构造方法,传入默认参数。

Executors 接口中JDK1.8新加的创建线程池方式:
newWorkStealingPool()
newWorkStealingPool(int parallelism)//创建一个具有抢占式操作的线程池,参数中传入的是一个线程并发的数量,该线程池不会保证任务的顺序执行
ExecutorService executorService = Executors.newWorkStealingPool(); //用的是 ForkJoinPool 类,
//创建单核心的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
//创建固定核心数的线程池,这里核心数 = 2
ExecutorService executorService = Executors.newFixedThreadPool(2);
//创建一个按照计划规定执行的线程池,这里核心数 = 2
ExecutorService executorService = Executors.newScheduledThreadPool(2);
//创建一个自动增长的线程池
ExecutorService executorService = Executors.newCachedThreadPool();

5》参数含义:
ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,RejectedExecutionHandler handler)                              
corePoolSize: 表示线程池核心线程数,当初始化线程池时,会创建核心线程进入等待状态,即使它是空闲的,核心线程也不会被摧毁,从而降低了任务一来时要创建新线程的时间和性能开销。
maximumPoolSize: 表示最大线程数,意味着核心线程数都被用完了,那只能重新创建新的线程来执行任务,但是前提是不能超过最大线程数量,否则该任务只能进入阻塞队列进行排队等候,直到有线程空闲了,才能继续执行任务。
keepAliveTime: 表示线程存活时间,除了核心线程外,那些被新创建出来的线程可以存活多久。意味着,这些新的线程一但完成任务,而后面都是空闲状态时,就会在一定时间后被摧毁。
unit:这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS
workQueue: 表示任务的阻塞队列,提交的任务将会被放到这个队列里,由于任务可能会有很多,而线程就那么几个,所以那么还未被执行的任务就进入队列中排队,队列我们知道是 FIFO 的,等到线程空闲了,就以这种方式取出任务。
threadFactory:线程工厂,用来创建线程,主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
handler:拒绝策略,当线程池里线程被耗尽,且队列也满了的时候会调用。
6》执行流程

任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。
5> Spring中的线程池 ThreadPoolTaskExecutor
两种实现方式:
<bean id="threadPoolTaskExecutor" scope="singleton" lazy-init="true" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
        <!-- 核心线程数 -->
        <property name="corePoolSize" value="4" />
        <!-- 最大线程数 -->
        <property name="maxPoolSize" value="8" />
        <!-- 队列最大长度 >=mainExecutor.maxSize -->
        <property name="queueCapacity" value="50" />
        <!-- 线程池对拒绝任务(无线程可用)的处理策略 ThreadPoolExecutor.CallerRunsPolicy策略 ,调用者的线程会执行该任务,如果执行器已关闭,则丢弃.  -->
        <property name="rejectedExecutionHandler">
            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
        </property>
    </bean>
或者:
@Configuration
public class ExecturConfig {
 @Bean("asyncTaskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setMaxPoolSize(MAX_POOL_SIZE);
        taskExecutor.setCorePoolSize(CORE_POOL_SIZE);
        taskExecutor.setThreadNamePrefix("async-task-thread-pool");
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.setQueueCapacity(CORE_POOL_SIZE * 300);
        taskExecutor.setKeepAliveSeconds(5);
        taskExecutor.initialize();
        return taskExecutor;
    }
}
线程池参数:corePoolSize、maxPoolSize、rejectedExecutionHandler、queueCapacity、keepAliveSeconds
rejectedExectutionHandler参数字段用于配置绝策略,常用拒绝策略如下:
AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。

6> ThreadLocal
ThreadLocal 叫线程本地变量或线程本地存储,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量
ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove 自己的变量,而不会影响其他线程的变量。

public T get() { }  //用来获取ThreadLocal在当前线程中保存的变量副本
public void set(T value) { }  //用来设置当前线程中变量的副本
public void remove() { }  //用来移除当前线程中变量的副本
protected T initialValue() { } //initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法

1.ThreadLocal实现原理
线程共享变量缓存如下:
Thread.ThreadLocalMap<ThreadLocal, Object>;
1)Thread: 当前线程,可以通过Thread.currentThread()获取。
2)ThreadLocal:我们的static ThreadLocal变量,本地变量。
3)Object: 当前线程共享变量。
我们调用ThreadLocal.get方法时,实际上是从当前线程中获取ThreadLocalMap<ThreadLocal, Object>,然后根据当前ThreadLocal获取当前线程共享变量Object。
ThreadLocal.set,ThreadLocal.remove实际上是同样的道理。
2.这种存储结构的好处:
1)线程死去的时候,线程共享变量ThreadLocalMap则销毁。
2)ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。
关于ThreadLocalMap<ThreadLocal, Object>弱引用问题:
当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。
虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。
1)使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
2)JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了
3.ThreadLocal是如何为每个线程创建变量的副本的?
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
1)在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
2)初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
3)然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
4.ThreadLocal的应用场景
最常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等。
数据库连接:
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
    public Connection initialValue() {  
        return DriverManager.getConnection(DB_URL);  
    }  
};    
public static Connection getConnection() {  
    return connectionHolder.get();  
}  
Session管理:
private static final ThreadLocal threadSession = new ThreadLocal();  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值