深入浅出java多线程编程

本文将从以下几个方面描述java多线程编程相关的内容。

  • 线程简介
  • 线程的状态与上下文切换的概念
  • 线程的监控
  • synchronize和volatile
  • 多线程的优点和缺点
  • 多线程的设计模式
  • 线程池
  • 线程简介

  进程代表运行中的程序。一个运行的java程序就是一个进程。

  从操作系统的角度来看,线程是进程中可独立执行的子任务。一个进程可以包含多个线程,同一个进程中的线程共享该进程所申请到的资源,如内存空间和文件句柄等。

  从JVM的角度来看,线程是进程中的一个组件,它可以看作执行java代码的最小单位。

  java中的线程可以分为守护线程和用户线程。用户线程会组织jvm的正常停止,即jvm正常停止前应用程序中的所有用户线程必须先停止完毕,否则jvm无法停止。而守护线程则不会影响jvm的正常停止。

  • 线程的状态与上下文切换的概念

  java线程的状态可以通过调用相应thread的getState方法获取。该方法的返回值类型Thread.State是一个枚举类型,包含的状态有以下几种。

  1. NEW

    1. 一个刚创建而未启动的线程处于该状态。由于一个线程实例只能被启动一次,因此一个线程只可能有一次处于该状态。  
  2. RUNNABLE
    1. 这是一个复合状态,包括READY和RUNNING。
    2. READY。表示该状态的线程可以被jvm的线程调度器进行调度而使之处于RUNNING状态。
    3. RUNNING。表示该线程正在运行,即相应线程的run方法正在被执行。当Thread实例的yield方法被调用时或由于线程调度器的原因,相应线程的状态会由RUNNING转为READY。  
  3. BLOCKED
    1. 一个线程发起了一个阻塞式io操作后,或者试图去获取以一个由其他线程持有的锁时,相应的线程会处于该状态。处于该状态的线程并不会占用CPU资源。当相应的io操作完成后,或者相应的锁被其他线程释放后,该线程的状态又可以转换为RUNNABLE。
  4. WAITING
    1. 一个线程执行了某些方法调用之后就会处于这种无限等待其他线程执行特定操作的状态。这些方法包括:Object.wait(),Thread.join()...能使相应线程从WAITING转换到RUNNABLE的相应方法包括:Object.notify(),Object.notifyAll()...
  5. TIMED_WAITING
    1. 与WAITING状态类似,差别在于等待时间非无限等待,指定时间过后,自动转为RUNNABLE。
  6. TERMINATED
    1. 已经执行结束的线程处于该状态。同NEW一样,有且仅有一次。run方法正常返回或者由于异常终止都会导致该状态。
  7. 上下文切换
    1. 由上述描述可知,一个线程的生命周期中,只可能一次处于NEW和TERMINATED状态。而一个线程的状态从RUNNABLE转换为BLOCKED,WAITING和TIME_WAITING状态中的任意一个都意味着上下文切换。
    2. 上下文切换的场景类似于我们接听手机,我们正在打电话时有另一个电话打进来,我们接听新的电话,而之前的电话就处于等待状态,等新的电话结束之后,我们回过头来与前者重新通话,“我们之前说道哪儿了?”
    3. 线程间的切换,状态变化需要对相应的上下文信息进行保存和恢复,这个过程就被称为上下文切换。
    4. 上下文切换会带来额外的开销,包括保存和恢复线程上下文信息的开销、对线程进行调度的CPU时间开销以及CPU缓存内容失效的开销。
    5. Linux平台下,我们可以使用perf命令来监视上下文切换情况。
    6. Window平台下,我们可以使用Window自带工具perfmon来监视上下文切换情况。
  • 线程的监控

    • jvisualvm、jstack、JMC
  • synchronized和volatile

    • 了解这两个关键字之前,我们需要先有以下几个概念,原子性、内存可见性和重排序。
    • 原子性。原子操作是指相应的操作是单一不可分割的操作。例如:count++就不是原子操作,因为该操作分为三步,1)读取count的值,2)count做++运算,3)把运算后的值赋予count。在多线程环境下,该操作可能会收到其他线程的干扰,导致我们不能得到想要的结果。
    • 内存可见性。CPU在执行代码的时候,为了减少变量访问的时间消耗,可能会将代码中访问的变量的值缓存到该CPU的缓存区。因此代码访问或者写入的变量,可能只是在缓存区而不是主内存。这就导致了一个CPU对变量的操作可能无法被其他CPU感知。
    • 重排序。编译器和CPU为了提高指令的执行效率可能会进行指令重排序,意思是一段代码的实际执行顺序会被重新排序。例如:People p = new People();正常地执行流程为:1)创建People的实例,2)将实例赋予变量p。但是由于指令重排的作用,实际实行顺序可能是:1)分配一段用于储存People实例的内存空间,2)将对该空间的引用赋值给变量p,3)创建People的实例。因此,当其他线程访问变量p时,可能此时p实例的初始化尚未完成。
    • synchronized关键字实现操作原子性的本质是通过该关键字所包括的临界区的排他性保证在同一时刻只有一个线程能执行临界区中的代码。该操作保证了原子性和内存可见性。
    • volatile关键字保证了内存可见性,即,一个线程对一个volatile关键字修饰的变量的值的更改对于其他访问该变量的线程总是可见的。其核心机制为当一个线程更改了volatile关键字修饰的变量的值时,该值会被写入主内存而不仅仅时该线程的CPU缓存区,而其他CPU的缓存区中储存的该变量的值就会失效。这就保证了当任意线程访问一个volatile修饰的值时,那一刻得到的值一定是最新的。但是如果在读取后,有线程对其进行了修改,就无法保证操作的原子性了。volatile关键字的另一个作用是它禁止了指令重排序。
    • synchronized关键字技能保证操作的原子性,也能保证内存可见性,但是会导致上下文切换。volatile关键字仅能保证内存可见性。
  • 多线程的优点和缺点
    • 优点
    • 提高系统的吞吐量。
    • 提高响应性。一个慢的操作不会影响其他请求的处理。
    • 充分利用多核CPU资源。
    • 最小化对系统资源的使用。一个进程中的多个线程可以共享该进程申请的资源(如内存空间)。
    • 简化程序的结构。
    • 缺点
    • 线程安全问题。多个线程共享数据必然会导致复杂度上升。
    • 线程的生命特征问题。多个线程在交互的过程中会出现无法充分使用线程的生命周期的问题,会导致一定程度上的浪费。
    • 上下文切换问题。频繁的上下文切换会增加对系统的消耗,不利于系统的吞吐量。
    • 可靠性问题。如果一个进程由于某种意外中止了,那么里面所有的线程都无法继续运行。
  • 多线程的设计模式
    • 多线程设计模式所解决的问题可以分为以下几类:

      • 不使用锁的情况下保证线程安全
      • 优雅的停止线程
      • 线程协作
      • 提高并发性
      • 提高响应性
      • 减少资源消耗
  • 线程池
    • 本来想自己整理,网上看到一篇博客关于线程池也相当细致,就不重复造轮子了,大家可以直接转链接:https://www.cnblogs.com/superfj/p/7544971.html

原文地址:https://www.cnblogs.com/keeplearningclc/p/10998624.html

时间: 2024-10-09 22:27:48

深入浅出java多线程编程的相关文章

《Java多线程编程核心技术》推荐

写这篇博客主要是给猿友们推荐一本书<Java多线程编程核心技术>. 之所以要推荐它,主要因为这本书写得十分通俗易懂,以实例贯穿整本书,使得原本抽象的概念,理解起来不再抽象. 只要你有一点点Java基础,你就可以尝试去阅读它,相信定会收获甚大! 博主之前网上找了很久都没完整pdf电子版的,只有不全的试读版,这里博主提供免费.清晰.完整版供各位猿友下载: http://download.csdn.net/detail/u013142781/9452683 刚刚已经提到,<Java多线程编程核

Java多线程编程模式实战指南(二):Immutable Object模式--转载

本文由本人首次发布在infoq中文站上:http://www.infoq.com/cn/articles/java-multithreaded-programming-mode-immutable-object.转载请注明作者: 黄文海 出处:http://viscent.iteye.com. 多线程共享变量的情况下,为了保证数据一致性,往往需要对这些变量的访问进行加锁.而锁本身又会带来一些问题和开销.Immutable Object模式使得我们可以在不使用锁的情况下,既保证共享变量访问的线程安

java多线程编程

一.多线程的优缺点 多线程的优点: 1)资源利用率更好2)程序设计在某些情况下更简单3)程序响应更快 多线程的代价: 1)设计更复杂虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂.在多线程访问共享数据的时候,这部分代码需要特别的注意.线程之间的交互往往非常复杂.不正确的线程同步产生的错误非常难以被发现,并且重现以修复. 2)上下文切换的开销当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指

Java 多线程编程两个简单的例子

/** * @author gao */ package gao.org; public class RunnableDemo implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10;i++){ System.out.println("新线程输出:"+i); } } public static void main(String []

Java多线程编程基础之线程对象

在进入java平台的线程对象之前,基于基础篇(一)的一些问题,我先插入两个基本概念. [线程的并发与并行] 在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent).而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel). 在上面包括以后的所有论述中,请各位朋友谅解,我无法用最准确的词语来定义储

拨开云雾见天日 —— Java多线程编程概念剖析

说到Java多线程编程,大多数人都会想到继承Thread或实现Runnable编程,new 一个Thread实例,调用start()方法,由OS调用即可.具体过程如下: public class MyThread extends Thread {     @Override     public void run() {         System.out.println("MyThread");     }     public static void main(String[] 

java多线程编程中实现Runnable接口方法相对于继承Thread方法的优势

 java多线程创建方法http://blog.csdn.net/cjc211322/article/details/24999163  java创建多线程方法之间的区别http://blog.csdn.net/cjc211322/article/details/25000449 java多线程编程中实现Runnable接口方法相对于继承Thread方法的优势

Java多线程编程(学习笔记)

一.说明 周末抽空重新学习了下多线程,为了方便以后查阅,写下学习笔记. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的.例如:程序中有两个子系统需要并发执行,这时候需要利用多线程编程. 通过多线程的使用,可以编写出非常高效的程序.但如果创建了太多的线程,程序执行的效率反而会降低. 同时上下文的切换开销也很重要,如果创建太多的线程,CPU花费在上下文的切换时间将对于执行程序的时间. 二.Java多线程编程 概念 在学习多线程时,我们应该首先明白另外一个概念. 进程:是计算机中的程序关于某

Java多线程编程详解

线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法:通过在方法声明中加入 synch