-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
250 lines (230 loc) · 50 KB
/
Copy pathsearch.xml
File metadata and controls
250 lines (230 loc) · 50 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>MyBatis入门</title>
<url>/2018/04/02/MyBatis%E5%85%A5%E9%97%A8/</url>
<content><![CDATA[<h1 id="入门"><a href="#入门" class="headerlink" title="入门"></a>入门</h1><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><blockquote>
<p>要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于 classpath 中即可。如果使用 Maven 来构建项目,则需将下面的 dependency 代码置于 pom.xml 文件中:</p>
</blockquote>
<a id="more"></a>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.mybatis<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>mybatis<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>x.x.x<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure>
<h2 id="从-XML-中构建-SqlSessionFactory"><a href="#从-XML-中构建-SqlSessionFactory" class="headerlink" title="从 XML 中构建 SqlSessionFactory"></a>从 XML 中构建 SqlSessionFactory</h2><p>每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。</p>
<p>从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。但是也可以使用任意的输入流(InputStream)实例,包括字符串形式的文件路径或者 file:// 的 URL 形式的文件路径来配置。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,可使从 classpath 或其他位置加载资源文件更加容易。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">String resource = <span class="string">"org/mybatis/example/mybatis-config.xml"</span>;</span><br><span class="line">InputStream inputStream = Resources.getResourceAsStream(resource);</span><br><span class="line">SqlSessionFactory sqlSessionFactory = <span class="keyword">new</span> SqlSessionFactoryBuilder().build(inputStream);</span><br></pre></td></tr></table></figure>
<p>XML 配置文件(configuration XML)中包含了对 MyBatis 系统的核心设置,包含获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)。XML 配置文件的详细内容后面再探讨,这里先给出一个简单的示例:</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8" ?></span></span><br><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">configuration</span></span></span><br><span class="line"><span class="meta"> <span class="meta-keyword">PUBLIC</span> <span class="meta-string">"-//mybatis.org//DTD Config 3.0//EN"</span></span></span><br><span class="line"><span class="meta"> <span class="meta-string">"http://mybatis.org/dtd/mybatis-3-config.dtd"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">configuration</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">environments</span> <span class="attr">default</span>=<span class="string">"development"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">environment</span> <span class="attr">id</span>=<span class="string">"development"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">transactionManager</span> <span class="attr">type</span>=<span class="string">"JDBC"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">dataSource</span> <span class="attr">type</span>=<span class="string">"POOLED"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"driver"</span> <span class="attr">value</span>=<span class="string">"${driver}"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"url"</span> <span class="attr">value</span>=<span class="string">"${url}"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"username"</span> <span class="attr">value</span>=<span class="string">"${username}"</span>/></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"password"</span> <span class="attr">value</span>=<span class="string">"${password}"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">dataSource</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">environment</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">environments</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">mappers</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">mapper</span> <span class="attr">resource</span>=<span class="string">"org/mybatis/example/BlogMapper.xml"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">mappers</span>></span></span><br><span class="line"><span class="tag"></<span class="name">configuration</span>></span></span><br></pre></td></tr></table></figure>
<p>当然,还有很多可以在XML 文件中进行配置,上面的示例指出的则是最关键的部分。要注意 XML 头部的声明,用来验证 XML 文档正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则是包含一组 mapper 映射器(这些 mapper 的 XML 文件包含了 SQL 代码和映射定义信息)。</p>
<h3 id="不使用-XML-构建-SqlSessionFactory"><a href="#不使用-XML-构建-SqlSessionFactory" class="headerlink" title="不使用 XML 构建 SqlSessionFactory"></a>不使用 XML 构建 SqlSessionFactory</h3><p>如果你更愿意直接从 Java 程序而不是 XML 文件中创建 configuration,或者创建你自己的 configuration 构建器,MyBatis 也提供了完整的配置类,提供所有和 XML 文件相同功能的配置项。</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();</span><br><span class="line">TransactionFactory transactionFactory = <span class="keyword">new</span> JdbcTransactionFactory();</span><br><span class="line">Environment environment = <span class="keyword">new</span> Environment(<span class="string">"development"</span>, transactionFactory, dataSource);</span><br><span class="line">Configuration configuration = <span class="keyword">new</span> Configuration(environment);</span><br><span class="line">configuration.addMapper(BlogMapper<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">SqlSessionFactory sqlSessionFactory = <span class="keyword">new</span> SqlSessionFactoryBuilder().build(configuration);</span><br></pre></td></tr></table></figure>
<p>注意该例中,configuration 添加了一个映射器类(mapper class)。映射器类是 Java 类,它们包含 SQL 映射语句的注解从而避免了 XML 文件的依赖。不过,由于 Java 注解的一些限制加之某些 MyBatis 映射的复杂性,XML 映射对于大多数高级映射(比如:嵌套 Join 映射)来说仍然是必须的。有鉴于此,如果存在一个对等的 XML 配置文件的话,MyBatis 会自动查找并加载它(这种情况下, BlogMapper.xml 将会基于类路径和 BlogMapper.class 的类名被加载进来)。具体细节稍后讨论。</p>
<h3 id="从-SqlSessionFactory-中获取-SqlSession"><a href="#从-SqlSessionFactory-中获取-SqlSession" class="headerlink" title="从 SqlSessionFactory 中获取 SqlSession"></a>从 SqlSessionFactory 中获取 SqlSession</h3><p>既然有了 SqlSessionFactory ,顾名思义,我们就可以从中获得 SqlSession 的实例了。SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">SqlSession session = sqlSessionFactory.openSession();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> Blog blog = (Blog) session.selectOne(<span class="string">"org.mybatis.example.BlogMapper.selectBlog"</span>, <span class="number">101</span>);</span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> session.close();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>诚然这种方式能够正常工作,并且对于使用旧版本 MyBatis 的用户来说也比较熟悉,不过现在有了一种更直白的方式。使用对于给定语句能够合理描述参数和返回值的接口(比如说BlogMapper.class),你现在不但可以执行更清晰和类型安全的代码,而且还不用担心易错的字符串字面值以及强制类型转换。<br>例如:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">SqlSession session = sqlSessionFactory.openSession();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> BlogMapper mapper = session.getMapper(BlogMapper<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> Blog blog = mapper.selectBlog(<span class="number">101</span>);</span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> session.close();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>现在我们来探究一下这里到底是怎么执行的。</p>
<h3 id="探究已映射的-SQL-语句"><a href="#探究已映射的-SQL-语句" class="headerlink" title="探究已映射的 SQL 语句"></a>探究已映射的 SQL 语句</h3><p>现在,或许你很想知道 SqlSession 和 Mapper 到底执行了什么操作,而 SQL 语句映射是个相当大的话题,可能会占去文档的大部分篇幅。不过为了让你能够了解个大概,这里会给出几个例子。</p>
<p>在上面提到的两个例子中,一个语句应该是通过 XML 定义,而另外一个则是通过注解定义。先看 XML 定义这个,事实上 MyBatis 提供的全部特性可以利用基于 XML 的映射语言来实现,这使得 MyBatis 在过去的数年间得以流行。如果你以前用过 MyBatis,这个概念应该会比较熟悉。不过 XML 映射文件已经有了很多的改进,随着文档的进行会愈发清晰。这里给出一个基于 XML 映射语句的示例,它应该可以满足上述示例中 SqlSession 的调用。</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8" ?></span></span><br><span class="line"><span class="meta"><!DOCTYPE <span class="meta-keyword">mapper</span></span></span><br><span class="line"><span class="meta"> <span class="meta-keyword">PUBLIC</span> <span class="meta-string">"-//mybatis.org//DTD Mapper 3.0//EN"</span></span></span><br><span class="line"><span class="meta"> <span class="meta-string">"http://mybatis.org/dtd/mybatis-3-mapper.dtd"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">mapper</span> <span class="attr">namespace</span>=<span class="string">"org.mybatis.example.BlogMapper"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">select</span> <span class="attr">id</span>=<span class="string">"selectBlog"</span> <span class="attr">resultType</span>=<span class="string">"Blog"</span>></span></span><br><span class="line"> select * from Blog where id = #{id}</span><br><span class="line"> <span class="tag"></<span class="name">select</span>></span></span><br><span class="line"><span class="tag"></<span class="name">mapper</span>></span></span><br></pre></td></tr></table></figure>
<p>对于这个简单的例子来说似乎有点小题大做了,但实际上它是非常轻量级的。在一个 XML 映射文件中,你想定义多少个映射语句都是可以的,这样下来,XML 头部和文档类型声明占去的部分就显得微不足道了。文件的剩余部分具有很好的自解释性。在命名空间“org.mybatis.example.BlogMapper”中定义了一个名为“selectBlog”的映射语句,这样它就允许你使用指定的完全限定名“org.mybatis.example.BlogMapper.selectBlog”来调用映射语句,就像上面的例子中做的那样:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">Blog blog = (Blog) session.selectOne(<span class="string">"org.mybatis.example.BlogMapper.selectBlog"</span>, <span class="number">101</span>);</span><br></pre></td></tr></table></figure>
<p>你可能注意到这和使用完全限定名调用 Java 对象的方法是相似的,之所以这样做是有原因的。这个命名可以直接映射到在命名空间中同名的 Mapper 类,并将已映射的 select 语句中的名字、参数和返回类型匹配成方法。这样你就可以像上面那样很容易地调用这个对应 Mapper 接口的方法。不过让我们再看一遍下面的例子:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">BlogMapper mapper = session.getMapper(BlogMapper<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line">Blog blog = mapper.selectBlog(<span class="number">101</span>);</span><br></pre></td></tr></table></figure>
<p>第二种方法有很多优势,首先它不是基于字符串常量的,就会更安全;其次,如果你的 IDE 有代码补全功能,那么你可以在有了已映射 SQL 语句的基础之上利用这个功能。</p>
<h4 id="命名空间的一点注释"><a href="#命名空间的一点注释" class="headerlink" title="命名空间的一点注释"></a>命名空间的一点注释</h4><p><code>命名空间(Namespaces)</code>在之前版本的 MyBatis 中是可选的,这样容易引起混淆因此毫无益处。现在命名空间则是必须的,且意于简单地用更长的完全限定名来隔离语句。</p>
<p>命名空间使得你所见到的接口绑定成为可能,尽管你觉得这些东西未必用得上,你还是应该遵循这里的规定以防哪天你改变了主意。出于长远考虑,使用命名空间,并将它置于合适的 Java 包命名空间之下,你将拥有一份更加整洁的代码并提高了 MyBatis 的可用性。</p>
<p><code>命名解析</code>:为了减少输入量,MyBatis 对所有的命名配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。 </p>
<ul>
<li>完全限定名(比如“com.mypackage.MyMapper.selectAllThings”)将被直接查找并且找到即用。 </li>
<li>短名称(比如“selectAllThings”)如果全局唯一也可以作为一个单独的引用。如果不唯一,有两个或两个以上的相同名称(比如“com.foo.selectAllThings ”和“com.bar.selectAllThings”),那么使用时就会收到错误报告说短名称是不唯一的,这种情况下就必须使用完全限定名。 </li>
</ul>
<p>对于像 BlogMapper 这样的映射器类(Mapper class)来说,还有另一招来处理。它们的映射的语句可以不需要用 XML 来做,取而代之的是可以使用 Java 注解。比如,上面的 XML 示例可被替换如下:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> org.mybatis.example;</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">BlogMapper</span> </span>{</span><br><span class="line"> <span class="meta">@Select</span>(<span class="string">"SELECT * FROM blog WHERE id = #{id}"</span>)</span><br><span class="line"> <span class="function">Blog <span class="title">selectBlog</span><span class="params">(<span class="keyword">int</span> id)</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>对于简单语句来说,注解使代码显得更加简洁,然而 Java 注解对于稍微复杂的语句就会力不从心并且会显得更加混乱。因此,如果你需要做很复杂的事情,那么最好使用 XML 来映射语句。</p>
<p>选择何种方式以及映射语句的定义的一致性对你来说有多重要这些完全取决于你和你的团队。换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。</p>
<h3 id="作用域(Scope)和生命周期"><a href="#作用域(Scope)和生命周期" class="headerlink" title="作用域(Scope)和生命周期"></a>作用域(Scope)和生命周期</h3><p>理解我们目前已经讨论过的不同作用域和生命周期类是至关重要的,因为错误的使用会导致非常严重的并发问题。</p>
<blockquote>
<p>对象生命周期和依赖注入框架</p>
</blockquote>
<p>依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器(mapper)并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。如果对如何通过依赖注入框架来使用 MyBatis 感兴趣可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。</p>
<h4 id="SqlSessionFactoryBuilder"><a href="#SqlSessionFactoryBuilder" class="headerlink" title="SqlSessionFactoryBuilder"></a>SqlSessionFactoryBuilder</h4><p>这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情。</p>
<h4 id="SqlSessionFactory"><a href="#SqlSessionFactory" class="headerlink" title="SqlSessionFactory"></a>SqlSessionFactory</h4><p>SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。</p>
<h4 id="SqlSession"><a href="#SqlSession" class="headerlink" title="SqlSession"></a>SqlSession</h4><p>每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理作用域中,比如 Servlet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。下面的示例就是一个确保 SqlSession 关闭的标准模式:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">SqlSession session = sqlSessionFactory.openSession();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">// do work</span></span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> session.close();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在你的所有的代码中一致性地使用这种模式来保证所有数据库资源都能被正确地关闭。</p>
<h3 id="映射器实例(Mapper-Instances)"><a href="#映射器实例(Mapper-Instances)" class="headerlink" title="映射器实例(Mapper Instances)"></a>映射器实例(Mapper Instances)</h3><p>映射器是一个你创建来绑定你映射的语句的接口。映射器接口的实例是从 SqlSession 中获得的。因此从技术层面讲,任何映射器实例的最大作用域是和请求它们的 SqlSession 相同的。尽管如此,映射器实例的最佳作用域是方法作用域。也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可废弃。并不需要显式地关闭映射器实例,尽管在整个请求作用域(request scope)保持映射器实例也不会有什么问题,但是很快你会发现,像 SqlSession 一样,在这个作用域上管理太多的资源的话会难于控制。所以要保持简单,最好把映射器放在方法作用域(method scope)内。下面的示例就展示了这个实践:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line">SqlSession session = sqlSessionFactory.openSession();</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> BlogMapper mapper = session.getMapper(BlogMapper<span class="class">.<span class="keyword">class</span>)</span>;</span><br><span class="line"> <span class="comment">// do work</span></span><br><span class="line">} <span class="keyword">finally</span> {</span><br><span class="line"> session.close();</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
]]></content>
<tags>
<tag>MyBatis</tag>
</tags>
</entry>
<entry>
<title>多线程常用设计</title>
<url>/2018/05/22/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%B8%B8%E7%94%A8%E8%AE%BE%E8%AE%A1/</url>
<content><![CDATA[<h3 id="1-读写分离锁设计模式"><a href="#1-读写分离锁设计模式" class="headerlink" title="1 读写分离锁设计模式"></a>1 读写分离锁设计模式</h3><blockquote>
<p>场景:多线程对共享资源进行读写操作时同时读是可以的;某线程在进行写操作,则其他线程无法进行读写操作;某些线程在执行读操作,则其他线程无法进行写操作。</p>
</blockquote>
<p>==当读操作明显多于写操作时,对读操作不加锁明显能提升性能。==</p>
<h4 id="读写分离锁的实现"><a href="#读写分离锁的实现" class="headerlink" title="读写分离锁的实现"></a>读写分离锁的实现</h4><blockquote>
<p>Lock接口以及其子类WriteLock、ReadLock,ReadWriteLock接口用于创建read lock和write lock,并提供查询reader和writer数量的方法;</p>
</blockquote>
<a id="more"></a>
<h4 id="ReadLock实现:"><a href="#ReadLock实现:" class="headerlink" title="ReadLock实现:"></a>ReadLock实现:</h4><blockquote>
<p>lock方法:获取排他锁,检查当前writer数量是否大于0,大于0表示有线程在进行写操作,则wait;小于等于0表示没有其他线程在写,则成功获取锁,并将reader数量加1;</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(readWriteLock.getMutex()) {</span><br><span class="line"> <span class="keyword">while</span>(readWriteLock.getWritingWriters() > <span class="number">0</span> || (readWriteLock.getPreferWrite() && readWriteLock.getWaitingWriters() > <span class="number">0</span>)) {</span><br><span class="line"> readWriteLock.getMutex().wait();</span><br><span class="line"> }</span><br><span class="line"> readWriteLock.incrementReadingReaders();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>unlock方法:获取排它锁,然后将reader数量减1,并notifyAll其他等待的线程。</p>
</blockquote>
<script src="//gist.github.com/185a727cbfad60db2079a820ccf92fc9.js"></script>
<h4 id="WriteLock实现:"><a href="#WriteLock实现:" class="headerlink" title="WriteLock实现:"></a>WriteLock实现:</h4><blockquote>
<p>lock方法:获取排它锁,将等待的writer数量加1,然后检查reader和writer的数量,大于0则wait,都小于等于0表示可进行写操作,将等待的writer数量减1,然后对writer数量加1;</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">lock</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(readWriteLock.getMutex()) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> readWriteLock.incrementWaitingWriters();</span><br><span class="line"> <span class="keyword">while</span>(readWriteLock.getReadingReaders() > <span class="number">0</span> || readWriteLock.getWritingWriters() > <span class="number">0</span>) {</span><br><span class="line"> readWriteLock.getMutex().wait();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> readWriteLock.decrementWaitingWriters();</span><br><span class="line"> }</span><br><span class="line"> readWriteLock.incrementWritingWriters();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>unlock方法:获取排它锁,将writer数量减1,并notifyAll其他等待的线程。</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">unlock</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(readWriteLock.getMutex()) {</span><br><span class="line"> readWriteLock.decrementWritingWriters();</span><br><span class="line"> readWriteLock.changePrefer(<span class="keyword">false</span>);</span><br><span class="line"> readWriteLock.getMutex().notifyAll();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="排它锁的使用是为了保护reader和writer计数器。"><a href="#排它锁的使用是为了保护reader和writer计数器。" class="headerlink" title="排它锁的使用是为了保护reader和writer计数器。"></a>排它锁的使用是为了保护reader和writer计数器。</h4><blockquote>
<p>技巧:可设置一个写偏好变量,当读操作获取到锁时,将写偏好变量设置为true,表示读操作完成后写操作优先去获取锁(在读锁的lock方法中检查偏好变量是否为true);当写操作完成时释放锁,并将写操作偏好设置为false,让读操作有更多机会获取锁。</p>
</blockquote>
<h3 id="2-Future设计"><a href="#2-Future设计" class="headerlink" title="2 Future设计"></a>2 Future设计</h3><blockquote>
<p>核心思路:将任务提交后,返回一个Future对象。当任务执行线程在任务完成后将返回值写回到Future中,并设置完成标识,并notifyAll所有wait结果的调用方。如果任务还没有完成,调用方通过future.get获取返回值时,future内部会检查是否任务已经完成,如果没有完成,则进入wait,等待完成后的notify,已完成则返回结果。</p>
</blockquote>
<h4 id="核心代码"><a href="#核心代码" class="headerlink" title="核心代码"></a>核心代码</h4><blockquote>
<p>任务提交:</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> Future<?> submit(Task<IN, OUT> task, IN input) {</span><br><span class="line"> <span class="keyword">final</span> FutureTask<OUT> future = <span class="keyword">new</span> FutureTask<>();</span><br><span class="line"> <span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> OUT result = task.get(input);</span><br><span class="line"> future.finish(result);</span><br><span class="line"> }).start();</span><br><span class="line"> <span class="keyword">return</span> future;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>Future的设置返回值finish方法实现:</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">finish</span><span class="params">(T result)</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(LOCK) {</span><br><span class="line"> <span class="keyword">if</span>(isDone) <span class="keyword">return</span>;</span><br><span class="line"> <span class="keyword">this</span>.result = result;</span><br><span class="line"> <span class="keyword">this</span>.isDone = <span class="keyword">true</span>;</span><br><span class="line"> LOCK.notifyAll();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>Future获取返回值的get方法实现:</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> T <span class="title">get</span><span class="params">()</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(LOCK) {</span><br><span class="line"> <span class="keyword">while</span>(!isDone) {</span><br><span class="line"> LOCK.wait();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h4><ul>
<li>get方法可以加一个超时功能,wait到一定时间没有返回则抛出异常。</li>
</ul>
<h3 id="3-上下文设计模式"><a href="#3-上下文设计模式" class="headerlink" title="3 上下文设计模式"></a>3 上下文设计模式</h3><blockquote>
<p>使用ThreadLocal可以实现线程间数据隔离。</p>
</blockquote>
<blockquote>
<p>早期版本ThreadLocal内存泄漏问题:因为早起ThreadLocal内部是与当前线程实例相关联的(用HashMap),所以当线程结束后,Thread实例和存储的数据依然存在,随着系统运行时间的增长可能会越来越多,最终导致内存泄漏。</p>
</blockquote>
<blockquote>
<p>ThreadLocal内部结构:threadLocal.get时,通过当前线程实例thread获取到thread.threadLocalMap,threadLocalMap的key就是threadLocal实例,threadLocalMap.getEntry(threadLocal)就得到了封装的值entry,Entry是WeakReferencede的子类,Entry中的key就是threadLocal实例,并作为弱引用存在。</p>
</blockquote>
<blockquote>
<p>当threadLocal设置为null之后,GC会回收entry中的key,但是thread.threadLocalMap中依然有指向entry的引用,所以value不能被回收。如果thread被回收,那么后续的threadLocalMap以及entry中的value也就被回收了。</p>
</blockquote>
<blockquote>
<p>内存泄漏和内存溢出:泄漏是指应用程序中已经不再持有某个对象的引用,但是GC无法回收,因为其到引用跟root的链路是可达的,所以无法回收。溢出是指内存不足,可见内存泄漏是导致内存溢出的原因之一。</p>
</blockquote>
<blockquote>
<p>ThreadLocal如何避免内存泄漏:</p>
</blockquote>
<ol>
<li>使用WeakReference,JVM中触发任意GC都会导致WeakReference被回收;</li>
<li>get或set数据时检查并清除已被GC回收的数据。</li>
</ol>
<h3 id="4-Latch设计模式"><a href="#4-Latch设计模式" class="headerlink" title="4 Latch设计模式"></a>4 Latch设计模式</h3><blockquote>
<p>一句话介绍:只有所有条件都满足时,门阀才打开。</p>
</blockquote>
<blockquote>
<p>实现:属性limit记录需要满足的条件个数,当limit降为0时打开门阀。</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">await</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(<span class="keyword">this</span>) {</span><br><span class="line"> <span class="keyword">while</span>(limit > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">this</span>.wait();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">countDown</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(<span class="keyword">this</span>) {</span><br><span class="line"> <span class="keyword">if</span>(limit <= <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> IllegalStateException(<span class="string">""</span>);</span><br><span class="line"> }</span><br><span class="line"> limit--;</span><br><span class="line"> <span class="keyword">this</span>.notifyAll();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>线程任务调用await方法等待limit降为0,当其他线程执行完之后调用countDown方法对limit减1,然后通知其他等待线程。</p>
<blockquote>
<p>带超时功能的实现:</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">await</span><span class="params">(TimeUnit unit, <span class="keyword">long</span> time)</span> <span class="keyword">throws</span> TimeoutException </span>{</span><br><span class="line"> <span class="keyword">if</span>(time <= <span class="number">0</span>) <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException(<span class="string">"参数不合法"</span>)</span><br><span class="line"> <span class="keyword">long</span> remainingNanos = unit.toNanos(time);</span><br><span class="line"> <span class="keyword">final</span> <span class="keyword">long</span> endNanos = System.nanoTime() + remainingNanos;</span><br><span class="line"> <span class="keyword">synchronized</span>(<span class="keyword">this</span>) {</span><br><span class="line"> <span class="keyword">while</span>(limit > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">if</span>(TimeUnit.NANOSECONDS.toMillis(remainingNanos) <= <span class="number">0</span>) <span class="keyword">throw</span> <span class="keyword">new</span> TimeoutException(<span class="string">"time out"</span>);</span><br><span class="line"> <span class="keyword">this</span>.wait(TimeUnit.NANOSECONDS.toMillis(remainingNanos));</span><br><span class="line"> remainingNanos = endNanos - System.nanoTime();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>记录超时时间点,每次检查limit时都重新计算剩余时间,如果剩余时间小于等于0,则超时。</p>
<h3 id="4-几种引用类型介绍"><a href="#4-几种引用类型介绍" class="headerlink" title="4 几种引用类型介绍"></a>4 几种引用类型介绍</h3><blockquote>
<p>SoftReference: 当内存不足时,GC会回收Soft Reference;</p>
</blockquote>
<blockquote>
<p>WeakReference: 无论是young GC还是full GC,都会被回收;</p>
</blockquote>
<blockquote>
<p>PhantomReference: 必须和ReferenceQueue配合使用,它的get方法始终返回null,当Phantom Reference被回收后,会将其插入到ReferenceQueue中,我们可以通过检查queue直到进行了一次GC。</p>
</blockquote>
<h3 id="5-Active-Object模式"><a href="#5-Active-Object模式" class="headerlink" title="5 Active Object模式"></a>5 Active Object模式</h3><blockquote>
<p>简介: 接收异步消息的主动对象,主动对象就是指其拥有自己的独立线程,它可以接收异步消息并返回处理的结果。</p>
</blockquote>
<h4 id="通用Active-Object的设计"><a href="#通用Active-Object的设计" class="headerlink" title="通用Active Object的设计"></a>通用Active Object的设计</h4><blockquote>
<p>核心思路:服务类中以@ActiveMethod标注需要Active的方法,创建服务类的代理对象,当调用方法时,代理方法中检查是否标注了@ActiveMethod注解,如果没有则直接反射调用并返回结果,如果有,则将方法、参数以及服务对象封装成message放入队列中,并返回Future,异步线程负责消费队列中的message,调用方可通过Future的get方法拿到最终的返回值。</p>
</blockquote>
<h4 id="核心代码:"><a href="#核心代码:" class="headerlink" title="核心代码:"></a>核心代码:</h4><blockquote>
<p>ActiveMessage的execute方法:</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Object result = method.invoke(instance, args);</span><br><span class="line"> <span class="keyword">if</span>(future != <span class="keyword">null</span>) {</span><br><span class="line"> Object realResult = ((Future<?>)result).get();</span><br><span class="line"> future.finish(realResult);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span>(Exception e) {</span><br><span class="line"> <span class="keyword">if</span>(future != <span class="keyword">null</span>) {</span><br><span class="line"> future.finish(<span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们将代理的对象、方法以及参数封装成ActiveMessage,执行线程取到message时调用execute方法执行,结果通过future.finish设置到future中,外部可通过get方法拿到返回值。</p>
<blockquote>
<p>ActiveServiceFactory生成代理对象:</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">static</span> <T> <span class="function">T <span class="title">active</span><span class="params">(T instance)</span> </span>{</span><br><span class="line"> Object proxy = Proxy.newInstance(instance.getClass().getClassLoader(), instance.getClass().getInterfaces(), <span class="keyword">new</span> ActiveInvocationHandler<>(instance));</span><br><span class="line"> <span class="keyword">return</span> (T) proxy;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>使用ActiveInvocationHandler代理instance实例,使用JDK动态代理技术,所以代理类必须有继承的接口。</p>
<blockquote>
<p>ActiveInvocationHandler的invoke方法:</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> <span class="keyword">if</span>(method.isAnnotationPresent(ActiveMethod<span class="class">.<span class="keyword">class</span>)) </span>{</span><br><span class="line"> <span class="keyword">this</span>.checkMethod(method);</span><br><span class="line"> ActiveMessage.Builder builder = <span class="keyword">new</span> ActiveMessage.Builder();</span><br><span class="line"> builder.useMethod(method).withArgs(args).forService(instance);</span><br><span class="line"> Object result = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span>(<span class="keyword">this</span>.isReturnFutureType(method)) {</span><br><span class="line"> result = <span class="keyword">new</span> ActiveFuture<>();</span><br><span class="line"> builder.returnFuture((ActiveFuture)result);</span><br><span class="line"> }</span><br><span class="line"> queue.offer(builder.build());</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> method.invoke(instance, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果方法注解了@ActiveMethod,如果方法有返回值则必须返回Future,将method、instance、args以及future封装成ActiveMessage放到queue中,然后将future返回。如果没有标注@ActiveMethod,则直接返回调用值。</p>
<blockquote>
<p>守护线程进行消息消费</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">for</span>(;;) {</span><br><span class="line"> ActiveMessage activeMessage = <span class="keyword">this</span>.queue.take();</span><br><span class="line"> activeMessage.execute();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>从队列中取出message进行执行。</p>
<h3 id="6-Event-Bus设计模式"><a href="#6-Event-Bus设计模式" class="headerlink" title="6 Event Bus设计模式"></a>6 Event Bus设计模式</h3><blockquote>
<p>消息中间件主要用于解决进程之间消息异步处理的解决方案。</p>
</blockquote>
<h4 id="核心接口设计"><a href="#核心接口设计" class="headerlink" title="核心接口设计"></a>核心接口设计</h4><blockquote>
<p>Bus接口,对外提供post方法用来发送event到topic上,register方法用来注册订阅者subscriber;</p>
</blockquote>
<blockquote>
<p>Registry注册表,用于记录对应的订阅者subscriber,以及受理消息的回调方法,回调方法我们用注解@Subscriber标识;</p>
</blockquote>
<blockquote>
<p>Dispatcher分发器,用来将event广播给注册表中监听了topic的订阅者。</p>
</blockquote>
<h4 id="设计思路"><a href="#设计思路" class="headerlink" title="设计思路"></a>设计思路</h4><p>将订阅者注册到Registry中,注册信息中有订阅者关注的topic以及对应的处理方法。Dispatcher通过topic从注册表Registry中查找对应topic的订阅者,然后调用订阅者的处理方法。Bus对外提供注册、订阅等方法,用户只需要通过Bus操作即可。</p>
<h3 id="7-Event-Driven架构"><a href="#7-Event-Driven架构" class="headerlink" title="7 Event-Driven架构"></a>7 Event-Driven架构</h3><blockquote>
<p>EDA,事件驱动架构是一种实现组件之间松耦合、易扩展的架构方式。</p>
</blockquote>
<h4 id="主要包括:"><a href="#主要包括:" class="headerlink" title="主要包括:"></a>主要包括:</h4><blockquote>
<p>Events: 事件处理的数据;</p>
</blockquote>
<blockquote>
<p>Event Handlers: 处理Events的方法;</p>
</blockquote>
<blockquote>
<p>Event Loop: 维护Events和Event Handlers之间的交互流程,通过对应的类型找到对应的Handler。</p>
</blockquote>
<h4 id="同步Event-Driven设计"><a href="#同步Event-Driven设计" class="headerlink" title="同步Event-Driven设计"></a>同步Event-Driven设计</h4><blockquote>
<p>Message接口,作为Event更高一层的抽象,提供getType方法,返回Event type;</p>
</blockquote>
<blockquote>
<p>Channel接口,提供dispatch(Message message)方法,用于处理一种类型的消息;</p>
</blockquote>
<blockquote>
<p>DynamicRouter接口,用于注册消息类型和Channel,以及dispatch方法来分发Message到对应的Channel,它的实现类要提供类型和Channel映射,具体实现如下:</p>
</blockquote>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">EventDispatcher</span> <span class="keyword">implements</span> <span class="title">DynamicRouter</span><<span class="title">Message</span>> </span>{</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map<Class<? extends Message>, Channel> routerTable;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">EventDispatcher</span><span class="params">()</span> </span>{</span><br><span class="line"> routerTable = <span class="keyword">new</span> HashMap<>();</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">dispatch</span><span class="params">(Message message)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span>(routerTable.containsKey(message.getType())) {</span><br><span class="line"> routerTable.get(message.getType()).dispatch(message);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> MessageMatcherException(<span class="string">"Can't match the channel for ["</span> + message.getType() + <span class="string">"] type"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerChannel</span><span class="params">(Class<? extends Message> messageType, Channel<? extends Message> channel)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.routerTable.put(messageType, channel);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<blockquote>
<p>测试:定义一个InputEvent、ResultEvent作为Event的实现,分别定义对应的Channel,并将Channel注册到dispatcher中,然后使用dispatcher.dispatch分发不同的Event,可以看到由对应的Channel处理。</p>
</blockquote>
<h4 id="异步Event-Driven设计"><a href="#异步Event-Driven设计" class="headerlink" title="异步Event-Driven设计"></a>异步Event-Driven设计</h4><blockquote>
<p>定义一个异步版的Channel - AsyncChannel,与同步版的区别在于执行的时候使用线程执行;</p>
</blockquote>
<blockquote>
<p>定义一个异步版的Dispatcher - AsyncEventDispatcher,其中map改用线程安全的ConcurrentHashMap。</p>
</blockquote>
]]></content>
<tags>
<tag>多线程</tag>
<tag>设计模式</tag>
<tag>锁</tag>
</tags>
</entry>
</search>