搜索词>>java多线程 耗时0.0040
  • java多线程编程_java多线程安全_java多线程实现安全锁CAS机制

    java多线程编程_java多线程安全_java多线程实现安全锁CAS机制,CAS在java多线程中相当于数据库的乐观锁,synchronized相当于数据库的乐观锁。<h2>引言</h2>     java多线程编程中难免会遇到资源共享。这里将会讲解一下java多线程中的CAS机制和锁的基础概念。java多线程编程_java多线程安全_java多线程实现安全锁CAS机制,CAS在java多线程中相当于数据库的乐观锁,synchronized相当于数据库的乐观锁。 <h2>一.java多线程编程常见问题</h2> <strong><a rel="external nofollow" target="_blank" id="java多线程1" name="java多线程1">java多线程示例程序</a>:</strong>启动两个线程,每个线程中让静态共享变量count循环累加100次<br /> <br /> <a rel="external nofollow" target="_blank" id="java多线程代码" name="java多线程代码"><strong>java多线程示例代码</strong></a>: <pre> <code class="language-java">package xqlee.net.project.demo.thread.synchronize; public class ForTest { public static int count = 0; public static void main(String[] args) { // 开启两个线程 for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } //每个线程累计加100 for (int j = 0; j < 100; j++) { count++; // System.out.println(count); } } }).start(); } try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("count=" + count); } } </code></pre> <strong>疑惑</strong>:<span style="color:#6600ff">最终输出的count结果是多少呢?两个线程执行完毕一定是200吗?</span><br /> <br /> <strong>答案</strong>:因为这段代码不是线程安装的,所以最终的自增结果很有可能会小于200。<br /> <strong>尝试执行结果</strong>:<br /> <img alt="java多线程演示代码执行结果" class="img-thumbnail" src="/assist/images/blog/00053be4566b4b93acc2f6e7b13e93bf.png" /><br /> 接下来,修改上面的演示代码。通过关键字synchronized在java多线程编程实现中添加一个锁。这个是个重量级的锁。后续详说。<br /> 修改后演示代码如下: <pre> <code class="language-java">package xqlee.net.project.demo.thread.synchronize; public class ForTest { public static int count = 0; public static void main(String[] args) { // 开启两个线程 for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } //每个线程累计加100 for (int j = 0; j < 100; j++) { synchronized (ForTest.class) { count++; } // System.out.println(count); } } }).start(); } try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("count=" + count); } } </code></pre> <br /> 再次执行演示代码,查看结果:<br /> <img alt="java多线程编程加锁后的执行结果" class="img-thumbnail" src="/assist/images/blog/0ef74f747d6c440c8f62513431d9b462.png" /><br />     加了同步锁之后,count自增的操作变成了原子性操作,所以最终的输出一定是<strong>count=200</strong>,代码实现了线程安全。<br /> <br />     但是注意,虽然synchronized确保了线程的安全,但是在某些情况下,却不是一个最优选择。为啥这么说呢?关键在于synchronized存在性能问题。synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后再争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统<strong>用户模式</strong>和<strong>内核模式</strong>的转换,代价比较高。尽管Java1.6为Synchronized做了优化,增加了从<strong>偏向锁</strong>到<strong>轻量级锁</strong>再到<strong>重量级锁</strong>的过度,但是在最终转变为重量级锁之后,性能仍然较低。<br /> <br /> <br /> 那么有啥好的其他解决办法嘛?<br /> 有:原子操作类;<br /> <img alt="JDK中的原子操作类" class="img-thumbnail" src="/assist/images/blog/4a065fca722e4577982931e44fe71839.png" /> <p>  所谓原子操作类,指的是java.util.concurrent.atomic包下,一系列以Atomic开头的包装类。例如<strong>AtomicBoolean</strong>,<strong>AtomicInteger</strong>,<strong>AtomicLong</strong>。它们分别用于Boolean,Integer,Long类型的原子性操作。</p> <p>现在我们尝试在代码中引入AtomicInteger类:</p> <pre> <code class="language-java">package xqlee.net.project.demo.thread.synchronize; import java.util.concurrent.atomic.AtomicInteger; public class ForTest2 { public static AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) { // 开启两个线程 for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } //每个线程累计加100 for (int j = 0; j < 100; j++) { count.incrementAndGet(); // System.out.println(count); } } }).start(); } try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("count=" + count); } } </code></pre> <br /> 演示执行结果:<br /> <img alt="原子操作类进行乐观锁" class="img-thumbnail" src="/assist/images/blog/be44242a5e9340abbe97aa8cd621a55f.png" /><br /> 使用AtomicInteger之后,最终的输出结果同样可以保证是200。并且在<strong>某些情况下</strong>,代码的性能会比Synchronized更好。<br /> <br /> 那么Atomic操作类底层到底利用了什么手段呢?<br /> 其实Atomic就是用到了我们要讲的[CAS机制]<br />   <p><strong>什么是CAS?</strong></p> <p> </p> <p>CAS是英文单词<strong>Compare And Swap</strong>的缩写,翻译过来就是比较并替换。</p> <p> </p> <p>CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。</p> <p>更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。</p> <p>这样说或许有些抽象,我们来看一个例子:</p> <p>1.在内存地址V当中,存储着值为10的变量。</p> <img alt="内存地址V" class="img-thumbnail" src="/assist/images/blog/9da6331c557f46d3ad148fe777a91e5a.png" /><br /> 2.此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。<br /> <img alt="内存地址V2" class="img-thumbnail" src="/assist/images/blog/761fcd40fb074a66915e845d08139997.png" /><br /> <br /> 3.在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。<br /> <br /> <img alt="内存地址V3" class="img-thumbnail" src="/assist/images/blog/e247329a723f45c39dd4db9f50b59122.png" /><br /> 4.线程1开始提交更新,首先进行<strong>A和地址V的实际值比较(Compare)</strong>,发现A不等于V的实际值,提交失败。<br /> <img alt="内存地址V4" class="img-thumbnail" src="/assist/images/blog/ea99b78cf5fa414fa481ff39437f310e.png" /><br /> 5.线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为<strong>自旋</strong>。<br /> <br /> <img alt="内存地址V5" class="img-thumbnail" src="/assist/images/blog/f03a795b87884530a02d4247f2e1bd26.png" /><br /> 6.这一次比较幸运,没有其他线程改变地址V的值。线程1进行<strong>Compare</strong>,发现A和地址V的实际值是相等的。<br /> <br /> <img alt="内存地址6" class="img-thumbnail" src="/assist/images/blog/f1d8e07fac864ef9845aed9436d17c7f.png" /><br /> 7.线程1进行<strong>SWAP</strong>,把地址V的值替换为B,也就是12。<br /> <img alt="内存地址7" class="img-thumbnail" src="/assist/images/blog/c407d63f332043bc8c09f4582d66e6dc.png" /><br />     所以从思想上来说,Synchronized属于<strong>悲观锁</strong>,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于<strong>乐观锁</strong>,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。<br /> <br /> 两种机制CAS机制和Synchronized,没有绝对的好坏。那么如何抉择呢?<br /> 在并发量非常搞的情况下使用Synchronized同步锁更适合一些。<br /> <br /> <strong>CAS缺点:</strong> <p><strong>1.CPU开销较大</strong></p> <p>在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。</p> <p><strong>2.不能保证代码块的原子性</strong></p> <p>CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。</p> <p><strong>3.ABA问题</strong></p> <p>这是CAS机制最大的问题所在。</p> <p>什么是<strong>ABA</strong>问题?怎么解决?我们后面来详细介绍。</p>
  • Java基础多线程之线程同步(主线程等待子线程结束)

    Java基础多线程之主线程等待子线程结束,Java基础编程之多线程入门学习篇。主要讲解几种方法来实现Java多线程中主线程等待子线程结束的最快方式。<h2>引言</h2>   随着互联网时代的发展。在Java编程中多线程来处理多任务降低任务响应时间已是一种趋势。这里主要讲解Java多线程入门的主线程等待子线程结束的几种方法。 <h2>一.通过线程的join()方法</h2> 通过join()方法来实现主线程等待子线程同步的例子如下<br /> 首先编写一个简单的测试用的线程类:<br />   <pre> <code class="language-java">package xqlee.net.project.demo.thread.waitchilds; public class ThreadChildOne extends Thread { public ThreadChildOne(String name) { super(name); } @Override public void run() { try { long time=1000 * ((int) (Math.random() * 10)); System.out.println("From Child:" + this.getName()+">>>Start["+time+"]"); sleep(time); System.out.println("From Child:" + this.getName()+">>>End"); } catch (Exception e) { e.printStackTrace(); } } } </code></pre> 这里通过继承Thread来编写的一个线程类。<br /> <br /> 编写一个测试类来实现: <pre> <code class="language-java">package xqlee.net.project.demo.thread.waitchilds; import java.util.ArrayList; import java.util.List; public class TestWaitChildsOne { public static void main(String[] args) { try { System.out.println("From Main Thread:==>Start"); System.out.println("From Main Thread:开始调用子线程----》"); long start = System.currentTimeMillis(); List<ThreadChildOne> list = new ArrayList<>(); for (int i = 0; i < 6; i++) { ThreadChildOne threadChild = new ThreadChildOne("Child-" + i); threadChild.start(); list.add(threadChild); } for (ThreadChildOne threadChild : list) { threadChild.join(); } System.out.println("From Main Thread:子线程调用结束,耗时:" + (System.currentTimeMillis() - start)); } catch (Exception e) { e.printStackTrace(); } } } </code></pre> <br /> 执行测试类,观察控制台如下图:<br /> <img alt="通过join()实现线程同步的运行结果" class="img-thumbnail" src="/assist/images/blog/2ee34862b6bd4cd9a836c745f47db767.png" /><br /> 从上图我们可以看到成功的实现了主线程等待子线程同步。观察这里比最大子线程耗时多了3毫秒,可能是因为CPU切换线程耗时属于正常现象。<br />   <h2>二.通过JDK自带的工具类CountDownLatch来实现多线程的线程同步</h2> 编写一个简单的测试线程类: <pre> <code class="language-java">package xqlee.net.project.demo.thread.waitchilds; import java.util.concurrent.CountDownLatch; public class ThreadChildTwo extends Thread { private CountDownLatch countDownLatch; public ThreadChildTwo(String name, CountDownLatch countDownLatch) { super(name); this.countDownLatch = countDownLatch; } @Override public void run() { try { long time = 1000 * ((int) (Math.random() * 10)); System.out.println("From Child:" + this.getName() + ">>>Start[" + time + "]"); sleep(time); System.out.println("From Child:" + this.getName() + ">>>End"); } catch (Exception e) { e.printStackTrace(); } finally { // 倒数器减1 countDownLatch.countDown(); } } } </code></pre> <br /> 测试类: <pre> <code class="language-java">package xqlee.net.project.demo.thread.waitchilds; import java.util.concurrent.CountDownLatch; public class TestWaitChildsTwo { public static void main(String[] args) { try {// 创建一个初始值为5的倒数计数器 CountDownLatch countDownLatch = new CountDownLatch(5); //这里的数字=线程数+1 System.out.println("From Main Thread:==>Start"); System.out.println("From Main Thread:开始调用子线程----》"); long start = System.currentTimeMillis(); for (int i = 0; i < 5; i++) { ThreadChildTwo threadChild = new ThreadChildTwo("Child-" + i,countDownLatch); threadChild.start(); } // 阻塞当前线程,直到倒数计数器倒数到0 countDownLatch.await(); System.out.println("From Main Thread:子线程调用结束,耗时:" + (System.currentTimeMillis() - start)); } catch (Exception e) { e.printStackTrace(); } } } </code></pre> <br /> 运行结果:<br /> <img alt="测试运行结果" class="img-thumbnail" src="/assist/images/blog/f18eb90839e1406b9086737df0fd2b35.png" />
  • Java多线程编程入门到初级

    Java多线程编程入门到初级Java多线程编程入门到初级
  • Java多线程编程 Java多线程是如何保证Java类的安全

    线程安全是像Java这样的语言/平台中的类的重要质量,我们经常在线程之间共享对象。由于缺乏线程安全性而导致的问题非常难以调试,因为它们零星且几乎不可能有意再现。你如何测试你的对象以确保它们是线程安全的?这是我如何做的。<h2>引言</h2> <blockquote> <p>线程安全是像Java这样的语言/平台中的类的重要质量,我们经常在线程之间共享对象。由于缺乏线程安全性而导致的问题非常难以调试,因为它们零星且几乎不可能有意再现。你如何测试你的对象以确保它们是线程安全的?这是我如何做的。</p> </blockquote> <br /> <br /> 这里我们用一个书(book)类来说明 <pre> <code class="language-java">public class Books { final Map<Integer, String> map = new ConcurrentHashMap<>(); int add(String title) { final Integer next = this.map.size() + 1; this.map.put(next, title); return next; } String title(int id) { return this.map.get(id); } }</code></pre> 首先,我们在那里放置一本书,书架返回它的ID。 然后我们可以通过它的ID读取书名: <pre> <code class="language-java">Books books = new Books(); String title = "Elegant Objects"; int id = books.add(title); assert books.title(id).equals(title);</code></pre> <br /> 这个类似乎是线程安全的,因为我们使用线程安全的ConcurrentHashMap而不是更原始的和非线程安全的HashMap,对吧? 我们来测试一下: <pre> <code class="language-java">class BooksTest { @Test public void addsAndRetrieves() { Books books = new Books(); String title = "Elegant Objects"; int id = books.add(title); assert books.title(id).equals(title); } }</code></pre> 测试通过,但它只是一个单线程测试。让我们尝试从几个并行线程(我使用Hamcrest)做同样的操作: <pre> <code class="language-java">class BooksTest { @Test public void addsAndRetrieves() { Books books = new Books(); int threads = 10; ExecutorService service = Executors.newFixedThreadPool(threads); Collection<Future<Integer>> futures = new ArrayList<>(threads); for (int t = 0; t < threads; ++t) { final String title = String.format("Book #%d", t); futures.add(service.submit(() -> books.add(title))); } Set<Integer> ids = new HashSet<>(); for (Future<Integer> f : futures) { ids.add(f.get()); } assertThat(ids.size(), equalTo(threads)); } }</code></pre> <p style="text-align:start"><span style="color:#111111"><span style="font-family:Cambria,Georgia,"Times New Roman",serif"><span style="background-color:#ffffff">首先,我通过创建一个线程池<code>Executors</code>。然后我提交类型十个对象<code>Callable</code>通过<code>submit()</code>。他们每个人都会在书架上添加一本新的独特书籍。所有这些都将以某种不可预知的顺序由池中的这10个线程中的一些执行。</span></span></span></p> <p style="text-align:start"><span style="color:#111111"><span style="font-family:Cambria,Georgia,"Times New Roman",serif"><span style="background-color:#ffffff">然后,我通过类型对象列表获取其执行者的结果<a class="foreign" href="https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html" rel="external nofollow" style="max-width:100%; color:inherit; text-decoration:underline" target="_blank"><code>Future</code></a>。最后,我计算创建的唯一书籍ID数量。如果数字是10,则不存在冲突。我正在使用该<a class="foreign" href="https://docs.oracle.com/javase/7/docs/api/java/util/Set.html" rel="external nofollow" style="max-width:100%; color:inherit; text-decoration:underline" target="_blank"><code>Set</code></a>集合以确保ID列表仅包含唯一元素。</span></span></span></p> <p style="text-align:start"><span style="color:#111111"><span style="font-family:Cambria,Georgia,"Times New Roman",serif"><span style="background-color:#ffffff">测试通过我的笔记本电脑。但是,它不够强大。这里的问题是,它不是真的<code>Books</code>从多个并行线程中进行测试。在我们的呼叫之间传递的时间<code>submit()</code>足够大,可以完成执行<code>books.add()</code>。这就是为什么现实中只有一个线程会同时运行。我们可以通过修改代码来检查一下:</span></span></span></p> <pre> <code class="language-java">AtomicBoolean running = new AtomicBoolean(); AtomicInteger overlaps = new AtomicInteger(); Collection<Future<Integer>> futures = new ArrayList<>(threads); for (int t = 0; t < threads; ++t) { final String title = String.format("Book #%d", t); futures.add( service.submit( () -> { if (running.get()) { overlaps.incrementAndGet(); } running.set(true); int id = books.add(title); running.set(false); return id; } ) ); } assertThat(overlaps.get(), greaterThan(0));</code></pre> <br /> 有了这段代码,我试着看看线程多长时间重复一次,并行执行一些操作。这绝不会发生,并且<code>overlaps</code>等于零。因此,我们的测试并没有真正测试任何东西。它只是将十本书逐个添加到书架上。如果我将线程数量增加到1000,它们有时会开始重叠。但是我们希望它们重叠,即使只有少数它们。为了解决这个问题,我们需要使用<code>CountDownLatch</code>: <pre> <code class="language-java">CountDownLatch latch = new CountDownLatch(1); AtomicBoolean running = new AtomicBoolean(); AtomicInteger overlaps = new AtomicInteger(); Collection<Future<Integer>> futures = new ArrayList<>(threads); for (int t = 0; t < threads; ++t) { final String title = String.format("Book #%d", t); futures.add( service.submit( () -> { latch.await(); if (running.get()) { overlaps.incrementAndGet(); } running.set(true); int id = books.add(title); running.set(false); return id; } ) ); } latch.countDown(); Set<Integer> ids = new HashSet<>(); for (Future<Integer> f : futures) { ids.add(f.get()); } assertThat(overlaps.get(), greaterThan(0));</code></pre>   <p style="text-align:start"><span style="color:#111111"><span style="font-family:Cambria,Georgia,"Times New Roman",serif"><span style="background-color:#ffffff">现在,每个线程在触摸书籍之前,都会等待给予的许可<code>latch</code>。当我们通过<code>submit()</code>他们全部提交他们并等待。然后我们释放闩锁,<code>countDown()</code>他们都开始同时进行。现在,在我的笔记本电脑中,<code>overlaps</code>即使<code>threads</code>是10 时,也等于3-5 。</span></span></span></p> <p style="text-align:start"><span style="color:#111111"><span style="font-family:Cambria,Georgia,"Times New Roman",serif"><span style="background-color:#ffffff">最后一次<code>assertThat()</code>崩溃!我没有像以前那样获得10本书ID。这是7-9,但从来没有10.班,显然,是不是线程安全的!</span></span></span></p> <p style="text-align:start"><span style="color:#111111"><span style="font-family:Cambria,Georgia,"Times New Roman",serif"><span style="background-color:#ffffff">但在我们修复课程之前,让我们来简化测试。让我们用<code>RunInThreads</code>从Cactoos,这确实是我们在前面已经做了完全一样的,但引擎盖下:</span></span></span></p> <pre> <code class="language-java">class BooksTest { @Test public void addsAndRetrieves() { Books books = new Books(); MatcherAssert.assertThat( t -> { String title = String.format( "Book #%d", t.getAndIncrement() ); int id = books.add(title); return books.title(id).equals(title); }, new RunsInThreads<>(new AtomicInteger(), 10) ); } }</code></pre>   <p style="text-align:start"><span style="color:#111111"><span style="font-family:Cambria,Georgia,"Times New Roman",serif"><span style="background-color:#ffffff">第一个参数<code>assertThat()</code>是<code>Func</code>(一个功能接口)的一个实例,接受<code>AtomicInteger</code>(的第一个参数<code>RunsInThreads</code>)并返回<code>Boolean</code>。该函数将在10个并行线程上执行,使用上面演示的相同的基于锁存器的方法。</span></span></span></p> <p style="text-align:start"><span style="color:#111111"><span style="font-family:Cambria,Georgia,"Times New Roman",serif"><span style="background-color:#ffffff">这<code>RunInThreads</code>似乎是紧凑和方便的,我已经在一些项目中使用它。</span></span></span></p> <p style="text-align:start"><span style="color:#111111"><span style="font-family:Cambria,Georgia,"Times New Roman",serif"><span style="background-color:#ffffff">顺便说一句,为了使<code>Books</code>线程安全,我们只需要添加<code>synchronized</code>到它的方法<code>add()</code>。</span></span></span></p> <br /> <br />  
  • Java多线程生命周期

    Java多线程生命周期<h2 style="margin-left:0px; margin-right:0px; text-align:start"><br /> <strong>1.介绍</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在本文中,我们将详细讨论Java中的核心概念 - 线程的生命周期。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们将使用快速说明的图表,当然还有实际的代码片段,以便在线程执行期间更好地理解这些状态。</span></span></span><br />  </p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>2. Java中的多线程</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff"><strong>在Java语言中,多线程是由Thread的核心概念驱动的</strong>。在他们的生命周期中,线程会经历各种状态:<br /> <img alt="java多线程状态" class="img-thumbnail" src="/assist/images/blog/fdb0b393f1d5484c8a6a8151f6cf63a8.png" /></span></span></span></p> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>3. Java中线程的生命周期</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">该<em>java.lang.Thread的</em>类包含一个<em>静态枚举-</em>它定义了它的潜在状态。在任何给定时间点,线程只能处于以下状态之一:</span></span></span></p> <ol> <li><strong><em>NEW -</em></strong>新创建的尚未开始执行的线程</li> <li><strong><em>RUNNABLE -</em></strong>运行或准备执行,但它正在等待资源分配</li> <li><strong><em>BLOCKED -</em></strong>等待获取监视器锁定以输入或重新输入同步块/方法</li> <li><strong><em>WAITING(</em></strong>等待)<strong><em> -</em></strong>等待其他线程执行特定操作而没有任何时间限制</li> <li><strong><em>TIMED_WAITING -</em></strong>等待其他线程在指定时间段内执行特定操作</li> <li><strong><em>TERMINATED -</em></strong>已经完成执行</li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">所有这些状态都在上图中列出; 现在我们来详细讨论其中的每一个。</span></span></span></p> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>3.1。New</strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff"><strong>一个<em>新的</em> <em>线程</em>(或出生<em>线程</em>),是指已创建,但尚未启动的线程。</strong>直到我们使用<em>start()</em>方法启动它之前,它一直处于这种状态。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">下面的代码片段显示了一个新创建的处于<em>NEW</em>状态的线程:</span></span></span></p> <pre> <code class="language-java">Runnable runnable = new NewState(); Thread t = new Thread(runnable); Log.info(t.getState());</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">由于我们还没有开始提到的线程,所以<em>t.getState()</em>方法打印出来:</p> <pre> <code class="language-html">NEW</code></pre> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>3.2。<em>RUNNABLE </em></strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">当我们创建一个新线程并在其上调用<em>start()</em>方法时,它将从<em>NEW</em>移至<em>RUNNABLE</em>状态。<strong>处于此状态的线程正在运行或准备运行,但它们正在等待系统中的资源分配。</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在多线程环境中,Thread-Scheduler(它是JVM的一部分)为每个线程分配固定时间量。所以它会运行一段时间,然后将控制权交给其他<em>RUNNABLE</em>线程。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">例如,让我们将<em>t.start()</em>方法添加到我们以前的代码并尝试访问其当前状态:</span></span></span></p> <pre> <code class="language-java">Runnable runnable = new NewState(); Thread t = new Thread(runnable); t.start(); Log.info(t.getState());</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start">该代码很<strong>可能</strong>会将输出返回为:</p> <pre> <code class="language-html">RUNNABLE</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">请注意,在这个例子中,并不总是保证当我们的控件达到<em>t.getState()时</em>,它仍然处于<em>RUNNABLE</em>状态。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">可能会发生它由<em>Thread-Scheduler</em>立即调度并可能完成执行。在这种情况下,我们可能会得到不同的结果。</span></span></span></p> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>3.3。<em>BLOCKED </em></strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">一个线程当前没有资格运行时处于<em>BLOCKED</em>状态。<strong>它在等待监视器锁定时尝试访问由其他某个线程锁定的代码段时进入此状态。</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们试着重现这个状态:</span></span></span></p> <pre> <code class="language-java">public class BlockedState { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new DemoThreadB()); Thread t2 = new Thread(new DemoThreadB()); t1.start(); t2.start(); Thread.sleep(1000); Log.info(t2.getState()); System.exit(0); } } class DemoThreadB implements Runnable { @Override public void run() { commonResource(); } public static synchronized void commonResource() { while(true) { // Infinite loop to mimic heavy processing // 't1' won't leave this method // when 't2' try to enters this } } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在这个代码中:</span></span></span></p> <ol> <li>我们创建了两个不同的线程 - <em>t1</em>和<em>t2</em></li> <li><em>t1</em>启动并进入同步的<em>commonResource()</em>方法; 这意味着只有一个线程可以访问它; 试图访问此方法的所有其他后续线程将被阻止进一步执行,直到当前线程完成处理</li> <li>当<em>t1</em>进入该方法时,它在循环中保持无限; 这只是为了模仿繁重的处理,以至于所有其他线程都无法进入此方法</li> <li>现在当我们开始<em>t2时</em>,它会尝试进入<em>commonResource()</em>方法,它已经被<em>t1</em>访问<em>,</em>因此,<em>t2</em>将保持在<em>BLOCKED</em>状态</li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">处于这种状态,我们调用<em>t2.getState()</em>并获取输出:</span></span></span></p> <pre> <code class="language-html">BLOCKED</code></pre> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>3.4。<em>WAITING</em></strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff"><strong>线程在等待其他线程执行特定操作时处于<em>WAITING</em>状态。</strong> <a href="https://docs.oracle.com/javase/9/docs/api/java/lang/Thread.State.html#WAITING" rel="external nofollow" style="box-sizing:border-box; color:#63b175; text-decoration:none" target="_blank">根据JavaDocs</a>,任何线程都可以通过调用以下三种方法之一来进入该状态:</span></span></span></p> <ol> <li><em>的Object.wait()</em></li> <li><em>thread.join()</em>或</li> <li><em>LockSupport.park()</em></li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">请注意,在<em>wait()</em>和<em>join()中</em> - 我们没有定义任何超时期限,因为该方案在下一节中介绍。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">我们有一个单独的教程,详细讨论了<em>wait()</em>,<em>notify()</em>和<em>notifyAll()</em>的用法。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">现在,让我们尝试重现这个状态:</span></span></span></p> <div style="text-align:start"> <div style="margin-left:0px; margin-right:0px"> <pre> <code class="language-java">public class WaitingState implements Runnable { public static Thread t1; public static void main(String[] args) { t1 = new Thread(new WaitingState()); t1.start(); } public void run() { Thread t2 = new Thread(new DemoThreadWS()); t2.start(); try { t2.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } } } class DemoThreadWS implements Runnable { public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } Log.info(WaitingState.t1.getState()); } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">让我们来讨论一下我们在做什么:</span></span></span></p> <ol> <li>我们已经创建并开始了<em>t1</em></li> <li><em>t1</em>创建一个<em>t2</em>并启动它</li> <li>当<em>t2</em>的处理继续时,我们调用<em>t2.join()</em>,这将<em>t1</em>置于<em>WAITING</em>状态,直到<em>t2</em>完成执行</li> <li>由于<em>t1</em>正在等待<em>t2</em>完成,因此我们从<em>t2</em>调用<em>t1.getState()</em></li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">这里的输出如你所期望的那样:</span></span></span></p> <pre> <code class="language-html">WAITING</code></pre> </div> </div> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>3.5。<em>TIMED_WAITING</em></strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff"><strong>当线程正在等待另一个线程在规定的时间内执行特定操作时,该线程处于<em>TIMED_WAITING</em>状态。</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff"><a href="https://docs.oracle.com/javase/9/docs/api/java/lang/Thread.State.html#TIMED_WAITING" rel="external nofollow" style="box-sizing:border-box; color:#63b175; text-decoration:none" target="_blank">根据JavaDocs</a>,有五种方法将线程置于<em>TIMED_WAITING</em>状态:</span></span></span></p> <ol> <li><em>thread.sleep(长毫米)</em></li> <li><em>等待(INT超时)</em>或<em>等待(INT超时,INT纳米)</em></li> <li><em>thread.join(long</em> millis <em>)</em></li> <li><em>LockSupport.parkNanos</em></li> <li><em>LockSupport.parkUntil</em></li> </ol> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">现在,我们试着快速重现这个状态:</span></span></span></p> <pre> <code class="language-java">public class TimedWaitingState { public static void main(String[] args) throws InterruptedException { DemoThread obj1 = new DemoThread(); Thread t1 = new Thread(obj1); t1.start(); // The following sleep will give enough time for ThreadScheduler // to start processing of thread t1 Thread.sleep(1000); Log.info(t1.getState()); } } class DemoThread implements Runnable { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } } }</code></pre> 在这里,我们创建并启动了一个线程<em>t1</em>,它以5秒的超时时间进入睡眠状态; 输出将是: <pre> <code class="language-html">TIMED_WAITING</code></pre> <h3 style="margin-left:0px; margin-right:0px; text-align:start"><strong>3.6。<em>TERMINATED </em></strong></h3> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">这是死亡线程的状态。<strong>当它完成执行或异常</strong><strong><em>终止</em></strong><strong>时,它</strong><strong>处于<em>TERMINATED</em>状态。</strong></span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">让我们尝试在下面的例子中达到这个状态:</span></span></span></p> <pre> <code class="language-java">ublic class TerminatedState implements Runnable { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new TerminatedState()); t1.start(); // The following sleep method will give enough time for // thread t1 to complete Thread.sleep(1000); Log.info(t1.getState()); } @Override public void run() { // No processing in this block } }</code></pre> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在这里,虽然我们已经启动了线程<em>t1</em>,但下一个语句<em>Thread.sleep(1000)</em>为<em>t1</em>提供了足够的时间来完成,因此该程序给出了如下输出:</span></span></span></p> <pre> <code class="language-html">TERMINATED</code></pre> <div style="text-align:start"> <div style="margin-left:0px; margin-right:0px"> <h2 style="margin-left:0px; margin-right:0px; text-align:start"><strong>4。结论</strong></h2> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">在本教程中,我们了解了Java中线程的生命周期。我们查看了由<em>Thread.State</em>枚举定义的所有七个状态,并用快速示例复制了它们。</span></span></span></p> <p style="margin-left:0px; margin-right:0px; text-align:start"><span style="color:#535353"><span style="font-family:raleway"><span style="background-color:#ffffff">虽然代码片段几乎可以在每台机器上提供相同的输出,但在某些特殊情况下,我们可能会得到一些不同的输出,因为无法确定线程调度程序的确切行为。</span></span></span></p> </div> </div> <br /> <br /> <br /> <br /> <br /> <br /> <br /> <br />  
  • Java多线程编程排队锁(Ticket Lock)详解

    本文将讲述排队锁的使用场景,什么情况适合使用排队锁?Java 怎么使用排队锁?一、什么是排队锁Ticket Lock?Ticket Lock 是为了解决自旋锁的公平性问题,类似于现实中银行柜台的排队叫号:锁拥有一个服务号,表示正在服务的线程,还有一个排队号;每个线程尝试获取锁之前先拿一个排队号,然后不断轮询锁的当前服务号是否是自己的排队号,如果是,则表示自己拥有了锁,不是则继续轮询。当线程释放锁时,将服务号加1,这样下一个线程看到这个变化,就退出自旋。二、JAVA 多线程编程中一种排队锁Ticket Lock使用案例import java.util.concurrent.atomic.AtomicInteger;public class TicketLock {   private AtomicInteger serviceNum = new AtomicInteger(); // 服务号   private AtomicInteger ticketNum = new AtomicInteger(); // 排队号   public int lock() {         // 首先原子性地获得一个排队号         int myTicketNum = ticketNum.getAndIncrement();              // 只要当前服务号不是自己的就不断轮询       while (serviceNum.get() != myTicketNum) {       }       return myTicketNum;    }    public void unlock(int myTicket) {        // 只有当前线程拥有者才能释放锁        int next = myTicket + 1;        serviceNum.compareAndSet(myTicket, next);    }}三、排队锁Ticket Lock缺点Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。后续将会介绍的CLH锁和MCS锁,解决这个问题。
  • Java 多线程编程自旋锁详解

    本文将讲述什么是自旋锁?自旋锁的使用场景,什么情况适合使用自旋锁?Java 怎么使用自旋锁?一、什么是自旋锁?计算机系统资源总是有限的,有些资源需要互斥访问,因此就有了锁机制,只有获得锁的线程才能访问资源。锁保证了每次只有一个线程可以访问资源。当线程申请一个已经被其他线程占用的锁,就会出现两种情况。一种是没有获得锁的线程会阻塞自己,等到锁被释放后再被唤起,这就是互斥锁;一种是没有获得锁的线程一直循环在那里看是否该锁的保持者已经释放了锁,这就是自旋锁。二、自旋锁可能引起的问题1.过多占据CPU时间:如果锁的当前持有者长时间不释放该锁,那么等待者将长时间的占据cpu时间片,导致CPU资源的浪费,因此可以设定一个时间,当锁持有者超过这个时间不释放锁时,等待者会放弃CPU时间片阻塞;2.死锁问题:试想一下,有一个线程连续两次试图获得自旋锁(比如在递归程序中),第一次这个线程获得了该锁,当第二次试图加锁的时候,检测到锁已被占用(其实是被自己占用),那么这时,线程会一直等待自己释放该锁,而不能继续执行,这样就引起了死锁。因此递归程序使用自旋锁应该遵循以下原则:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。三、JAVA 多线程编程中一种自旋锁使用案例import java.util.concurrent.atomic.AtomicReference;public class SpinLock {    //java中原子(CAS)操作    AtomicReference<Thread> owner = new AtomicReference<Thread>();//持有自旋锁的线程对象    private int count;    public void lock() {        Thread cur = Thread.currentThread();        //lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。当有第二个线程调用lock操作时由于owner值不为空,导致循环        //一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。        while (!owner.compareAndSet(null, cur)) {        }    }    public void unLock() {        Thread cur = Thread.currentThread();        owner.compareAndSet(cur, null);    }}上述代码说明:调用lock方法时,如果owner当前值为null,说明自旋锁还没有被占用,将owner设置为currentThread,并进行锁定。调用lock方法时,如果owner当前值不为null,说明自旋锁已经被其他线程占用,当前线程就会在while中继续循环检测。调用unlock方法时,会将owner置为空,相当于释放自旋锁。public class TestThread extends Thread {    static int sum;    private SpinLock lock;    public TestThread(SpinLock lock) {        this.lock = lock;    }    @Override    public void run() {        this.lock.lock();        sum++;        System.out.println("当前值:=====>"+sum);        this.lock.unLock();    }    public static void main(String[] args) throws InterruptedException {        SpinLock lock = new SpinLock();        for (int i = 0; i < 100; i++) {            TestThread test = new TestThread(lock);            Thread t = new Thread(test);            t.start();        }        Thread.currentThread().sleep(1000);        System.out.println(sum);    }}上述代码执行的输出结果一定是顺序的1-100输出。四、名词解释java CAS原子操作:Compare And Set 在java.util.concurrent.atomic包下很多类都具有compareAndSet方法。五、自旋锁的使用场景,什么时候适合使用自旋锁由于自旋锁只是在当前线程不停地执行循环体,不进行线程状态的切换,因此响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要占用CPU时间。如果线程竞争不激烈,并且保持锁的时间很短,则适合使用自旋锁。
  • Java多线程编程CLH锁详解

    本文将讲述CLH锁的使用场景,什么情况适合使用CLH锁?Java 怎么使用CLH锁?一、什么是CLH锁?CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。二、JAVA 多线程编程中一种CLH锁使用案例import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class CLHLock {     public static class CLHNode {         private volatile boolean isLocked = true; // 默认是在等待锁     }     @SuppressWarnings("unused" )     private volatile CLHNode tail ;     private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater                   . newUpdater(CLHLock.class, CLHNode .class , "tail" );     public void lock(CLHNode currentThread) {         CLHNode preNode = UPDATER.getAndSet( this, currentThread);         if(preNode != null) {//已有线程占用了锁,进入自旋             while(preNode.isLocked ) {             }         }     }     public void unlock(CLHNode currentThread) {         // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。         if (!UPDATER .compareAndSet(this, currentThread, null)) {             // 还有后续线程             currentThread. isLocked = false ;// 改变状态,让后续线程结束自旋         }     } }​​​​​​​三、CLH锁 与 MCS锁 的比较代码段 小部件
  • Java多线程编程MCS锁详解

    本文将讲述MCS锁的使用场景,什么情况适合使用MCS锁?Java 怎么使用MCS锁?一、什么是MCS锁?MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。二、JAVA 多线程编程中一种MCS锁使用案例import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; public class MCSLock {     public static class MCSNode {         volatile MCSNode next;         volatile boolean isBlock = true; // 默认是在等待锁     }     volatile MCSNode queue;// 指向最后一个申请锁的MCSNode     private static final AtomicReferenceFieldUpdater UPDATER = AtomicReferenceFieldUpdater             .newUpdater(MCSLock.class, MCSNode.class, "queue");     public void lock(MCSNode currentThread) {         MCSNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1         if (predecessor != null) {             predecessor.next = currentThread;// step 2             while (currentThread.isBlock) {// step 3             }         }else { // 只有一个线程在使用锁,没有前驱来通知它,所以得自己标记自己为非阻塞                currentThread. isBlock = false;           }     }     public void unlock(MCSNode currentThread) {         if (currentThread.isBlock) {// 锁拥有者进行释放锁才有意义             return;         }         if (currentThread.next == null) {// 检查是否有人排在自己后面             if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4                 // compareAndSet返回true表示确实没有人排在自己后面                 return;             } else {                 // 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者                 // 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完                 while (currentThread.next == null) { // step 5                 }             }         }         currentThread.next.isBlock = false;         currentThread.next = null;// for GC     } }
  • spring boot 2.0入门之spring boot 2.0 Junit单元测试多线程问题解决

    spring boot 2.0 入门之单元测试多线程。spring boot 2.0 项目含多线程异步处理业务单元测试执行主线程结束不等待子线程结束。问题描述测试的业务将部分数据添加入一个公开队列中,有另外一个业务类进行多线程轮训等待处理。业务场景中涉及到了多线程异步任务。当使用spring boot 2.0自带的Junit整合测试业务时候,端点等待子线程无效。子线程会随主线程断点而断点执行。简单的说就是你在测试的业务中添加断点来等待子线程(异步多线程业务处理)时候,你会发现主线断点走一步,子线程也就动一下。从而子线程的异步任务无法得到正确的测试结果。主线程结束子线程就算任务没完成也会被强制结束。问题解决由于Junit的运行机制,目前临时的解决方式是在写的单元测试方法中调用测试的业务方法后面添加线程睡眠,也就是Thread.sleep(time);睡眠的时间根据子线程处理业务的速度估计个2倍。