java中的线程

java语言里的线程本质上就是操做系统的线程,他们是一 一对应的java

线程生命周期

线程状态转换图—— 五态模型
  1. 初始状态: 线程已经被建立,可是尚未分配CPU执行。 这个状态属于编程语言特有的,不过这里所谓的被建立,仅仅时在编程语言层面被建立,而在操做系统层面,真正的线程还没建立。
  2. 可运行状态:初始状态线程执行start()方法,线程具有CPU的执行资格,没有CPU的执行权。 这种状态下,真正的操做系统线程已经被成功建立了,因此能够分配CPU执行了。
  3. 运行状态: 处于可运行状态的线程获得CUP执行权。 当有空闲的CPU时,操做系统会将其分配给一个处于可运行状态的线程,被分配到CPU的线程状态就转换成了运行状态。
  4. 休眠状态:运行状态的线程释放CPU的执行权。 运行状态的线程若是调用一个阻塞的API(sleep,wait)或者等待某个事件(例如条件变量),那么线程的状态就会转换到休眠状态,同时释放CPU使用权,休眠状态的线程永远没有机会得到 CPU使用权。当等待的事件出现了(sleep到期,wait执行notif),线程就会从休眠状态转换到可运行状态。
  5. 终止状态: 线程执行完或者出现异常或者调用API强制结束。

java中的线程周期

Java语言中线程共有六种状态,分别是:算法

  1. NEW(初始化状态) : Java刚建立出来的Thread对象。
  2. RUNNABLE(可运行/运行状态) : Thread执行start()方法。
  3. BLOCKED(阻塞状态),WAITING(无时限等待),TIMED_WAITING(有时限等待) : RUNNABLE状态的线程调用wait()、join()、sleep()方法。
  4. TERMINATED(终止状态): run()方法执行完,或者意外中断。

BLOCKED,WAITING,TIMED_WAITING 是上面提到的休眠状态。Java线程处于这些状态那么这个线程就永远没有CPU的使用权。编程

线程能够经过isInterrupted()方法,检 测是否是本身被中断了。安全

建立一个线程

Java刚建立出来的Thread对象就是NEW状态,而建立Thread对象主要有两种方法。一种是继承Thread对 象,重写run()方法。示例代码以下:多线程

方式一: 继承Thread并发

//	⾃定义线程对象 
class MyThread extends Thread{
    public void run() {				
        //	线程须要执⾏的代码	
    } 
} 
//	建立线程对象 
MyThread myThread =	new	MyThread();

方式二: 实现Runnable接口异步

//实现Runnable接⼝ 
class Runner implements Runnable {		
    @Override		
    public void run(){				
        //线程须要执⾏的代码		
    } 
} 
//建立线程对象 
Thread thread = new Thread(new Runner());

方式三:实现Callable接口编程语言

//实现Runnable接⼝ 
class Runner implements Callable<String> {		
    @Override
     public String call() throws Exception {
          //线程须要执⾏的代码		
         return null;
     }
}

//建立 FutureTask
 FutureTask<String> ft1 = new FutureTask(new Runner());
//执行这个任务
Thread t1 = new Thread(ft1);
t1.start();
//获取返回值
t1.get();

方法三实质上也是实现了Runnable接口,由于FutureTask实现了Runnable接口ide

Future接口提供的方法:性能

//	取消任务 
boolean	cancel(boolean	mayInterruptIfRunning); 
//	判断任务是否已取消		
boolean	isCancelled(); 
//	判断任务是否已结束 
boolean	isDone(); 
//	得到任务执⾏结果 
get(); 
//	得到任务执⾏结果,⽀持超时 
get(long timeout, TimeUnit unit);

这两个get()方法都是阻塞式的,若是被调用的时候,任务尚未执行完,那么调用get()方法的线程会阻塞,直到任务执行完才会被唤醒。

  • Java刚建立出来的Thread对象就是NEW状态
  • NEW状态的线程不会被操做系统调度,所以不会执行
  • NEW状态的线程调用start()会进入RUNNABLE状态

stop()与interrupt()的区别

stop()会杀死线程,若是线程持有ReentrantLock锁,被stop()的线程并不会自动调用ReentrantLock的unlock()去释放锁,那其余线程就再也没机会得到ReentrantLock锁。因此该方法就不建议使用了,相似的方法还有suspend()和resume()方法,这两个方法一样也都不建议使用。

interrupt()仅仅是通知线程,线程有机会执行一些后续操做,同时也能够无视这个通知。

为何要使用多线程

提升程序的性能: 下降延迟,提升吞吐量。

提升性能的方式:1优化算法;2将硬件的性能发挥到极致

在并发编程领域,提高性能本质上就是提高硬件的利用率,具体来讲就是提高I/O的利用率和CPU的利用率。

若是CPU和I/O设备的利用率都很低,那么能够尝试经过增长线程提升吞吐量。

建立多少线程合适?

咱们的成语通常都是CPU计算和I/O操做交叉执行的,因为I/O设备的速度相对于CPU来讲都是很慢的,因此大部分状况下,I/O操做的执行时间相对于CPU计算来讲都很是长,这种场景咱们通常都成为I/O密集型程序和CPU密集型程序,计算最近线程数的方法是不一样的。

对于CPU密集型的计算场景,理论上“线程的数量-CPU核数”就是最合适的。不过在工程上,线程的数量通常会设置为"CPU核数+1" ,这样的话,当线程由于偶尔的内存页失效或其余缘由致使阻塞时,这个额外的线程能够顶上,从而保证CPU的利用率。

对于I/O密集型的计算场景,好比前面咱们的例子中,若是CPU计算和I/O操做的耗时是1:1,那么2个线程是 最合适的。若是CPU计算和I/O操做的耗时是1:2,那多少个线程合适呢?是3个线程,以下图所示:CPU在 A、B、C三个线程之间切换,对于线程A,当CPU从B、C切换回来时,线程A正好执行完I/O操做。这样CPU 和I/O设备的利用率都达到了100%。

更多的精力其实应该放在算法的优化上,线程池的配置,按照经验配置一个,随时关注线程池大小对程序 的影响便可,具体作法:能够为你的程序配置一个全局的线程池,须要异步执行的任务,扔到这个全局线 程池处理,线程池大小按照经验设置,每隔一段时间打印一下线程池的利用率,作到内心有数。

设置线程数的原则: 将硬件的性能发挥到极致。

为何局部变量是线程安全的?

调用栈结构

线程与调用栈的关系图

每一个线程都有本身的调用栈,局部变量保存在线程各自的调用栈里面,不会共享,因此天然也就没有并发问题。

局部变量的做用域是方法内部,也就是说当方法执行完,局部变量就没用了,局部变量和方法同生共死。

局部变量是和方法同生共死的,一个变量若是想跨越方法的边界,就必须建立在堆里。

调用栈与线程

两个线程能够同时用不一样的参数调用相同的方法。

每一个线程都有本身独立的调用栈。

线程封闭 : 仅在单线程内访问数据。不存在多线程的数据共享。

栈溢出的缘由:

由于每调用一个方法就会在栈上建立一个栈帧,方法调用结束后就会弹出该栈帧,而栈的大小不是无限的 ,因此递归调用次数过多的话就会致使栈溢出。而递归调用的特色是每递归一次,就要建立一个新的栈帧 ,并且还要保留以前的环境(栈帧),直到遇到结束条件。因此递归调用必定要明确好结束条件,不要出现死循环,并且要避免栈太深。

解决方法:

  1. 简单粗暴,不要使用递归,使用循环替代。缺点:代码逻辑不够清晰;
  2. 限制递归次数;
  3. 使用尾递归,尾递归是指在方法返回时只调用本身自己,且不能包含表达式。编译器或解释器会把尾递归作优化,使递归方法不论调用多少次,都只占用一个栈帧,因此不会出现栈溢出。然而,Java没有尾递归优化。

**** 码字不易若是对你有帮助请给个关注****

**** 爱技术爱生活 QQ群: 894109590****

相关文章
相关标签/搜索