天天看點

Oracle官方并發教程之不可變對象

一個對象如果在建立後不能被修改,那麼就稱為不可變對象。在并發程式設計中,一種被普遍認可的原則就是:盡可能的使用不可變對象來建立簡單、可靠的代碼。

在并發程式設計中,不可變對象特别有用。由于建立後不能被修改,是以不會出現由于線程幹擾産生的錯誤或是記憶體一緻性錯誤。

但是程式員們通常并不熱衷于使用不可變對象,因為他們擔心每次建立新對象的開銷。實際上這種開銷常常被過分高估,而且使用不可變對象所帶來的一些效率提升也抵消了這種開銷。例如:使用不可變對象降低了垃圾回收所産生的額外開銷,也減少了用來確定使用可變對象不出現并發錯誤的一些額外代碼。

接下來看一個可變對象的類,然後轉化為一個不可變對象的類。通過這個例子說明轉化的原則以及使用不可變對象的好處。

Oracle官方并發教程之不可變對象

<code>01</code>

<code>public</code> <code>class</code> <code>synchronizedrgb {</code>

<code>02</code>

<code>03</code>

<code>    </code><code>// values must be between 0 and 255.</code>

<code>04</code>

<code>    </code><code>private</code> <code>int</code> <code>red;</code>

<code>05</code>

<code>    </code><code>private</code> <code>int</code> <code>green;</code>

<code>06</code>

<code>    </code><code>private</code> <code>int</code> <code>blue;</code>

<code>07</code>

<code>    </code><code>private</code> <code>string name;</code>

<code>08</code>

<code>09</code>

<code>    </code><code>private</code> <code>void</code> <code>check(</code><code>int</code> <code>red,</code>

<code>10</code>

<code>                       </code><code>int</code> <code>green,</code>

<code>11</code>

<code>                       </code><code>int</code> <code>blue) {</code>

<code>12</code>

<code>        </code><code>if</code> <code>(red &lt; </code><code>0</code> <code>|| red &gt; </code><code>255</code>

<code>13</code>

<code>            </code><code>|| green &lt; </code><code>0</code> <code>|| green &gt; </code><code>255</code>

<code>14</code>

<code>            </code><code>|| blue &lt; </code><code>0</code> <code>|| blue &gt; </code><code>255</code><code>) {</code>

<code>15</code>

<code>            </code><code>throw</code> <code>new</code> <code>illegalargumentexception();</code>

<code>16</code>

<code>        </code><code>}</code>

<code>17</code>

<code>    </code><code>}</code>

<code>18</code>

<code>19</code>

<code>    </code><code>public</code> <code>synchronizedrgb(</code><code>int</code> <code>red,</code>

<code>20</code>

<code>                           </code><code>int</code> <code>green,</code>

<code>21</code>

<code>                           </code><code>int</code> <code>blue,</code>

<code>22</code>

<code>                           </code><code>string name) {</code>

<code>23</code>

<code>        </code><code>check(red, green, blue);</code>

<code>24</code>

<code>        </code><code>this</code><code>.red = red;</code>

<code>25</code>

<code>        </code><code>this</code><code>.green = green;</code>

<code>26</code>

<code>        </code><code>this</code><code>.blue = blue;</code>

<code>27</code>

<code>        </code><code>this</code><code>.name = name;</code>

<code>28</code>

<code>29</code>

<code>30</code>

<code>    </code><code>public</code> <code>void</code> <code>set(</code><code>int</code> <code>red,</code>

<code>31</code>

<code>                    </code><code>int</code> <code>green,</code>

<code>32</code>

<code>                    </code><code>int</code> <code>blue,</code>

<code>33</code>

<code>                    </code><code>string name) {</code>

<code>34</code>

<code>35</code>

<code>        </code><code>synchronized</code> <code>(</code><code>this</code><code>) {</code>

<code>36</code>

<code>            </code><code>this</code><code>.red = red;</code>

<code>37</code>

<code>            </code><code>this</code><code>.green = green;</code>

<code>38</code>

<code>            </code><code>this</code><code>.blue = blue;</code>

<code>39</code>

<code>            </code><code>this</code><code>.name = name;</code>

<code>40</code>

<code>41</code>

<code>42</code>

<code>43</code>

<code>    </code><code>public</code> <code>synchronized</code> <code>int</code> <code>getrgb() {</code>

<code>44</code>

<code>        </code><code>return</code> <code>((red &lt;&lt; </code><code>16</code><code>) | (green &lt;&lt; </code><code>8</code><code>) | blue);</code>

<code>45</code>

<code>46</code>

<code>47</code>

<code>    </code><code>public</code> <code>synchronized</code> <code>string getname() {</code>

<code>48</code>

<code>        </code><code>return</code> <code>name;</code>

<code>49</code>

<code>50</code>

<code>51</code>

<code>    </code><code>public</code> <code>synchronized</code> <code>void</code> <code>invert() {</code>

<code>52</code>

<code>        </code><code>red = </code><code>255</code> <code>- red;</code>

<code>53</code>

<code>        </code><code>green = </code><code>255</code> <code>- green;</code>

<code>54</code>

<code>        </code><code>blue = </code><code>255</code> <code>- blue;</code>

<code>55</code>

<code>        </code><code>name = </code><code>"inverse of "</code> <code>+ name;</code>

<code>56</code>

<code>57</code>

<code>}</code>

使用synchronizedrgb時需要小心,避免其處于不一緻的狀态。例如一個線程執行了以下代碼:

<code>1</code>

<code>synchronizedrgb color =</code>

<code>2</code>

<code>    </code><code>new</code> <code>synchronizedrgb(</code><code>0</code><code>, </code><code>0</code><code>, </code><code>0</code><code>, </code><code>"pitch black"</code><code>);</code>

<code>3</code>

<code>...</code>

<code>4</code>

<code>int</code> <code>mycolorint = color.getrgb();      </code><code>//statement 1</code>

<code>5</code>

<code>string mycolorname = color.getname(); </code><code>//statement 2</code>

如果有另外一個線程在statement 1之後、statement 2之前調用了color.set方法,那麼mycolorint的值和mycolorname的值就會不比對。為了避免出現這樣的結果,必須要像下面這樣把這兩條語句綁定到一塊執行:

<code>synchronized</code> <code>(color) {</code>

<code>    </code><code>int</code> <code>mycolorint = color.getrgb();</code>

<code>    </code><code>string mycolorname = color.getname();</code>

這種不一緻的問題隻可能發生在可變對象上。

以下的一些規則是建立不可變對象的簡單政策。并非所有不可變類都完全遵守這些規則,不過這不是編寫這些類的程式員們粗心大意造成的,很可能的是他們有充分的理由確定這些對象在建立後不會被修改。但這需要非常複雜細緻的分析,并不适用于初學者。

Oracle官方并發教程之不可變對象

不要提供setter方法。(包括修改字段的方法和修改字段引用對象的方法)

将類的所有字段定義為final、private的。

不允許子類重寫方法。簡單的辦法是将類聲明為final,更好的方法是将構造函數聲明為私有的,通過工廠方法建立對象。

如果類的字段是對可變對象的引用,不允許修改被引用對象。

不提供修改可變對象的方法。

不共享可變對象的引用。當一個引用被當做參數傳遞給構造函數,而這個引用指向的是一個外部的可變對象時,一定不要儲存這個引用。如果必須要儲存,那麼建立可變對象的拷貝,然後儲存拷貝對象的引用。同樣如果需要傳回内部的可變對象時,不要傳回可變對象本身,而是傳回其拷貝。

将這一政策應用到synchronizedrgb有以下幾步:

synchronizedrgb類有兩個setter方法。第一個set方法隻是簡單的為字段設值(譯者注:删掉即可),第二個invert方法修改為建立一個新對象,而不是在原有對象上修改。

所有的字段都已經是私有的,加上final即可。

将類聲明為final的

隻有一個字段是對象引用,并且被引用的對象也是不可變對象。

<a href="http://ifeve.com/immutable-objects/#viewsource">檢視源代碼</a>

<code>final</code> <code>public</code> <code>class</code> <code>immutablergb {</code>

<code>    </code><code>final</code> <code>private</code> <code>int</code> <code>red;</code>

<code>    </code><code>final</code> <code>private</code> <code>int</code> <code>green;</code>

<code>    </code><code>final</code> <code>private</code> <code>int</code> <code>blue;</code>

<code>    </code><code>final</code> <code>private</code> <code>string name;</code>

<code>    </code><code>public</code> <code>immutablergb(</code><code>int</code> <code>red,</code>

<code>                        </code><code>int</code> <code>green,</code>

<code>                        </code><code>int</code> <code>blue,</code>

<code>                        </code><code>string name) {</code>

<code>    </code><code>public</code> <code>int</code> <code>getrgb() {</code>

<code>    </code><code>public</code> <code>string getname() {</code>

<code>    </code><code>public</code> <code>immutablergb invert() {</code>

<code>        </code><code>return</code> <code>new</code> <code>immutablergb(</code><code>255</code> <code>- red,</code>

<code>                       </code><code>255</code> <code>- green,</code>

<code>                       </code><code>255</code> <code>- blue,</code>

<code>                       </code><code>"inverse of "</code> <code>+ name);</code>