java多线程(二)锁对象

转载请注明出处:http://blog.csdn.net/xingjiarong/article/details/47679007
在上一篇博客中,我们讨论了Race Condition现象以及它产生的原因,现在我们知道它是不好的一种现象了,那么我们有什么方法避免它呢。最直接有效的方式就是放弃多线程,直接改为使用单线程但操作数据,但是这是不优雅的,因为我们知道有时候,多线程有它自己的优势。在这里我们讨论两种其他的方法——锁对象和条件对象。

锁对象

java SE5.0之后为实现多线程的互斥引入了ReentrantLock类。ReentrantLock类一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

ReentrantLock类有两种构造方法:

构造方法

一、不带公平参数的构造方法

<code class="hljs cs has-numbering"><span class="hljs-keyword">private</span> ReentrantLock <span class="hljs-keyword">lock</span> = <span class="hljs-keyword">new</span> ReentrantLock();</code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>

默认的是非公平锁,这种锁不会根据线程等待时间的长短来优先调度线程。

这样就构造了一个锁对象lock。

二、带公平参数的锁对象

<code class="hljs cs has-numbering"><span class="hljs-keyword">private</span> ReentrantLock <span class="hljs-keyword">lock</span> = <span class="hljs-keyword">new</span> ReentrantLock(<span class="hljs-keyword">true</span>);</code><ul style="display: block;" class="pre-numbering"><li>1</li></ul>

此类的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。

公平锁和非公平锁的区别:

与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。因此,使用公平锁的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁时。

使用方法

<code class="hljs cs has-numbering">class X {
    <span class="hljs-keyword">private</span> final ReentrantLock <span class="hljs-keyword">lock</span> = <span class="hljs-keyword">new</span> ReentrantLock();

    <span class="hljs-comment">// 其他变量的定义</span>

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">m</span>() { 
     <span class="hljs-keyword">lock</span>.<span class="hljs-keyword">lock</span>();  <span class="hljs-comment">// 当试图获得锁时,如果锁已经被别的线程占有,那么该线程会一直被阻塞,直到获得锁</span>
     <span class="hljs-keyword">try</span> {
       <span class="hljs-comment">// 处理数据</span>
     } <span class="hljs-keyword">finally</span> {
       <span class="hljs-keyword">lock</span>.unlock(); <span class="hljs-comment">//释放锁</span>
     }
   }
}</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li></ul>

首先为大家介绍一下,ReentrantLock类的两个最常用的方法:

lock()
获得锁对象,如果该锁对象没有被其他线程占有,那么可以立刻获得锁,并执行接下来的处理;如果锁对象已经被其他对象占有,那么该线程就会被阻塞在请求锁的对象的操作上,直到其他的线程释放锁,该线程得到锁,才能继续的向下执行。

unlock()
释放锁,已经获得锁对象的线程在操作完数据后要释放锁,以便其他的线程重新获得锁来执行自己的操作,否则所有的试图获得锁的线程都不能继续向下执行。

使用方法简单明了,就是在执行各个线程都要操作相同数据的代码之前请求锁,在finally语句中释放锁,为什么要在finally中释放锁呢,这是因为如果try语句块中有语句发生异常,则会直接跳过try中所有的剩余代码包括unlock(),所以锁对象就不能得到释放,其他的线程也不能继续向下执行,导致程序不能继续执行,我们在finally中释放锁,这样就能保证一定可以将锁释放掉,不管获得锁的线程是不是正确的执行结束。

现在来使用这个方法修改一下我们上一篇博客中的代码:

<code class="hljs java has-numbering"><span class="hljs-keyword">import</span> java.util.concurrent.locks.ReentrantLock;

class MyThread implements Runnable {
    <span class="hljs-javadoc">/**
     * 计算类型,1表示减法,其他的表示加法
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> type;
    <span class="hljs-javadoc">/**
     * 锁对象
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> ReentrantLock lock = <span class="hljs-keyword">new</span> ReentrantLock();

    <span class="hljs-keyword">public</span> <span class="hljs-title">MyThread</span>(<span class="hljs-keyword">int</span> type) {
        <span class="hljs-keyword">this</span>.type = type;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {

        <span class="hljs-keyword">if</span> (type == <span class="hljs-number">1</span>)
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10000</span>; i++) {

                lock.lock();

                Test.num--;

                lock.unlock();

            }
        <span class="hljs-keyword">else</span>
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10000</span>; i++) {

                lock.lock();

                Test.num++;

                lock.unlock();
            }

    }
}

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Test</span> {</span>

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> num = <span class="hljs-number">1000000</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) {

        Thread a = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> MyThread(<span class="hljs-number">1</span>));
        Thread b = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> MyThread(<span class="hljs-number">2</span>));

        a.start();
        b.start();

        <span class="hljs-comment">/*
         * 主线程等待子线程完成,然后再打印数值
         */</span>
        <span class="hljs-keyword">try</span> {
            a.join();
            b.join();
        } <span class="hljs-keyword">catch</span> (Exception e) {
            e.printStackTrace();
        }

        System.out.println(num);
    }

}
</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li></ul>

再多运行几次,是不是结果都是正确的呢。

现在,我们来解释一下,什么是可重入的锁。ReentrantLock类中有一个计数器,用来表示一个线程获取锁的数量,初始值为0,当一个线程获得锁时,该值被置为1,可重入的意思就是,已经获得锁的线程还可以继续调用同一个锁所保护的方法,也就是再一次获得锁,当再一次获得锁时,ReentrantLock中的计数器就加1,每释放一次锁,计数器就减1,当计数器减为0的时候,这个线程才是真正的释放了这个锁。

我们接着讨论另外一个非常重要的问题。ReentrantLock类是依赖于创建它的类的对象。什么意思呢,就是说如果两个线程同时访问同一个ReentrantLock对象的lock()方法保护的方法时,OK,这是没有问题的,锁对象会成功的保护数据操作不会出错。但是如果两个线程同时访问ReentrantLock类的不同对象的被lock()保护的方法,那么这两个线程是不会相互影响的,也就是说lock()方法这时不能保证数据的正确性。

我们来看一下上边那个代码的这一部分:

<code class="hljs lasso has-numbering"><span class="hljs-keyword">Thread</span> a <span class="hljs-subst">=</span> <span class="hljs-literal">new</span> <span class="hljs-keyword">Thread</span>(<span class="hljs-literal">new</span> MyThread(<span class="hljs-number">1</span>));
<span class="hljs-keyword">Thread</span> b <span class="hljs-subst">=</span> <span class="hljs-literal">new</span> <span class="hljs-keyword">Thread</span>(<span class="hljs-literal">new</span> MyThread(<span class="hljs-number">2</span>));
</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li></ul>

这里建立了两个对象a和b,所以他们每个类都有自己的ReentrantLock对象,这就是我们上边所说的ReentrantLock类的不同对象,这样如果两个线程分别操作a和b的数据,lock方法是不会有效的。

不信我们试试看这个代码:

<code class="hljs java has-numbering"><span class="hljs-keyword">import</span> java.util.concurrent.locks.ReentrantLock;

class MyThread implements Runnable {
    <span class="hljs-javadoc">/**
     * 计算类型,1表示减法,其他的表示加法
     */</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> type;
    <span class="hljs-javadoc">/**
     * 锁对象
     */</span>
    <span class="hljs-keyword">private</span> ReentrantLock lock = <span class="hljs-keyword">new</span> ReentrantLock();

    <span class="hljs-keyword">public</span> <span class="hljs-title">MyThread</span>(<span class="hljs-keyword">int</span> type) {
        <span class="hljs-keyword">this</span>.type = type;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">run</span>() {

        <span class="hljs-keyword">if</span> (type == <span class="hljs-number">1</span>)
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10000</span>; i++) {

                lock.lock();

                Test.num--;

                lock.unlock();

            }
        <span class="hljs-keyword">else</span>
            <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">10000</span>; i++) {

                lock.lock();

                Test.num++;

                lock.unlock();
            }

    }
}

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Test</span> {</span>

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> num = <span class="hljs-number">1000000</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span>(String[] args) {

        Thread a = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> MyThread(<span class="hljs-number">1</span>));
        Thread b = <span class="hljs-keyword">new</span> Thread(<span class="hljs-keyword">new</span> MyThread(<span class="hljs-number">2</span>));

        a.start();
        b.start();

        <span class="hljs-comment">/*
         * 主线程等待子线程完成,然后再打印数值
         */</span>
        <span class="hljs-keyword">try</span> {
            a.join();
            b.join();
        } <span class="hljs-keyword">catch</span> (Exception e) {
            e.printStackTrace();
        }

        System.out.println(num);
    }

}
</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li></ul>

注意这里的代码和上边的代码并不一样,唯一的区别就在于声明ReentrantLock对象时前边是否加了static,这里是没有static修饰,再运行几次,是不是结果是不正确的呢。

为什么会这样呢?因为如果被static修饰,那么两个线程就是共用的同一个lock对象,如果不被static修饰,那么每个线程就是使用的它自己的lock对象,所以不会static修饰就会出现错误
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值