[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"consumer-news-detail-900":3,"consumer-news-interaction-900":41,"consumer-news-related-900":44},{"detail":4,"item":36},{"card":5,"schemaVersion":23,"fields":24,"content":30},{"id":6,"kind":7,"targetType":8,"targetId":9,"subtype":7,"typeLabel":10,"title":11,"subtitle":12,"summary":13,"coverUrl":14,"badgeText":15,"href":16,"sourceName":12,"meta":17,"metrics":20,"tags":21,"resolved":22},"NEWS_ARTICLE:900","news","NEWS_ARTICLE",900,"资讯","java 多线程开发系列之七：玩转多线程（线程的协作）","博客园","线程的协作多种多样，这次主要说说wait和notify两种Object的方法。这也是java早期原生的重要的协作机制之一。先说下这两个英文单词：wait [weɪt] v.等待;等候;(尤指长期地)希望，盼望，期待notify [ˈnəʊtɪfaɪ] vt.通知;(正式)通报;也就是一个等待，一个通","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F704073\u002F202608\u002F704073-20260831103138251-1707185366.png","","\u002Fnews\u002F900",[18,19],"2026","软件开发",{},[19],true,"consumer-content-detail-v1",{"sourceName":12,"authorName":25,"categoryName":19,"summary":13,"description":13,"publishTime":26,"updateTime":27,"sourceUrl":28,"language":29},"王若伊_恩赐解脱","2026-08-31T10:35","2026-09-02T20:37:54","https:\u002F\u002Fwww.cnblogs.com\u002Fjilodream\u002Fp\u002F22770136","中文",{"format":31,"policy":32,"normalized":22,"html":33,"text":34,"wordCount":35,"hasBody":22},"HTML","NEWS_CONTENT_V1","\u003Cp>\u003Cspan>线程的协作多种多样，这次主要说说wait和notify两种Object的方法。这也是java早期原生的重要的协作机制之一。\u003C\u002Fspan>\u003Cbr>\u003Cspan>先说下这两个英文单词：\u003C\u002Fspan>\u003Cbr>\u003Cspan>wait [weɪt] v.等待;等候;(尤指长期地)希望，盼望，期待\u003C\u002Fspan>\u003Cbr>\u003Cspan>notify [ˈnəʊtɪfaɪ] vt.通知;(正式)通报;\u003C\u002Fspan>\u003Cbr>\u003Cspan>也就是一个等待，一个通知。\u003C\u002Fspan>\u003Cbr>\u003Cspan>一般的使用场景就是：\u003C\u002Fspan>\u003Cbr>\u003Cspan>线程a，运行到某一刻发现条件不满足，执行等待方法，\u003C\u002Fspan>\u003Cbr>\u003Cspan>线程b，将条件满足，通知等待的线程，\u003C\u002Fspan>\u003Cbr>\u003Cspan>线程a，收到通知请求，判断等待条件是否满足，满足后继续执行，不满足继续等待。\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>核心思路就是下边这图这样\u003C\u002Fp>\n\u003Cp>\u003Cspan>\u003Cimg src=\"https:\u002F\u002Fi1.wp.com\u002Fimg2024.cnblogs.com\u002Fblog\u002F704073\u002F202608\u002F704073-20260831103138251-1707185366.png?w=720&amp;quality=65&amp;strip=all\" alt=\"waitnotify\">\u003C\u002Fspan>\u003C\u002Fp>\n\n\u003Cp>\u003Cspan>那不使用wait\u002Fnotify等待通知的方式来协作，线程a就一直while循环，等待条件满足，再继续执行可以么？\u003Cbr>\n  当然可以，这种情况我们也称之为 忙等（busy-waiting \u002F spin）。自旋锁采用的就是这个逻辑。\u003Cbr>\n  但是自旋往往比较占用性能，大部分场景没这个必要。\u003Cbr>\n  我们更多的时候还是希望线程主动让出占用的cpu资源，(防盗连接：本文首发自http:\u002F\u002Fwww.cnblogs.com\u002Fjilodream\u002F )等到条件合适再继续占用。\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>在确定完使用场景，我们还要注意到，wait 和notify 的使用，是放置在Object 方法上的，为了保证不被乱调，只有拿到了该Object的监视器锁的线程才有资格调用wait\u002Fnotify方法的，否则就会报出IllegalMonitorStateException的运行时异常。注意看注释的解释：当前线程如果不是object 的显示器锁的持有者，就抛出该异常。\u003C\u002Fspan>\u003Cbr>\u003Cspan>IllegalMonitorStateException if the current thread is not the owner of the object's monitor。\u003C\u002Fspan>\u003Cbr>\u003Cspan>这里所谓的监视器锁，就是我们平常说的同步锁，synchronized锁。\u003C\u002Fspan>\u003Cbr>\u003Cspan>同理由于同步块的存在，被通知唤醒的线程，只是进入就绪状态，还是需要再次拿到锁才可以继续进行。\u003C\u002Fspan>\u003Cbr>\u003Cspan>具体的几个核心方法源码如下：\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cdiv>\n \u003Cpre>\u003Cspan>\u003Cspan> 1\u003C\u002Fspan>     \u003Cspan>public\u003C\u002Fspan> \u003Cspan>final\u003C\u002Fspan> \u003Cspan>native\u003C\u002Fspan> \u003Cspan>void\u003C\u002Fspan>\u003Cspan> notify();\n\u003C\u002Fspan>\u003Cspan> 2\u003C\u002Fspan> \n\u003Cspan> 3\u003C\u002Fspan>     \u003Cspan>public\u003C\u002Fspan> \u003Cspan>final\u003C\u002Fspan> \u003Cspan>native\u003C\u002Fspan> \u003Cspan>void\u003C\u002Fspan>\u003Cspan> notifyAll();\n\u003C\u002Fspan>\u003Cspan> 4\u003C\u002Fspan> \n\u003Cspan> 5\u003C\u002Fspan>     \u003Cspan>public\u003C\u002Fspan> \u003Cspan>final\u003C\u002Fspan> \u003Cspan>void\u003C\u002Fspan> wait() \u003Cspan>throws\u003C\u002Fspan>\u003Cspan> InterruptedException {\n\u003C\u002Fspan>\u003Cspan> 6\u003C\u002Fspan>         wait(0L\u003Cspan>);\n\u003C\u002Fspan>\u003Cspan> 7\u003C\u002Fspan> \u003Cspan>    }\n\u003C\u002Fspan>\u003Cspan> 8\u003C\u002Fspan> \n\u003Cspan> 9\u003C\u002Fspan>     \u003Cspan>public\u003C\u002Fspan> \u003Cspan>final\u003C\u002Fspan> \u003Cspan>native\u003C\u002Fspan> \u003Cspan>void\u003C\u002Fspan> wait(\u003Cspan>long\u003C\u002Fspan> timeoutMillis) \u003Cspan>throws\u003C\u002Fspan>\u003Cspan> InterruptedException;\n\u003C\u002Fspan>\u003Cspan>10\u003C\u002Fspan> \n\u003Cspan>11\u003C\u002Fspan>     \u003Cspan>public\u003C\u002Fspan> \u003Cspan>final\u003C\u002Fspan> \u003Cspan>void\u003C\u002Fspan> wait(\u003Cspan>long\u003C\u002Fspan> timeoutMillis, \u003Cspan>int\u003C\u002Fspan> nanos) \u003Cspan>throws\u003C\u002Fspan>\u003Cspan> InterruptedException {\n\u003C\u002Fspan>\u003Cspan>12\u003C\u002Fspan>         \u003Cspan>if\u003C\u002Fspan> (timeoutMillis &lt; 0\u003Cspan>) {\n\u003C\u002Fspan>\u003Cspan>13\u003C\u002Fspan>             \u003Cspan>throw\u003C\u002Fspan> \u003Cspan>new\u003C\u002Fspan> IllegalArgumentException(\"timeoutMillis value is negative\"\u003Cspan>);\n\u003C\u002Fspan>\u003Cspan>14\u003C\u002Fspan> \u003Cspan>        }\n\u003C\u002Fspan>\u003Cspan>15\u003C\u002Fspan> \n\u003Cspan>16\u003C\u002Fspan>         \u003Cspan>if\u003C\u002Fspan> (nanos &lt; 0 || nanos &gt; 999999\u003Cspan>) {\n\u003C\u002Fspan>\u003Cspan>17\u003C\u002Fspan>             \u003Cspan>throw\u003C\u002Fspan> \u003Cspan>new\u003C\u002Fspan>\u003Cspan> IllegalArgumentException(\n\u003C\u002Fspan>\u003Cspan>18\u003C\u002Fspan>                                 \"nanosecond timeout value out of range\"\u003Cspan>);\n\u003C\u002Fspan>\u003Cspan>19\u003C\u002Fspan> \u003Cspan>        }\n\u003C\u002Fspan>\u003Cspan>20\u003C\u002Fspan> \n\u003Cspan>21\u003C\u002Fspan>         \u003Cspan>if\u003C\u002Fspan> (nanos &gt; 0 &amp;&amp; timeoutMillis &lt;\u003Cspan> Long.MAX_VALUE) {\n\u003C\u002Fspan>\u003Cspan>22\u003C\u002Fspan>             timeoutMillis++\u003Cspan>;\n\u003C\u002Fspan>\u003Cspan>23\u003C\u002Fspan> \u003Cspan>        }\n\u003C\u002Fspan>\u003Cspan>24\u003C\u002Fspan> \n\u003Cspan>25\u003C\u002Fspan> \u003Cspan>        wait(timeoutMillis);\n\u003C\u002Fspan>\u003Cspan>26\u003C\u002Fspan>     }\u003C\u002Fspan>\u003C\u002Fpre>\n\u003C\u002Fdiv>\n\u003Cp>\u003Cspan>都是包装了一下，然后最终调用本地方法，其中\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>wait() 线程进入等待（更准确的说法是进入了lock对象的等待集），(防盗连接：本文首发自http:\u002F\u002Fwww.cnblogs.com\u002Fjilodream\u002F )并释放锁\u003C\u002Fspan>\u003Cbr>\u003Cspan>wait(long t) 线程进入等待，等待t的时间，同时释放锁，如果等待时间超过t，则结束等待，进入就绪状态，重新开始争夺监视器锁\u003C\u002Fspan>\u003Cbr>\u003Cspan>notify() 随机通知（唤醒）一个等待中的线程结束等待\u003C\u002Fspan>\u003Cbr>\u003Cspan>notifyAll() 通知（唤醒）所有等待中的线程结束等待\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>了解了这些必要条件，我们来看一个经典的生产消费者通知的问题：\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cdiv>\n \u003Cpre>\u003Cspan>\u003Cspan> 1\u003C\u002Fspan> \u003Cspan>\u002F**\u003C\u002Fspan>\n\u003Cspan> 2\u003C\u002Fspan> \u003Cspan> * @discription\n\u003C\u002Fspan>\u003Cspan> 3\u003C\u002Fspan>  \u003Cspan>*\u002F\u003C\u002Fspan>\n\u003Cspan> 4\u003C\u002Fspan> \u003Cspan>public\u003C\u002Fspan> \u003Cspan>class\u003C\u002Fspan>\u003Cspan> WNStudy {\n\u003C\u002Fspan>\u003Cspan> 5\u003C\u002Fspan> \n\u003Cspan> 6\u003C\u002Fspan>     \u003Cspan>public\u003C\u002Fspan> \u003Cspan>static\u003C\u002Fspan> \u003Cspan>void\u003C\u002Fspan>\u003Cspan> main(String[] args) {\n\u003C\u002Fspan>\u003Cspan> 7\u003C\u002Fspan>         Cache cache = \u003Cspan>new\u003C\u002Fspan>\u003Cspan> Cache();\n\u003C\u002Fspan>\u003Cspan> 8\u003C\u002Fspan>         Runnable producer = () -&gt;\u003Cspan> {\n\u003C\u002Fspan>\u003Cspan> 9\u003C\u002Fspan>             \u003Cspan>try\u003C\u002Fspan>\u003Cspan> {\n\u003C\u002Fspan>\u003Cspan>10\u003C\u002Fspan>                 \u003Cspan>for\u003C\u002Fspan> (\u003Cspan>int\u003C\u002Fspan> i = 0; i &lt; 5; i++\u003Cspan>) {\n\u003C\u002Fspan>\u003Cspan>11\u003C\u002Fspan>                     String id = \"id\" +\u003Cspan> i;\n\u003C\u002Fspan>\u003Cspan>12\u003C\u002Fspan> \u003Cspan>                    cache.put(id);\n\u003C\u002Fspan>\u003Cspan>13\u003C\u002Fspan> \u003Cspan>                }\n\u003C\u002Fspan>\u003Cspan>14\u003C\u002Fspan>             } \u003Cspan>catch\u003C\u002Fspan>\u003Cspan> (InterruptedException e) {\n\u003C\u002Fspan>\u003Cspan>15\u003C\u002Fspan>                 \u003Cspan>\u002F\u002F\u003C\u002Fspan>\u003Cspan>dosth\u003C\u002Fspan>\n\u003Cspan>16\u003C\u002Fspan> \u003Cspan>            }\n\u003C\u002Fspan>\u003Cspan>17\u003C\u002Fspan> \u003Cspan>        };\n\u003C\u002Fspan>\u003Cspan>18\u003C\u002Fspan> \n\u003Cspan>19\u003C\u002Fspan> \n\u003Cspan>20\u003C\u002Fspan>         Runnable consumer = () -&gt;\u003Cspan> {\n\u003C\u002Fspan>\u003Cspan>21\u003C\u002Fspan>             \u003Cspan>try\u003C\u002Fspan>\u003Cspan> {\n\u003C\u002Fspan>\u003Cspan>22\u003C\u002Fspan>                 \u003Cspan>for\u003C\u002Fspan> (\u003Cspan>int\u003C\u002Fspan> i = 0; i &lt; 5; i++\u003Cspan>) {\n\u003C\u002Fspan>\u003Cspan>23\u003C\u002Fspan> \u003Cspan>                   cache.take();\n\u003C\u002Fspan>\u003Cspan>24\u003C\u002Fspan> \u003Cspan>                }\n\u003C\u002Fspan>\u003Cspan>25\u003C\u002Fspan>             } \u003Cspan>catch\u003C\u002Fspan>\u003Cspan> (InterruptedException e) {\n\u003C\u002Fspan>\u003Cspan>26\u003C\u002Fspan>                 \u003Cspan>\u002F\u002F\u003C\u002Fspan>\u003Cspan>dosth\u003C\u002Fspan>\n\u003Cspan>27\u003C\u002Fspan> \u003Cspan>            }\n\u003C\u002Fspan>\u003Cspan>28\u003C\u002Fspan> \u003Cspan>        };\n\u003C\u002Fspan>\u003Cspan>29\u003C\u002Fspan>         Thread tp1 = \u003Cspan>new\u003C\u002Fspan>\u003Cspan> Thread(producer);\n\u003C\u002Fspan>\u003Cspan>30\u003C\u002Fspan>         Thread tp2 = \u003Cspan>new\u003C\u002Fspan>\u003Cspan> Thread(producer);\n\u003C\u002Fspan>\u003Cspan>31\u003C\u002Fspan>         Thread tc1 = \u003Cspan>new\u003C\u002Fspan>\u003Cspan> Thread(consumer);\n\u003C\u002Fspan>\u003Cspan>32\u003C\u002Fspan>         Thread tc2 = \u003Cspan>new\u003C\u002Fspan>\u003Cspan> Thread(consumer);\n\u003C\u002Fspan>\u003Cspan>33\u003C\u002Fspan> \n\u003Cspan>34\u003C\u002Fspan> \u003Cspan>        tp1.start();\n\u003C\u002Fspan>\u003Cspan>35\u003C\u002Fspan> \u003Cspan>        tp2.start();\n\u003C\u002Fspan>\u003Cspan>36\u003C\u002Fspan> \u003Cspan>        tc1.start();\n\u003C\u002Fspan>\u003Cspan>37\u003C\u002Fspan> \u003Cspan>        tc2.start();\n\u003C\u002Fspan>\u003Cspan>38\u003C\u002Fspan> \u003Cspan>    }\n\u003C\u002Fspan>\u003Cspan>39\u003C\u002Fspan> \n\u003Cspan>40\u003C\u002Fspan>     \u003Cspan>static\u003C\u002Fspan> \u003Cspan>class\u003C\u002Fspan>\u003Cspan> Cache {\n\u003C\u002Fspan>\u003Cspan>41\u003C\u002Fspan>         \u003Cspan>private\u003C\u002Fspan> \u003Cspan>final\u003C\u002Fspan> Object lock = \u003Cspan>new\u003C\u002Fspan>\u003Cspan> Object();\n\u003C\u002Fspan>\u003Cspan>42\u003C\u002Fspan>         \u003Cspan>private\u003C\u002Fspan> \u003Cspan>static\u003C\u002Fspan> \u003Cspan>final\u003C\u002Fspan> \u003Cspan>int\u003C\u002Fspan> MAX_LEN = 3\u003Cspan>;\n\u003C\u002Fspan>\u003Cspan>43\u003C\u002Fspan>         \u003Cspan>private\u003C\u002Fspan> String[] idCache = \u003Cspan>new\u003C\u002Fspan>\u003Cspan> String[MAX_LEN];\n\u003C\u002Fspan>\u003Cspan>44\u003C\u002Fspan> \n\u003Cspan>45\u003C\u002Fspan>         \u003Cspan>private\u003C\u002Fspan> \u003Cspan>int\u003C\u002Fspan> size = 0\u003Cspan>;\n\u003C\u002Fspan>\u003Cspan>46\u003C\u002Fspan> \n\u003Cspan>47\u003C\u002Fspan> \n\u003Cspan>48\u003C\u002Fspan>         \u003Cspan>private\u003C\u002Fspan> \u003Cspan>void\u003C\u002Fspan> put(String id) \u003Cspan>throws\u003C\u002Fspan>\u003Cspan> InterruptedException {\n\u003C\u002Fspan>\u003Cspan>49\u003C\u002Fspan>             \u003Cspan>synchronized\u003C\u002Fspan>\u003Cspan> (lock) {\n\u003C\u002Fspan>\u003Cspan>50\u003C\u002Fspan>                 \u003Cspan>while\u003C\u002Fspan> (size ==\u003Cspan> (MAX_LEN)) {\n\u003C\u002Fspan>\u003Cspan>51\u003C\u002Fspan> \u003Cspan>                    lock.wait();\n\u003C\u002Fspan>\u003Cspan>52\u003C\u002Fspan> \u003Cspan>                }\n\u003C\u002Fspan>\u003Cspan>53\u003C\u002Fspan>                 idCache[size] =\u003Cspan> id;\n\u003C\u002Fspan>\u003Cspan>54\u003C\u002Fspan>                 size++\u003Cspan>;\n\u003C\u002Fspan>\u003Cspan>55\u003C\u002Fspan>                 System.out.println(\"producer ok +\" +\u003Cspan> id);\n\u003C\u002Fspan>\u003Cspan>56\u003C\u002Fspan> \u003Cspan>                lock.notifyAll();\n\u003C\u002Fspan>\u003Cspan>57\u003C\u002Fspan> \u003Cspan>            }\n\u003C\u002Fspan>\u003Cspan>58\u003C\u002Fspan> \u003Cspan>        }\n\u003C\u002Fspan>\u003Cspan>59\u003C\u002Fspan> \n\u003Cspan>60\u003C\u002Fspan>         \u003Cspan>private\u003C\u002Fspan> String take() \u003Cspan>throws\u003C\u002Fspan>\u003Cspan> InterruptedException {\n\u003C\u002Fspan>\u003Cspan>61\u003C\u002Fspan>             \u003Cspan>synchronized\u003C\u002Fspan>\u003Cspan> (lock) {\n\u003C\u002Fspan>\u003Cspan>62\u003C\u002Fspan>                 \u003Cspan>while\u003C\u002Fspan> (size == 0\u003Cspan>) {\n\u003C\u002Fspan>\u003Cspan>63\u003C\u002Fspan> \u003Cspan>                    lock.wait();\n\u003C\u002Fspan>\u003Cspan>64\u003C\u002Fspan> \u003Cspan>                }\n\u003C\u002Fspan>\u003Cspan>65\u003C\u002Fspan>                 String id = idCache[size - 1\u003Cspan>];\n\u003C\u002Fspan>\u003Cspan>66\u003C\u002Fspan>                 idCache[size - 1] = \u003Cspan>null\u003C\u002Fspan>\u003Cspan>;\n\u003C\u002Fspan>\u003Cspan>67\u003C\u002Fspan>                 size--\u003Cspan>;\n\u003C\u002Fspan>\u003Cspan>68\u003C\u002Fspan> \u003Cspan>                lock.notifyAll();\n\u003C\u002Fspan>\u003Cspan>69\u003C\u002Fspan>                 System.out.println(\"consumer ok -\" +\u003Cspan> id);\n\u003C\u002Fspan>\u003Cspan>70\u003C\u002Fspan>                 \u003Cspan>return\u003C\u002Fspan>\u003Cspan> id;\n\u003C\u002Fspan>\u003Cspan>71\u003C\u002Fspan> \u003Cspan>            }\n\u003C\u002Fspan>\u003Cspan>72\u003C\u002Fspan> \u003Cspan>        }\n\u003C\u002Fspan>\u003Cspan>73\u003C\u002Fspan> \u003Cspan>    }\n\u003C\u002Fspan>\u003Cspan>74\u003C\u002Fspan> }\u003C\u002Fspan>\u003C\u002Fpre>\n\u003C\u002Fdiv>\n\u003Cp>\u003Cspan>核心逻辑是 tp1\u002Ftp2 两个生产者不断的向栈中推数据\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>tc1\u002Ftc2 两个消费者不断的从栈中取数据\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>执行后结果如下：\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cdiv>\n \u003Cpre>\u003Cspan>\u003Cspan> 1\u003C\u002Fspan> producer ok +\u003Cspan>id0\n\u003C\u002Fspan>\u003Cspan> 2\u003C\u002Fspan> consumer ok -\u003Cspan>id0\n\u003C\u002Fspan>\u003Cspan> 3\u003C\u002Fspan> producer ok +\u003Cspan>id1\n\u003C\u002Fspan>\u003Cspan> 4\u003C\u002Fspan> consumer ok -\u003Cspan>id1\n\u003C\u002Fspan>\u003Cspan> 5\u003C\u002Fspan> producer ok +\u003Cspan>id0\n\u003C\u002Fspan>\u003Cspan> 6\u003C\u002Fspan> consumer ok -\u003Cspan>id0\n\u003C\u002Fspan>\u003Cspan> 7\u003C\u002Fspan> producer ok +\u003Cspan>id1\n\u003C\u002Fspan>\u003Cspan> 8\u003C\u002Fspan> producer ok +\u003Cspan>id2\n\u003C\u002Fspan>\u003Cspan> 9\u003C\u002Fspan> consumer ok -\u003Cspan>id2\n\u003C\u002Fspan>\u003Cspan>10\u003C\u002Fspan> consumer ok -\u003Cspan>id1\n\u003C\u002Fspan>\u003Cspan>11\u003C\u002Fspan> producer ok +\u003Cspan>id3\n\u003C\u002Fspan>\u003Cspan>12\u003C\u002Fspan> producer ok +\u003Cspan>id2\n\u003C\u002Fspan>\u003Cspan>13\u003C\u002Fspan> consumer ok -\u003Cspan>id2\n\u003C\u002Fspan>\u003Cspan>14\u003C\u002Fspan> consumer ok -\u003Cspan>id3\n\u003C\u002Fspan>\u003Cspan>15\u003C\u002Fspan> producer ok +\u003Cspan>id3\n\u003C\u002Fspan>\u003Cspan>16\u003C\u002Fspan> producer ok +\u003Cspan>id4\n\u003C\u002Fspan>\u003Cspan>17\u003C\u002Fspan> consumer ok -\u003Cspan>id4\n\u003C\u002Fspan>\u003Cspan>18\u003C\u002Fspan> consumer ok -\u003Cspan>id3\n\u003C\u002Fspan>\u003Cspan>19\u003C\u002Fspan> producer ok +\u003Cspan>id4\n\u003C\u002Fspan>\u003Cspan>20\u003C\u002Fspan> consumer ok -id4\u003C\u002Fspan>\u003C\u002Fpre>\n\u003C\u002Fdiv>\n\u003Cp>\u003Cspan>我们可以看到栈的数据是符合入栈出栈的逻辑的。\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>回到api本身，wait(long t)，表示时间到了，线程会自动被唤醒进入就绪状态，无需其它线程唤醒。\u003C\u002Fspan>\u003Cbr>\u003Cspan>同时需要注意的是，唤醒通知非累加动作，notify只影响到等待集中的线程，未进入等待集的线程，不受唤醒的影响，也不会累加这个状态，所收到的唤醒通知也会被丢弃，需要进入wait时重新被唤醒。\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>不知道大家发现没有，如果由于notify 是随机的，而notifyAll 唤醒所有线程后，究竟是哪个线程抢到锁继续执行，也是未知的。(防盗连接：本文首发自http:\u002F\u002Fwww.cnblogs.com\u002Fjilodream\u002F )因此可能不是期待中的线程唤醒。比如例子中，可能队列满了，唤醒抢到锁的线程仍然是消费者线程。从而导致无意义的唤醒和重新等待，从而浪费了性能。这种现象我们称之为惊群效应。传统的wait\u002Fnotify 方式是无法避免这种情况的，需要由更高级的ReentrantLock 配合condition才可以。这个放在后边的文章来说吧。\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>由于随机性，和不确定性，因此当线程被唤醒后，往往需要重新检查就绪条件，如果不满足还需要重新等待，从而避免被其它线程虚假唤醒，\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>\u003Cstrong>反思\u003C\u002Fstrong>\u003C\u002Fspan>\u003C\u002Fp>\n\u003Cp>\u003Cspan>今天看了下，这个主题中，距离上一篇相关文章已经有十年了，想想很惭愧，但是再一细想，自己也一直没有松懈。\u003C\u002Fspan>\u003Cbr>\u003Cspan>反思了一下为什么拖了这么久，\u003C\u002Fspan>\u003Cbr>\u003Cspan>一方面是使用的技术不断的在演进，刚开始的项目中，接触到的业务中多线程还需要使用wait notify来协作，后边主要在做服务端开发，服务端又慢慢的进入到分布式场景中，这种进程内的锁的使用就更少了。即使有用到，完整的并发工具包也早已满足大部分的业务需要。\u003C\u002Fspan>\u003Cbr>\u003Cspan>一方面是操作的业务不断的在变化，需要学习的东西也不断的在变，最早是原生自研的框架，后边是spring，springboot ，再然后是容器化技术，消息队列，读写分离，db\u002F缓存技术，ai技术等等。基础的东西是很重要，但是不能全身心的死磕基础技术，而放弃了更高层的应用。学习的优先级必然应该向使用方向来倾斜，而不应该将大部分精力花费到太多的用不到，甚至不需要理解的技术层面上。但这样又必然会造成知其然而不知其所以然的窘境。学习，是挺难的。\u003C\u002Fspan>\u003C\u002Fp>","线程的协作多种多样，这次主要说说wait和notify两种Object的方法。这也是java早期原生的重要的协作机制之一。 先说下这两个英文单词： wait [weɪt] v.等待;等候;(尤指长期地)希望，盼望，期待 notify [ˈnəʊtɪfaɪ] vt.通知;(正式)通报; 也就是一个等待，一个通知。 一般的使用场景就是： 线程a，运行到某一刻发现条件不满足，执行等待方法， 线程b，将条件满足，通知等待的线程， 线程a，收到通知请求，判断等待条件是否满足，满足后继续执行，不满足继续等待。 核心思路就是下边这图这样 那不使用wait\u002Fnotify等待通知的方式来协作，线程a就一直while循环，等待条件满足，再继续执行可以么？ 当然可以，这种情况我们也称之为 忙等（busy-waiting \u002F spin）。自旋锁采用的就是这个逻辑。 但是自旋往往比较占用性能，大部分场景没这个必要。 我们更多的时候还是希望线程主动让出占用的cpu资源，(防盗连接：本文首发自http:\u002F\u002Fwww.cnblogs.com\u002Fjilodream\u002F )等到条件合适再继续占用。 在确定完使用场景，我们还要注意到，wait 和notify 的使用，是放置在Object 方法上的，为了保证不被乱调，只有拿到了该Object的监视器锁的线程才有资格调用wait\u002Fnotify方法的，否则就会报出IllegalMonitorStateException的运行时异常。注意看注释的解释：当前线程如果不是object 的显示器锁的持有者，就抛出该异常。 IllegalMonitorStateException if the current thread is not the owner of the object's monitor。 这里所谓的监视器锁，就是我们平常说的同步锁，synchronized锁。 同理由于同步块的存在，被通知唤醒的线程，只是进入就绪状态，还是需要再次拿到锁才可以继续进行。 具体的几个核心方法源码如下： 1 public final native void notify(); 2 3 public final native void notifyAll(); 4 5 public final void wait() throws InterruptedException { 6 wait(0L); 7 } 8 9 public final native void wait(long timeoutMillis) throws InterruptedException; 10 11 public final void wait(long timeoutMillis, int nanos) throws InterruptedException { 12 if (timeoutMillis \u003C 0) { 13 throw new IllegalArgumentException(\"timeoutMillis value is negative\"); 14 } 15 16 if (nanos \u003C 0 || nanos > 999999) { 17 throw new IllegalArgumentException( 18 \"nanosecond timeout value out of range\"); 19 } 20 21 if (nanos > 0 && timeoutMillis \u003C Long.MAX_VALUE) { 22 timeoutMillis++; 23 } 24 25 wait(timeoutMillis); 26 } 都是包装了一下，然后最终调用本地方法，其中 wait() 线程进入等待（更准确的说法是进入了lock对象的等待集），(防盗连接：本文首发自http:\u002F\u002Fwww.cnblogs.com\u002Fjilodream\u002F )并释放锁 wait(long t) 线程进入等待，等待t的时间，同时释放锁，如果等待时间超过t，则结束等待，进入就绪状态，重新开始争夺监视器锁 notify() 随机通知（唤醒）一个等待中的线程结束等待 notifyAll() 通知（唤醒）所有等待中的线程结束等待 了解了这些必要条件，我们来看一个经典的生产消费者通知的问题： 1 \u002F** 2 * @discription 3 *\u002F 4 public class WNStudy { 5 6 public static void main(String[] args) { 7 Cache cache = new Cache(); 8 Runnable producer = () -> { 9 try { 10 for (int i = 0; i \u003C 5; i++) { 11 String id = \"id\" + i; 12 cache.put(id); 13 } 14 } catch (InterruptedException e) { 15 \u002F\u002Fdosth 16 } 17 }; 18 19 20 Runnable consumer = () -> { 21 try { 22 for (int i = 0; i \u003C 5; i++) { 23 cache.take(); 24 } 25 } catch (InterruptedException e) { 26 \u002F\u002Fdosth 27 } 28 }; 29 Thread tp1 = new Thread(producer); 30 Thread tp2 = new Thread(producer); 31 Thread tc1 = new Thread(consumer); 32 Thread tc2 = new Thread(consumer); 33 34 tp1.start(); 35 tp2.start(); 36 tc1.start(); 37 tc2.start(); 38 } 39 40 static class Cache { 41 private final Object lock = new Object(); 42 private static final int MAX_LEN = 3; 43 private String[] idCache = new String[MAX_LEN]; 44 45 private int size = 0; 46 47 48 private void put(String id) throws InterruptedException { 49 synchronized (lock) { 50 while (size == (MAX_LEN)) { 51 lock.wait(); 52 } 53 idCache[size] = id; 54 size++; 55 System.out.println(\"producer ok +\" + id); 56 lock.notifyAll(); 57 } 58 } 59 60 private String take() throws InterruptedException { 61 synchronized (lock) { 62 while (size == 0) { 63 lock.wait(); 64 } 65 String id = idCache[size - 1]; 66 idCache[size - 1] = null; 67 size--; 68 lock.notifyAll(); 69 System.out.println(\"consumer ok -\" + id); 70 return id; 71 } 72 } 73 } 74 } 核心逻辑是 tp1\u002Ftp2 两个生产者不断的向栈中推数据 tc1\u002Ftc2 两个消费者不断的从栈中取数据 执行后结果如下： 1 producer ok +id0 2 consumer ok -id0 3 producer ok +id1 4 consumer ok -id1 5 producer ok +id0 6 consumer ok -id0 7 producer ok +id1 8 producer ok +id2 9 consumer ok -id2 10 consumer ok -id1 11 producer ok +id3 12 producer ok +id2 13 consumer ok -id2 14 consumer ok -id3 15 producer ok +id3 16 producer ok +id4 17 consumer ok -id4 18 consumer ok -id3 19 producer ok +id4 20 consumer ok -id4 我们可以看到栈的数据是符合入栈出栈的逻辑的。 回到api本身，wait(long t)，表示时间到了，线程会自动被唤醒进入就绪状态，无需其它线程唤醒。 同时需要注意的是，唤醒通知非累加动作，notify只影响到等待集中的线程，未进入等待集的线程，不受唤醒的影响，也不会累加这个状态，所收到的唤醒通知也会被丢弃，需要进入wait时重新被唤醒。 不知道大家发现没有，如果由于notify 是随机的，而notifyAll 唤醒所有线程后，究竟是哪个线程抢到锁继续执行，也是未知的。(防盗连接：本文首发自http:\u002F\u002Fwww.cnblogs.com\u002Fjilodream\u002F )因此可能不是期待中的线程唤醒。比如例子中，可能队列满了，唤醒抢到锁的线程仍然是消费者线程。从而导致无意义的唤醒和重新等待，从而浪费了性能。这种现象我们称之为惊群效应。传统的wait\u002Fnotify 方式是无法避免这种情况的，需要由更高级的ReentrantLock 配合condition才可以。这个放在后边的文章来说吧。 由于随机性，和不确定性，因此当线程被唤醒后，往往需要重新检查就绪条件，如果不满足还需要重新等待，从而避免被其它线程虚假唤醒， 反思 今天看了下，这个主题中，距离上一篇相关文章已经有十年了，想想很惭愧，但是再一细想，自己也一直没有松懈。 反思了一下为什么拖了这么久， 一方面是使用的技术不断的在演进，刚开始的项目中，接触到的业务中多线程还需要使用wait notify来协作，后边主要在做服务端开发，服务端又慢慢的进入到分布式场景中，这种进程内的锁的使用就更少了。即使有用到，完整的并发工具包也早已满足大部分的业务需要。 一方面是操作的业务不断的在变化，需要学习的东西也不断的在变，最早是原生自研的框架，后边是spring，springboot ，再然后是容器化技术，消息队列，读写分离，db\u002F缓存技术，ai技术等等。基础的东西是很重要，但是不能全身心的死磕基础技术，而放弃了更高层的应用。学习的优先级必然应该向使用方向来倾斜，而不应该将大部分精力花费到太多的用不到，甚至不需要理解的技术层面上。但这样又必然会造成知其然而不知其所以然的窘境。学习，是挺难的。",4076,{"id":6,"kind":7,"title":11,"summary":13,"image":14,"href":16,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":40},"2026 · 软件开发","#2563eb","16 \u002F 10",[19],{"targetType":8,"targetId":9,"likedByMe":42,"likeCount":43,"commentCount":43,"contentLikeCount":43,"contentCommentCount":43,"sourceLikeCount":43,"sourceCommentCount":43},false,0,[45,51,58,64,70,77,84,90],{"id":46,"kind":7,"title":47,"summary":48,"image":15,"href":49,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":50},"NEWS_ARTICLE:832","用 runtime-async 写一个支持 async\u002Fawait 的轻量脚本引擎","起因：一个好奇 事情的开头很简单。 给 .NET 做过动态脚本的人大概都碰到过同一堵墙：表达式树也好、Reflection.Emit 也好，都很难支持 async\u002Fawait。原因不在语法，而在 await 的实现方式——C# 编译器要为每个 async 方法生成一个状态机结构体，把方法体切成若干片","\u002Fnews\u002F832",[19],{"id":52,"kind":7,"title":53,"summary":54,"image":55,"href":56,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":57},"NEWS_ARTICLE:855","被罚了500后，整个人都变老实了","我们组有个同事，以前是那种闲不住的人。现在也闲不住 只不过是不敢动了。 去年部门空降了一位新总监，阿里P8，第一次全员会PPT上就八个大字：拥抱变化，永不言弃。 新领导上来搞流程改革，每个模块指定Owner，出事追责到人，A级事故罚款500起步，B级300 依次类推，发版上线改配置全走审批。说实话之","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F273387\u002F202608\u002F273387-20260818222617092-925481998.png","\u002Fnews\u002F855",[19],{"id":59,"kind":7,"title":60,"summary":61,"image":15,"href":62,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":63},"NEWS_ARTICLE:862","go中make声明切片, 修改切片,append给切片扩容,合并切片,复制切片","make 声明切片 make([]类型, 长度， 容量) kage main import (&quot;fmt&quot;) \u002F\u002F 入口函数 func main() { \u002F\u002F make 声明切片，长度是4， 容量是10 var sliceArr1 = make([]int, 4, 10) fmt.","\u002Fnews\u002F862",[19],{"id":65,"kind":7,"title":66,"summary":67,"image":15,"href":68,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":69},"NEWS_ARTICLE:866","Spring Boot事件监听，这东西到底解决啥问题？","一、先聊个场景，你就明白这玩意儿干啥的了 点过外卖吧？那咱们就用这个场景来说事。 你掏出手机下了单，付了钱。接下来会发生啥？ 厨房那头开始备菜炒菜（这件事耽误不得，客户饿着呢） 手机收到一条短信：&quot;您的订单已收到，预计30分钟送达&quot; 你的会员账户里多了一堆积分 店长那个收银小喇叭","\u002Fnews\u002F866",[19],{"id":71,"kind":7,"title":72,"summary":73,"image":74,"href":75,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":76},"NEWS_ARTICLE:881","RSA 密码传输加密通用接入方案","@目录前言一、介绍二、RSA 在本方案中的角色三、密文协议格式四、密钥生成与 Apollo 配置4.1 编译工具类4.2 执行生成密钥4.3 执行输出示例4.4 Apollo 配置示例五、后端接入方式AOP 注解示例六、后端解密流程七、前端接入方式7.1 获取公钥和 nonce7.2 使用 RSA-","https:\u002F\u002Fimg2024.cnblogs.com\u002Fblog\u002F1867541\u002F202608\u002F1867541-20260831165110853-162420906.png","\u002Fnews\u002F881",[19],{"id":78,"kind":7,"title":79,"summary":80,"image":81,"href":82,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":83},"NEWS_ARTICLE:888","[开源] LogCrate：免解压、支持筛选和 AI 分析的 PC 端桌面日志工具","日常排查客户软件运行问题时，经常需要反复下载日志、解压缩，再从一堆文件里慢慢翻找，过程比较麻烦；而且很多日志工具也不支持针对具体字段进行筛选。 对比了一些市面上的日志分析工具后，发现能够同时兼容归档阅读、目录监控、到达通知、结构化筛选和 AI 分析的工具并不多，于是就自己动手做了一款。 PS：自己做","https:\u002F\u002Fiili.io\u002FCy4ztaa.png","\u002Fnews\u002F888",[19],{"id":85,"kind":7,"title":86,"summary":87,"image":15,"href":88,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":89},"NEWS_ARTICLE:902","DeepSeek Harness 插件","准备环境 dsh 从源码运行，见 dsh 说明。或见之前分享的 DeepSeek Harness 开始。 开发插件 依照 dsh 文档，动手做一遍： 第一个插件: https:\u002F\u002Fdeepseek-harness.github.io\u002Fdeepseek-harness\u002Fdevelop\u002Fbasic\u002F 第","\u002Fnews\u002F902",[19],{"id":91,"kind":7,"title":92,"summary":93,"image":94,"href":95,"meta":37,"badge":10,"author":12,"stats":-1,"accent":38,"coverRatio":39,"tags":96},"NEWS_ARTICLE:917","[python] pywinauto使用指北","当业务系统没有API、命令行接口或可直接集成的数据通道时，桌面自动化往往是打通业务流程的最后一公里。pywinauto库通过Win32 API与Microsoft UI Automation（UIA）访问Windows窗口及控件，使Python脚本能够驱动桌面应用。本文聚焦于pywinauto的基础","https:\u002F\u002Fgitlab.com\u002Fluohenyueji\u002Farticle_picture_warehouse\u002F-\u002Fraw\u002Fmain\u002FCSDN\u002F%5Bpython%5D%20pywinauto%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8C%97\u002Fimgs\u002F1.jpg","\u002Fnews\u002F917",[19]]