【dz模块源码】【dubbo源码 书籍】【linux源码注解】jvm线程源码_jvm 线程

时间:2024-11-30 00:22:31 来源:java im源码 编辑:主力连续增仓指标源码

1.JAVA如何获取jvm中的线线程所有线程?
2.Java基础:Java虚拟机(JVM)
3.java线程池(一):java线程池基本使用及Executors
4.jvm中一个线程调用sleep(100),然后发生stw(stop-the-world
5.JVM调优-CMS常见参数、线程计算与推荐配置
6.JAVA 线程

jvm线程源码_jvm 线程

JAVA如何获取jvm中的程源所有线程?

       在Java中,你可以通过Java的线线程java.lang.management包获取JVM中的所有线程。这个包提供了一些用于管理和监视Java虚拟机的程源工具。具体来说,线线程你可以使用ThreadMXBean接口来获取线程信息。程源dz模块源码

       以下是线线程一段示例代码,演示如何获取和打印JVM中的程源所有线程:

       java复制代码

       import java.lang.management.ManagementFactory;

       import java.lang.management.ThreadInfo;

       import java.lang.management.ThreadMXBean;

       public class Main {

       public static void main(String[] args) {

       // 获取ThreadMXBean

       ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

       // 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息

       ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false,线线程 false);

       // 遍历线程信息,仅打印线程ID和线程名称信息

       for (ThreadInfo threadInfo : threadInfos) {

       System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());

       }

       }

       }

       这段代码首先通过ManagementFactory.getThreadMXBean()获取ThreadMXBean实例,程源然后调用dumpAllThreads()方法获取所有线程的线线程ThreadInfo,最后遍历并打印所有线程的程源ID和名称。

       需要注意的线线程是,dumpAllThreads()方法会返回一个ThreadInfo数组,程源每个ThreadInfo代表一个线程,线线程包含了关于该线程的大量信息,包括线程ID、线程名称、线程状态、线程堆栈信息等。在上面的示例代码中,我们只打印了线程ID和线程名称,但你可以根据需要打印其他信息。

Java基础:Java虚拟机(JVM)

       JVM是Java Virtual Machine的缩写,它是一种基于计算设备的规范,提供虚拟机功能,屏蔽了操作系统平台信息,dubbo源码 书籍实现Java语言在不同平台运行时的平台无关性。通过JVM,Java实现一次编译多处运行。

       Java运行环境(JRE)是JVM的运行平台,包括虚拟机平台和虚拟机本体(JVM)。Java开发工具包(JDK)则为Java的开发提供工具,依赖于JRE,通常JDK安装会附带一个JRE。

       JVM结构包括:程序计数器、Java堆、Java虚拟机栈、本地方法栈、方法区。程序计数器用于指示代码执行位置,每个线程有独立内存区域。Java虚拟机栈和本地方法栈与线程生命周期相同,用于方法执行的内存模型和本地方法服务。Java堆是共享区域,用于存放对象实例,垃圾回收的主要区域。方法区用于存储已加载类信息、常量、静态变量、编译后代码,非堆区,可能存在内存回收。linux源码注解代码缓存用于存储编译后的原生代码。类信息包括运行时常量池和方法数据。

       Java垃圾回收通过收集内存中不再使用的对象,减少内存泄漏和程序错误。根据对象的生命周期特征,采用新生代和旧生代的方式进行收集,减少对应用的暂停时间。不同对象引用类型采用不同方法进行回收。

       JVM线程与原生线程的关系:JVM允许程序使用多个并发线程,与操作系统原生线程直接映射。Java线程结束时,操作系统线程被回收。操作系统调度所有线程,分配到可用CPU上。线程结束时释放所有资源。

java线程池(一):java线程池基本使用及Executors

       @[toc] 在前面学习线程组的时候就提到过线程池。实际上线程组在我们的日常工作中已经不太会用到,但是线程池恰恰相反,是我们日常工作中必不可少的工具之一。现在开始对线程池的使用,以及底层ThreadPoolExecutor的源码进行分析。

1.为什么需要线程池

       我们在前面对线程基础以及线程的生命周期有过详细介绍。一个基本的常识就是,线程是一个特殊的对象,其底层是依赖于JVM的native方法,在jvm虚拟机内部实现的仙剑世界源码。线程与普通对象不一样的地方在于,除了需要在堆上分配对象之外,还需要给每个线程分配一个线程栈、以及本地方法栈、程序计数器等线程的私有空间。线程的初始化工作相对于线程执行的大多数任务而言,都是一个耗时比较长的工作。这与数据库使用一样。有时候我们连接数据库,仅仅只是为了执行一条很小的sql语句。但是在我们日常的开发工作中,我们的绝大部分工作内容,都会分解为一个个短小的执行任务来执行。这样才能更加合理的复用资源。这种思想就与我们之前提到的协程一样。任务要尽可能的小。但是在java中,任务不可能像协程那样拆分得那么细。那么试想,如果说,有一个已经初始化好的很多线程,在随时待命,那么当我们有任务提交的时候,这些线程就可以立即工作,无缝接管我们的任务请求。那么效率就会大大增加。modbus top 源码这些个线程可以处理任何任务。这样一来我们就把实际的任务与线程本身进行了解耦。从而将这些线程实现了复用。 这种复用的一次创建,可以重复使用的池化的线程对象就被成为线程池。 在线程池中,我们的线程是可以复用的,不用每次都创建一个新的线程。减少了创建和销毁线程的时间开销。 同时,线程池还具有队列缓冲策略,拒绝机制和动态线程管理。可以实现线程环境的隔离。当一个线程有问题的时候,也不会对其他的线程造成影响。 以上就是我们使用线程池的原因。一句话来概括就是资源复用,降低开销。

2.java中线程池的实现

       在java中,线程池的主要接口是Executor和ExecutorService在这两个接口中分别对线程池的行为进行了约束,最主要的是在ExecutorService。之后,线程池的实际实现类是AbstractExecutorService类。这个类有三个主要的实现类,ThreadpoolExecutorService、ForkJoinPool以及DelegatedExecutorService。

       后面我们将对这三种最主要的实现类的源码以及实现机制进行分析。

3.创建线程的工厂方法Executors

       在java中, 已经给我们提供了创建线程池的工厂方法类Executors。通过这个类以静态方法的模式可以为我们创建大多数线程池。Executors提供了5种创建线程池的方式,我们先来看看这个类提供的工厂方法。

3.1 newFixedThreadPool/** * Creates a thread pool that reuses a fixed number of threads * operating off a shared unbounded queue.At any point, at most * { @code nThreads} threads will be active processing tasks. * If additional tasks are submitted when all threads are active, * they will wait in the queue until a thread is available. * If any thread terminates due to a failure during execution * prior to shutdown, a new one will take its place if needed to * execute subsequent tasks.The threads in the pool will exist * until it is explicitly { @link ExecutorService#shutdown shutdown}. * * @param nThreads the number of threads in the pool * @return the newly created thread pool * @throws IllegalArgumentException if { @code nThreads <= 0} */public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}

       这个方法能够创建一个固定线程数量的无界队列的线程池。参数nthreads是最多可同时处理的活动的线程数。如果在所有线程都在处理任务的情况下,提交了其他的任务,那么这些任务将处于等待队列中。直到有一个线程可用为止。如果任何线程在关闭之前的执行过程中,由于失败而终止,则需要在执行后续任务的时候,创建一个新的线程来替换。线程池中的所有线程都将一直存在,直到显示的调用了shutdown方法。 上述方法能创建一个固定线程数量的线程池。内部默认的是使用LinkedBlockingQueue。但是需要注意的是,这个LinkedBlockingQueue底层是链表结构,其允许的最大队列长度为Integer.MAX_VALUE。

public LinkedBlockingQueue() { this(Integer.MAX_VALUE);}

       这样在使用的过程中如果我们没有很好的控制,那么就可能导致内存溢出,出现OOM异常。因此这种方式实际上已经不被提倡。我们在使用的过程中应该谨慎使用。 newFixedThreadPool(int nThreads, ThreadFactory threadFactory)方法:

/** * Creates a thread pool that reuses a fixed number of threads * operating off a shared unbounded queue, using the provided * ThreadFactory to create new threads when needed.At any point, * at most { @code nThreads} threads will be active processing * tasks.If additional tasks are submitted when all threads are * active, they will wait in the queue until a thread is * available.If any thread terminates due to a failure during * execution prior to shutdown, a new one will take its place if * needed to execute subsequent tasks.The threads in the pool will * exist until it is explicitly { @link ExecutorService#shutdown * shutdown}. * * @param nThreads the number of threads in the pool * @param threadFactory the factory to use when creating new threads * @return the newly created thread pool * @throws NullPointerException if threadFactory is null * @throws IllegalArgumentException if { @code nThreads <= 0} */public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);}

       这个方法与3.1中newFixedThreadPool(int nThreads)的方法的唯一区别就是,增加了threadFactory参数。在前面方法中,对于线程的创建是采用的默认实现Executors.defaultThreadFactory()。而在此方法中,可以根据需要自行定制。

3.2 newSingleThreadExecutor/** * Creates an Executor that uses a single worker thread operating * off an unbounded queue. (Note however that if this single * thread terminates due to a failure during execution prior to * shutdown, a new one will take its place if needed to execute * subsequent tasks.)Tasks are guaranteed to execute * sequentially, and no more than one task will be active at any * given time. Unlike the otherwise equivalent * { @code newFixedThreadPool(1)} the returned executor is * guaranteed not to be reconfigurable to use additional threads. * * @return the newly created single-threaded Executor */public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}

       此方法将会创建指有一个线程和一个无届队列的线程池。需要注意的是,如果这个执行线程在执行过程中由于失败而终止,那么需要在执行后续任务的时候,用一个新的线程来替换。 那么这样一来,上述线程池就能确保任务的顺序性,并且在任何时间都不会有多个线程处于活动状态。与newFixedThreadPool(1)不同的是,使用newSingleThreadExecutor返回的ExecutorService不能被重新分配线程数量。而使用newFixExecutor(1)返回的ExecutorService,其活动的线程的数量可以重新分配。后面专门对这个问题进行详细分析。 newSingleThreadExecutor(ThreadFactory threadFactory) 方法:

/** * Creates an Executor that uses a single worker thread operating * off an unbounded queue, and uses the provided ThreadFactory to * create a new thread when needed. Unlike the otherwise * equivalent { @code newFixedThreadPool(1, threadFactory)} the * returned executor is guaranteed not to be reconfigurable to use * additional threads. * * @param threadFactory the factory to use when creating new * threads * * @return the newly created single-threaded Executor * @throws NullPointerException if threadFactory is null */public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory));}

       这个方法与3.3中newSingleThreadExecutor的区别就在于增加了一个threadFactory。可以自定义创建线程的方法。

3.3 newCachedThreadPool/** * Creates a thread pool that creates new threads as needed, but * will reuse previously constructed threads when they are * available.These pools will typically improve the performance * of programs that execute many short-lived asynchronous tasks. * Calls to { @code execute} will reuse previously constructed * threads if available. If no existing thread is available, a new * thread will be created and added to the pool. Threads that have * not been used for sixty seconds are terminated and removed from * the cache. Thus, a pool that remains idle for long enough will * not consume any resources. Note that pools with similar * properties but different details (for example, timeout parameters) * may be created using { @link ThreadPoolExecutor} constructors. * * @return the newly created thread pool */public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE,L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}

       这个方法用来创建一个线程池,该线程池可以根据需要自动增加线程。以前的线程也可以复用。这个线程池通常可以提高很多执行周期短的异步任务的性能。对于execute将重用以前的构造线程。如果没有可用的线程,就创建一个 新的线程添加到pool中。秒内,如果该线程没有被使用,则该线程将会终止,并从缓存中删除。因此,在足够长的时间内,这个线程池不会消耗任何资源。可以使用ThreadPoolExecutor构造函数创建具有类似属性但是详细信息不同的线程池。 ?需要注意的是,这个方法创建的线程池,虽然队列的长度可控,但是线程的数量的范围是Integer.MAX_VALUE。这样的话,如果使用不当,同样存在OOM的风险。比如说,我们使用的每个任务的耗时比较长,任务的请求又非常快,那么这样势必会造成在单位时间内创建了大量的线程。从而造成内存溢出。 newCachedThreadPool(ThreadFactory threadFactory)方法:

/** * Creates a thread pool that creates new threads as needed, but * will reuse previously constructed threads when they are * available, and uses the provided * ThreadFactory to create new threads when needed. * @param threadFactory the factory to use when creating new threads * @return the newly created thread pool * @throws NullPointerException if threadFactory is null */public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE,L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),threadFactory);}

       这个方法区别同样也是在于,增加了threadFactory可以自行指定线程的创建方式。

2.4 newScheduledThreadPool/** * Creates a thread pool that can schedule commands to run after a * given delay, or to execute periodically. * @param corePoolSize the number of threads to keep in the pool, * even if they are idle * @return a newly created scheduled thread pool * @throws IllegalArgumentException if { @code corePoolSize < 0} */public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { return new ScheduledThreadPoolExecutor(corePoolSize);}

       创建一个线程池,该线程池可以将任务在指定的延迟时间之后运行。或者定期运行。这个方法返回的是ScheduledThreadPoolExecutor。这个类是ThreadPoolExecutor的子类。在原有线程池的的基础之上,增加了延迟和定时功能。我们在后面分析了ThreadPoolExecutor源码之后,再来分析这个类的源码。 与之类似的方法:

/** * Creates a thread pool that can schedule commands to run after a * given delay, or to execute periodically. * @param corePoolSize the number of threads to keep in the pool, * even if they are idle * @param threadFactory the factory to use when the executor * creates a new thread * @return a newly created scheduled thread pool * @throws IllegalArgumentException if { @code corePoolSize < 0} * @throws NullPointerException if threadFactory is null */public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) { return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);}

       通过这个方法,我们可以指定threadFactory。自定义线程创建的方式。 同样,我们还可以只指定一个线程:

public static ScheduledExecutorService newSingleThreadScheduledExecutor() { return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));}public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1, threadFactory));}

       上述两个方法都可以实现这个功能,但是需要注意的是,这两个方法的返回在外层包裹了一个包装类。

3.5 newWorkStealingPool

       这种方式是在jdk1.8之后新增的。我们先来看看其源码:

public LinkedBlockingQueue() { this(Integer.MAX_VALUE);}0

       这个方法实际上返回的是ForkJoinPool。该方法创建了一

jvm中一个线程调用sleep(),然后发生stw(stop-the-world

       Thread.sleep方法的原理在于调用操作系统提供的睡眠功能,此功能挂起线程,由内核启动定时器,超时后恢复线程执行。内核并不介入用户态的垃圾回收(GC)过程。

       GC需要停止世界的(STW)时,会先让所有线程到达安全点。而native方法本身即为安全点,因此GC可以与sleep并行进行。当线程恢复执行时,从native方法返回或在native中调用某些JVM方法,如果发现GC的STW还未完成,则必须等待STW结束后才能继续执行。

       因此,结论是sleep可以与GC并行,只要在sleep结束时有可用CPU资源立即调度执行,且此时不在STW状态,那么毫秒的延迟就是准确的,即使期间已经执行过多次GC。

JVM调优-CMS常见参数、线程计算与推荐配置

       在JVM调优中,CMS GC收集器的常见参数对性能有着显著影响。以下是关键参数的详细介绍:

       1. 开启CMS收集器:-XX:+UseConcMarkSweepGC

       2.

       年轻代并行收集:-XX:UseParNewGC (默认启用)

       3.

       并行标记:-XX:+CMSParallelRemarkEnabled (默认启用)

       4.

       并发执行:-XX:+CMSConcurrentMTEnabled (默认启用)

       5.

       并发CMS线程数:-XX:ConcGCThreads

       6.

       并行GC线程数:-XX:ParallelGCThreads (默认值由ConcGCThreads决定)

       7.

       老年代使用率阈值:-XX:CMSInitiatingOccupancyFraction (配合UseCMSInitiatingOccupancyOnly)

       8.

       启用占用率检查:-XX:+UseCMSInitiatingOccupancyOnly

       9.

       类对象回收:-XX:+CMSClassUnloadingEnabled (默认关闭)

       .

       增量模式:-XX:+CMSIncrementalMode (默认关闭)

       .

       内存压缩:-XX:CMSFullGCsBeforeCompaction

       .

       预扫描:-XX:+CMSScavengeBeforeRemark (默认关闭)

       .

       强制使用CMS:-XX:+ExplicitGCInvokesConcurrent

       .

       包括永久代回收:-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses

       .

       忽略系统GC:-XX:+DisableExplicitGC

       .

       压缩类对象:-XX:UseCompressedOops (默认启用)

       .

       GC暂停时间:-XX:MaxGCPauseMillis (限制在合理范围内)

       对于不同内存配置,推荐如下设置:

       - 8CG:推荐4C8G的ParallelGCThreads和ConcGCThreads设置

       - 4C8G:推荐的参数配置

       - 2C4G:推荐的参数配置

       值得注意的是,最大堆内存配置通常不建议与容器或虚拟机内存完全一致,具体原因会在后续文章中详细探讨。在调整这些参数时,需根据应用的具体需求和性能目标进行优化。

JAVA 线程

       è¿™æ˜¯javaeye上非常经典的关于线程的帖子,写的非常通俗易懂的,适合任何读计算机的同学.

       çº¿ç¨‹åŒæ­¥

       æˆ‘们可以在计算机上运行各种计算机软件程序。每一个运行的程序可能包括多个独立运行的线程(Thread)。

       çº¿ç¨‹ï¼ˆThread)是一份独立运行的程序,有自己专用的运行栈。线程有可能和其他线程共享一些资源,比如,内存,文件,数据库等。

       å½“多个线程同时读写同一份共享资源的时候,可能会引起冲突。这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。

       åŒæ­¥è¿™ä¸ªè¯æ˜¯ä»Žè‹±æ–‡synchronize(使同时发生)翻译过来的。我也不明白为什么要用这个很容易引起误解的词。既然大家都这么用,咱们也就只好这么将就。

       çº¿ç¨‹åŒæ­¥çš„真实意思和字面意思恰好相反。线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

       å› æ­¤ï¼Œå…³äºŽçº¿ç¨‹åŒæ­¥ï¼Œéœ€è¦ç‰¢ç‰¢è®°ä½çš„第一点是:线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。这可真是个无聊的绕口令。

       å…³äºŽçº¿ç¨‹åŒæ­¥ï¼Œéœ€è¦ç‰¢ç‰¢è®°ä½çš„第二点是 “共享”这两个字。只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。

       å…³äºŽçº¿ç¨‹åŒæ­¥ï¼Œéœ€è¦ç‰¢ç‰¢è®°ä½çš„第三点是,只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。

       å…³äºŽçº¿ç¨‹åŒæ­¥ï¼Œéœ€è¦ç‰¢ç‰¢è®°ä½çš„第四点是:多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

       ä¸ºäº†åŠ æ·±ç†è§£ï¼Œä¸‹é¢ä¸¾å‡ ä¸ªä¾‹å­ã€‚

       æœ‰ä¸¤ä¸ªé‡‡è´­å‘˜ï¼Œä»–们的工作内容是相同的,都是遵循如下的步骤:

       ï¼ˆ1)到市场上去,寻找并购买有潜力的样品。

       ï¼ˆ2)回到公司,写报告。

       è¿™ä¸¤ä¸ªäººçš„工作内容虽然一样,他们都需要购买样品,他们可能买到同样种类的样品,但是他们绝对不会购买到同一件样品,他们之间没有任何共享资源。所以,他们可以各自进行自己的工作,互不干扰。

       è¿™ä¸¤ä¸ªé‡‡è´­å‘˜å°±ç›¸å½“于两个线程;两个采购员遵循相同的工作步骤,相当于这两个线程执行同一段代码。

       ä¸‹é¢ç»™è¿™ä¸¤ä¸ªé‡‡è´­å‘˜å¢žåŠ ä¸€ä¸ªå·¥ä½œæ­¥éª¤ã€‚采购员需要根据公司的“布告栏”上面公布的信息,安排自己的工作计划。

       è¿™ä¸¤ä¸ªé‡‡è´­å‘˜æœ‰å¯èƒ½åŒæ—¶èµ°åˆ°å¸ƒå‘Šæ çš„前面,同时观看布告栏上的信息。这一点问题都没有。因为布告栏是只读的,这两个采购员谁都不会去修改布告栏上写的信息。

       ä¸‹é¢å¢žåŠ ä¸€ä¸ªè§’色。一个办公室行政人员这个时候,也走到了布告栏前面,准备修改布告栏上的信息。

       å¦‚果行政人员先到达布告栏,并且正在修改布告栏的内容。两个采购员这个时候,恰好也到了。这两个采购员就必须等待行政人员完成修改之后,才能观看修改后的信息。

       å¦‚果行政人员到达的时候,两个采购员已经在观看布告栏了。那么行政人员需要等待两个采购员把当前信息记录下来之后,才能够写上新的信息。

       ä¸Šè¿°è¿™ä¸¤ç§æƒ…况,行政人员和采购员对布告栏的访问就需要进行同步。因为其中一个线程(行政人员)修改了共享资源(布告栏)。而且我们可以看到,行政人员的工作流程和采购员的工作流程(执行代码)完全不同,但是由于他们访问了同一份可变共享资源(布告栏),所以他们之间需要同步。

       åŒæ­¥é”

       å‰é¢è®²äº†ä¸ºä»€ä¹ˆè¦çº¿ç¨‹åŒæ­¥ï¼Œä¸‹é¢æˆ‘们就来看如何才能线程同步。

       çº¿ç¨‹åŒæ­¥çš„基本实现思路还是比较容易理解的。我们可以给共享资源加一把锁,这把锁只有一把钥匙。哪个线程获取了这把钥匙,才有权利访问该共享资源。

       ç”Ÿæ´»ä¸­ï¼Œæˆ‘们也可能会遇到这样的例子。一些超市的外面提供了一些自动储物箱。每个储物箱都有一把锁,一把钥匙。人们可以使用那些带有钥匙的储物箱,把东西放到储物箱里面,把储物箱锁上,然后把钥匙拿走。这样,该储物箱就被锁住了,其他人不能再访问这个储物箱。(当然,真实的储物箱钥匙是可以被人拿走复制的,所以不要把贵重物品放在超市的储物箱里面。于是很多超市都采用了电子密码锁。)

       çº¿ç¨‹åŒæ­¥é”è¿™ä¸ªæ¨¡åž‹çœ‹èµ·æ¥å¾ˆç›´è§‚。但是,还有一个严峻的问题没有解决,这个同步锁应该加在哪里?

       å½“然是加在共享资源上了。反应快的读者一定会抢先回答。

       æ²¡é”™ï¼Œå¦‚果可能,我们当然尽量把同步锁加在共享资源上。一些比较完善的共享资源,比如,文件系统,数据库系统等,自身都提供了比较完善的同步锁机制。我们不用另外给这些资源加锁,这些资源自己就有锁。

       ä½†æ˜¯ï¼Œå¤§éƒ¨åˆ†æƒ…况下,我们在代码中访问的共享资源都是比较简单的共享对象。这些对象里面没有地方让我们加锁。

       è¯»è€…可能会提出建议:为什么不在每一个对象内部都增加一个新的区域,专门用来加锁呢?这种设计理论上当然也是可行的。问题在于,线程同步的情况并不是很普遍。如果因为这小概率事件,在所有对象内部都开辟一块锁空间,将会带来极大的空间浪费。得不偿失。

       äºŽæ˜¯ï¼ŒçŽ°ä»£çš„编程语言的设计思路都是把同步锁加在代码段上。确切的说,是把同步锁加在“访问共享资源的代码段”上。这一点一定要记住,同步锁是加在代码段上的。

       åŒæ­¥é”åŠ åœ¨ä»£ç æ®µä¸Šï¼Œå°±å¾ˆå¥½åœ°è§£å†³äº†ä¸Šè¿°çš„空间浪费问题。但是却增加了模型的复杂度,也增加了我们的理解难度。

       çŽ°åœ¨æˆ‘们就来仔细分析“同步锁加在代码段上”的线程同步模型。

       é¦–先,我们已经解决了同步锁加在哪里的问题。我们已经确定,同步锁不是加在共享资源上,而是加在访问共享资源的代码段上。

       å…¶æ¬¡ï¼Œæˆ‘们要解决的问题是,我们应该在代码段上加什么样的锁。这个问题是重点中的重点。这是我们尤其要注意的问题:访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。

       è¿™å°±æ˜¯è¯´ï¼ŒåŒæ­¥é”æœ¬èº«ä¹Ÿä¸€å®šæ˜¯å¤šä¸ªçº¿ç¨‹ä¹‹é—´çš„共享对象。

       Java语言的synchronized关键字

       ä¸ºäº†åŠ æ·±ç†è§£ï¼Œä¸¾å‡ ä¸ªä»£ç æ®µåŒæ­¥çš„例子。

       ä¸åŒè¯­è¨€çš„同步锁模型都是一样的。只是表达方式有些不同。这里我们以当前最流行的Java语言为例。Java语言里面用synchronized关键字给代码段加锁。整个语法形式表现为

       synchronized(同步锁) {

       // 访问共享资源,需要同步的代码段

       }

       è¿™é‡Œå°¤å…¶è¦æ³¨æ„çš„就是,同步锁本身一定要是共享的对象。

       â€¦ f1() {

       Object lock1 = new Object(); // 产生一个同步锁

       synchronized(lock1){

       // 代码段 A

       // 访问共享资源 resource1

       // 需要同步

       }

       }

       ä¸Šé¢è¿™æ®µä»£ç æ²¡æœ‰ä»»ä½•æ„ä¹‰ã€‚因为那个同步锁是在函数体内部产生的。每个线程调用这段代码的时候,都会产生一个新的同步锁。那么多个线程之间,使用的是不同的同步锁。根本达不到同步的目的。

       åŒæ­¥ä»£ç ä¸€å®šè¦å†™æˆå¦‚下的形式,才有意义。

       public static final Object lock1 = new Object();

       â€¦ f1() {

       synchronized(lock1){ // lock1 是公用同步锁

       // 代码段 A

       // 访问共享资源 resource1

       // 需要同步

       }

       ä½ ä¸ä¸€å®šè¦æŠŠåŒæ­¥é”å£°æ˜Žä¸ºstatic或者public,但是你一定要保证相关的同步代码之间,一定要使用同一个同步锁。

       è®²åˆ°è¿™é‡Œï¼Œä½ ä¸€å®šä¼šå¥½å¥‡ï¼Œè¿™ä¸ªåŒæ­¥é”åˆ°åº•æ˜¯ä¸ªä»€ä¹ˆä¸œè¥¿ã€‚为什么随便声明一个Object对象,就可以作为同步锁?

       åœ¨Java里面,同步锁的概念就是这样的。任何一个Object Reference都可以作为同步锁。我们可以把Object Reference理解为对象在内存分配系统中的内存地址。因此,要保证同步代码段之间使用的是同一个同步锁,我们就要保证这些同步代码段的synchronized关键字使用的是同一个Object Reference,同一个内存地址。这也是为什么我在前面的代码中声明lock1的时候,使用了final关键字,这就是为了保证lock1的Object Reference在整个系统运行过程中都保持不变。

       ä¸€äº›æ±‚知欲强的读者可能想要继续深入了解synchronzied(同步锁)的实际运行机制。Java虚拟机规范中(你可以在google用“JVM Spec”等关键字进行搜索),有对synchronized关键字的详细解释。synchronized会编译成 monitor enter, … monitor exit之类的指令对。Monitor就是实际上的同步锁。每一个Object Reference在概念上都对应一个monitor。

       è¿™äº›å®žçŽ°ç»†èŠ‚问题,并不是理解同步锁模型的关键。我们继续看几个例子,加深对同步锁模型的理解。

       public static final Object lock1 = new Object();

       â€¦ f1() {

       synchronized(lock1){ // lock1 是公用同步锁

       // 代码段 A

       // 访问共享资源 resource1

       // 需要同步

       }

       }

       â€¦ f2() {

       synchronized(lock1){ // lock1 是公用同步锁

       // 代码段 B

       // 访问共享资源 resource1

       // 需要同步

       }

       }

       ä¸Šè¿°çš„代码中,代码段A和代码段B就是同步的。因为它们使用的是同一个同步锁lock1。

       å¦‚果有个线程同时执行代码段A,同时还有个线程同时执行代码段B,那么这个线程之间都是要进行同步的。

       è¿™ä¸ªçº¿ç¨‹éƒ½è¦ç«žäº‰ä¸€ä¸ªåŒæ­¥é”lock1。同一时刻,只有一个线程能够获得lock1的所有权,只有一个线程可以执行代码段A或者代码段B。其他竞争失败的线程只能暂停运行,进入到该同步锁的就绪(Ready)队列。

       æ¯ä¸€ä¸ªåŒæ­¥é”ä¸‹é¢éƒ½æŒ‚了几个线程队列,包括就绪(Ready)队列,待召(Waiting)队列等。比如,lock1对应的就绪队列就可以叫做lock1 - ready queue。每个队列里面都可能有多个暂停运行的线程。

       æ³¨æ„ï¼Œç«žäº‰åŒæ­¥é”å¤±è´¥çš„线程进入的是该同步锁的就绪(Ready)队列,而不是后面要讲述的待召队列(Waiting Queue,也可以翻译为等待队列)。就绪队列里面的线程总是时刻准备着竞争同步锁,时刻准备着运行。而待召队列里面的线程则只能一直等待,直到等到某个信号的通知之后,才能够转移到就绪队列中,准备运行。

       æˆåŠŸèŽ·å–同步锁的线程,执行完同步代码段之后,会释放同步锁。该同步锁的就绪队列中的其他线程就继续下一轮同步锁的竞争。成功者就可以继续运行,失败者还是要乖乖地待在就绪队列中。

       å› æ­¤ï¼Œçº¿ç¨‹åŒæ­¥æ˜¯éžå¸¸è€—费资源的一种操作。我们要尽量控制线程同步的代码段范围。同步的代码段范围越小越好。我们用一个名词“同步粒度”来表示同步代码段的范围。

       åŒæ­¥ç²’度

       åœ¨Java语言里面,我们可以直接把synchronized关键字直接加在函数的定义上。

       æ¯”如。

       â€¦ synchronized … f1() {

       // f1 代码段

       }

       è¿™æ®µä»£ç å°±ç­‰ä»·äºŽ

       â€¦ f1() {

       synchronized(this){ // 同步锁就是对象本身

       // f1 代码段

       }

       }

       åŒæ ·çš„原则适用于静态(static)函数

       æ¯”如。

       â€¦ static synchronized … f1() {

       // f1 代码段

       }

       è¿™æ®µä»£ç å°±ç­‰ä»·äºŽ

       â€¦static … f1() {

       synchronized(Class.forName(…)){ // 同步锁是类定义本身

       // f1 代码段

       }

       }

       ä½†æ˜¯ï¼Œæˆ‘们要尽量避免这种直接把synchronized加在函数定义上的偷懒做法。因为我们要控制同步粒度。同步的代码段越小越好。synchronized控制的范围越小越好。

       æˆ‘们不仅要在缩小同步代码段的长度上下功夫,我们同时还要注意细分同步锁。

       æ¯”如,下面的代码

       public static final Object lock1 = new Object();

       â€¦ f1() {

       synchronized(lock1){ // lock1 是公用同步锁

       // 代码段 A

       // 访问共享资源 resource1

       // 需要同步

       }

       }

       â€¦ f2() {

       synchronized(lock1){ // lock1 是公用同步锁

       // 代码段 B

       // 访问共享资源 resource1

       // 需要同步

       }

       }

       â€¦ f3() {

       synchronized(lock1){ // lock1 是公用同步锁

       // 代码段 C

       // 访问共享资源 resource2

       // 需要同步

       }

       }

       â€¦ f4() {

       synchronized(lock1){ // lock1 是公用同步锁

       // 代码段 D

       // 访问共享资源 resource2

       // 需要同步

       }

       }

       ä¸Šè¿°çš„4段同步代码,使用同一个同步锁lock1。所有调用4段代码中任何一段代码的线程,都需要竞争同一个同步锁lock1。

       æˆ‘们仔细分析一下,发现这是没有必要的。

       å› ä¸ºf1()的代码段A和f2()的代码段B访问的共享资源是resource1,f3()的代码段C和f4()的代码段D访问的共享资源是resource2,它们没有必要都竞争同一个同步锁lock1。我们可以增加一个同步锁lock2。f3()和f4()的代码可以修改为:

       public static final Object lock2 = new Object();

       â€¦ f3() {

       synchronized(lock2){ // lock2 是公用同步锁

       // 代码段 C

       // 访问共享资源 resource2

       // 需要同步

       }

       }

       â€¦ f4() {

       synchronized(lock2){ // lock2 是公用同步锁

       // 代码段 D

       // 访问共享资源 resource2

       // 需要同步

       }

       }

       è¿™æ ·ï¼Œf1()和f2()就会竞争lock1,而f3()和f4()就会竞争lock2。这样,分开来分别竞争两个锁,就可以大大较少同步锁竞争的概率,从而减少系统的开销。

       ä¿¡å·é‡

       åŒæ­¥é”æ¨¡åž‹åªæ˜¯æœ€ç®€å•çš„同步模型。同一时刻,只有一个线程能够运行同步代码。

       æœ‰çš„时候,我们希望处理更加复杂的同步模型,比如生产者/消费者模型、读写同步模型等。这种情况下,同步锁模型就不够用了。我们需要一个新的模型。这就是我们要讲述的信号量模型。

       ä¿¡å·é‡æ¨¡åž‹çš„工作方式如下:线程在运行的过程中,可以主动停下来,等待某个信号量的通知;这时候,该线程就进入到该信号量的待召(Waiting)队列当中;等到通知之后,再继续运行。

       å¾ˆå¤šè¯­è¨€é‡Œé¢ï¼ŒåŒæ­¥é”éƒ½ç”±ä¸“门的对象表示,对象名通常叫Monitor。

       åŒæ ·ï¼Œåœ¨å¾ˆå¤šè¯­è¨€ä¸­ï¼Œä¿¡å·é‡é€šå¸¸ä¹Ÿæœ‰ä¸“门的对象名来表示,比如,Mutex,Semphore。

       ä¿¡å·é‡æ¨¡åž‹è¦æ¯”同步锁模型复杂许多。一些系统中,信号量甚至可以跨进程进行同步。另外一些信号量甚至还有计数功能,能够控制同时运行的线程数。

       æˆ‘们没有必要考虑那么复杂的模型。所有那些复杂的模型,都是最基本的模型衍生出来的。只要掌握了最基本的信号量模型——“等待/通知”模型,复杂模型也就迎刃而解了。

       æˆ‘们还是以Java语言为例。Java语言里面的同步锁和信号量概念都非常模糊,没有专门的对象名词来表示同步锁和信号量,只有两个同步锁相关的关键字——volatile和synchronized。

       è¿™ç§æ¨¡ç³Šè™½ç„¶å¯¼è‡´æ¦‚念不清,但同时也避免了Monitor、Mutex、Semphore等名词带来的种种误解。我们不必执着于名词之争,可以专注于理解实际的运行原理。

       åœ¨Java语言里面,任何一个Object Reference都可以作为同步锁。同样的道理,任何一个Object Reference也可以作为信号量。

       Object对象的wait()方法就是等待通知,Object对象的notify()方法就是发出通知。

       å…·ä½“调用方法为

       ï¼ˆ1)等待某个信号量的通知

       public static final Object signal = new Object();

       â€¦ f1() {

       synchronized(singal) { // 首先我们要获取这个信号量。这个信号量同时也是一个同步锁

       // 只有成功获取了signal这个信号量兼同步锁之后,我们才可能进入这段代码

       signal.wait(); // 这里要放弃信号量。本线程要进入signal信号量的待召(Waiting)队列

       // 可怜。辛辛苦苦争取到手的信号量,就这么被放弃了

       // 等到通知之后,从待召(Waiting)队列转到就绪(Ready)队列里面

       // 转到了就绪队列中,离CPU核心近了一步,就有机会继续执行下面的代码了。

       // 仍然需要把signal同步锁竞争到手,才能够真正继续执行下面的代码。命苦啊。

       â€¦

       }

       }

       éœ€è¦æ³¨æ„çš„是,上述代码中的signal.wait()的意思。signal.wait()很容易导致误解。signal.wait()的意思并不是说,signal开始wait,而是说,运行这段代码的当前线程开始wait这个signal对象,即进入signal对象的待召(Waiting)队列。

       ï¼ˆ2)发出某个信号量的通知

       â€¦ f2() {

       synchronized(singal) { // 首先,我们同样要获取这个信号量。同时也是一个同步锁。

       // 只有成功获取了signal这个信号量兼同步锁之后,我们才可能进入这段代码

       signal.notify(); // 这里,我们通知signal的待召队列中的某个线程。

       // 如果某个线程等到了这个通知,那个线程就会转到就绪队列中

       // 但是本线程仍然继续拥有signal这个同步锁,本线程仍然继续执行

       // 嘿嘿,虽然本线程好心通知其他线程,

       // 但是,本线程可没有那么高风亮节,放弃到手的同步锁

       // 本线程继续执行下面的代码

       â€¦

       }

       }

       éœ€è¦æ³¨æ„çš„是,signal.notify()的意思。signal.notify()并不是通知signal这个对象本身。而是通知正在等待signal信号量的其他线程。

       ä»¥ä¸Šå°±æ˜¯Object的wait()和notify()的基本用法。

       å®žé™…上,wait()还可以定义等待时间,当线程在某信号量的待召队列中,等到足够长的时间,就会等无可等,无需再等,自己就从待召队列转移到就绪队列中了。

       å¦å¤–,还有一个notifyAll()方法,表示通知待召队列里面的所有线程。

       è¿™äº›ç»†èŠ‚问题,并不对大局产生影响。

       ç»¿è‰²çº¿ç¨‹

       ç»¿è‰²çº¿ç¨‹ï¼ˆGreen Thread)是一个相对于操作系统线程(Native Thread)的概念。

       æ“ä½œç³»ç»Ÿçº¿ç¨‹ï¼ˆNative Thread)的意思就是,程序里面的线程会真正映射到操作系统的线程,线程的运行和调度都是由操作系统控制的

       ç»¿è‰²çº¿ç¨‹ï¼ˆGreen Thread)的意思是,程序里面的线程不会真正映射到操作系统的线程,而是由语言运行平台自身来调度。

       å½“前版本的Python语言的线程就可以映射到操作系统线程。当前版本的Ruby语言的线程就属于绿色线程,无法映射到操作系统的线程,因此Ruby语言的线程的运行速度比较慢。

       éš¾é“说,绿色线程要比操作系统线程要慢吗?当然不是这样。事实上,情况可能正好相反。Ruby是一个特殊的例子。线程调度器并不是很成熟。

       ç›®å‰ï¼Œçº¿ç¨‹çš„流行实现模型就是绿色线程。比如,stackless Python,就引入了更加轻量的绿色线程概念。在线程并发编程方面,无论是运行速度还是并发负载上,都优于Python。

       å¦ä¸€ä¸ªæ›´è‘—名的例子就是ErLang(爱立信公司开发的一种开源语言)。

       ErLang的绿色线程概念非常彻底。ErLang的线程不叫Thread,而是叫做Process。这很容易和进程混淆起来。这里要注意区分一下。

       ErLang Process之间根本就不需要同步。因为ErLang语言的所有变量都是final的,不允许变量的值发生任何变化。因此根本就不需要同步。

       final变量的另一个好处就是,对象之间不可能出现交叉引用,不可能构成一种环状的关联,对象之间的关联都是单向的,树状的。因此,内存垃圾回收的算法效率也非常高。这就让ErLang能够达到Soft Real Time(软实时)的效果。这对于一门支持内存垃圾回收的语言来说,可不是一件容易的事情。

copyright © 2016 powered by 皮皮网   sitemap