Java中的锁机制学习笔记

今天回顾了下java中的锁机制,和object类中中的wait(),notify(),notifyAll();每次敲代码的时候老看见这几个方法,总让人将它们和Android中的notifyDataSetChanged()的方法想到一块去,其实这些东西在Java基础的时候就已经学过了,可能是学的不扎实所有总有一种似成相识的感觉,来来回回,有一种身体被掏空了的感觉!

废话不多说,看看这几个方法的使用场景,在我们的代码难免遇到使用多个线程操作一个对象的情景,这样,我们如果不对这个对象进行一定的保护(加锁),那么这个对象对于我们来说就是线程不安全的,下面就用一个简单的卖票的小例子来演示一下:

package cn.dave.test;

public class TicketDemo {

    private int ticketNum=150;

    public static void main(String[] args) {
        TicketDemo ticketDemo=new TicketDemo();
        sellThread sThread0=ticketDemo.new sellThread();
        sellThread sThread1=ticketDemo.new sellThread();
        sellThread sThread2=ticketDemo.new sellThread();
        sellThread sThread3=ticketDemo.new sellThread();
        sThread0.start();
        sThread1.start();
        sThread2.start();
        sThread3.start();
    }

    class sellThread extends Thread{
        public void run() {
            while(ticketNum>0){
                ticketNum--;
                System.out.println("票已卖出,现在还剩:"+ticketNum);
            }
        }
    }

}

代码非常的简单,我们定义了150张票,并开启了四个线程去卖,直到票的数量为0,看起来好像没什么问题,我们运行开一下Log日志的输出:

票已卖出,现在还剩:148
票已卖出,现在还剩:146
票已卖出,现在还剩:147
票已卖出,现在还剩:148
票已卖出,现在还剩:143
票已卖出,现在还剩:144
....

NO,程序刚开始执行就出问题了,148的时候卖了两次,这就出现了线程安全的问题了,那我们可以怎么解决这个问题了,这是我们就用到了Java中的synchronized机制了,我们先来看看加了锁后的效果:

        synchronized (lock) {
                while(ticketNum>0){
                    ticketNum--;
                    System.out.println("票已卖出,现在还剩:"+ticketNum);
                }
            }

代码非常的简单,我们加了一个同步代码块,这样让我们每次只能同时只有一个线程可以访问我们公共的对象对其进行操作,解决了线程的安全问题,那么这个lock是什么了?

private Object lock=new Object();//对象锁

它是一个我们定义的类成员变量,作为我们的对象锁,我们再看看这样的执行结果:

票已卖出,现在还剩:149
票已卖出,现在还剩:148
票已卖出,现在还剩:147
票已卖出,现在还剩:146
票已卖出,现在还剩:145
票已卖出,现在还剩:144
...

这把就非常的漂亮了!那么这个锁我们以后都必须这样写吗?当然不是了,Java中为我们提供了如下几种方法:

  1. 在需要同步的方法的方法签名中加入synchronized关键字。

  2. 使用synchronized块对需要进行同步的代码段进行同步。

  3. 使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象。

注意:在使用同步方法锁的时候我们需要知道,当前方法持有的锁是当前的对象,如果这个方法是我们类的成员方法,那么这个方法的锁对象就是一内存this,如果在多线程中使用方法锁的话,就像上面卖票例子那样,我们持有的锁对象就不是类对象了,而是我们的多线程对象,这样就造成了锁的不唯一,没法到达线程安全的目的了。

好了,上面就是我对锁的见解,有不对的地方欢迎指正!

说了这么半天,好像没开篇说的wait()等这几个方法没有半毛钱的关系啊,不急,下面我就结合我自己的理解,来谈谈我对那几个方法的理解。

我将Object类中的这几个方法进行了一个比喻,辅助记忆,当一个对象调用这几个方法的时候,就像一个动物园的饲养员要进行投放食物了,这是动物都处于wait()的状态,当饲养员给一群没有严格等级分明的动物投放食物的时候,就像调用了notifyAll()方法,一下正在wait()的动物就会一下全上来抢食,就看谁手快了。而如果是面对一群有等级分明的动物时,就像调用notify()方法,此时就只有一个老大先出来吃,等他完事了,在调用notify()通知下一个wait()的来吃,这样依次执行,当然这只是举个例子,Java中还是看脸来抢的···

说了这么些,我们就来看个实际的例子:

package cn.dave.test;

public class TestNotify {

    private String[] flag = new String[] { "stop" };
    private Object lock=new Object(); 

    /**
     * @param args
     */
    public static void main(String[] args) {
        TestNotify test=new TestNotify();
        WaitThread waitThread=test.new WaitThread("wait");
        WaitedThread waitedThread1=test.new WaitedThread("waited1");
        WaitedThread waitedThread2=test.new WaitedThread("waited2");
        WaitedThread waitedThread3=test.new WaitedThread("waited3");
        waitThread.start();
        waitedThread1.start();
        waitedThread2.start();
        waitedThread3.start();
    }

    class WaitThread extends Thread {

        public WaitThread(String flag) {
            super(flag);
        }

        public void run() {
            try {

                Thread.sleep(2000);
                synchronized (lock) {
                    flag[0] = "begin";
                    lock.notify();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class WaitedThread extends Thread {

        public WaitedThread(String flag) {
            super(flag);
        }

        public void run() {
            synchronized (lock) {
                try {
                    while (flag[0].equals("stop")) {
                        System.out.println(getName() + "begin wait!");
                        long wait = System.currentTimeMillis();
                        lock.wait();
                        wait = System.currentTimeMillis() - wait;
                        System.out.println("wait time :" + wait);
                        Thread.sleep(2000);
                        lock.notify();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(getName() + " end waiting!");
        }
    }

}

在例子中我们创建了一个用于唤醒其他线程的线程waitThread,和三个等待的线程waitedThread1,waitedThread2,waitedThread3,在代码中大家也发现了我们增加了两处同步代码块,那么为什么了?因为不加的话会抛异常啊,哈哈!!!

waited2begin wait!Exception in thread "waited2" Exception in thread "waited3" Exception in thread "waited1" 
waited1begin wait!
waited3begin wait!
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Unknown Source)
    at cn.dave.test.TestNotify$WaitedThread.run(TestNotify.java:52)
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Unknown Source)
    at cn.dave.test.TestNotify$WaitedThread.run(TestNotify.java:52)
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Unknown Source)
    at cn.dave.test.TestNotify$WaitedThread.run(TestNotify.java:52)
Exception in thread "wait" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at cn.dave.test.TestNotify$WaitThread.run(TestNotify.java:34)

出现这个的原因是因为:

  • 任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。
  • 无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)
  • 如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常

那么我们运行的结果如下:

waited2begin wait!
waited3begin wait!
waited1begin wait!
wait time :2000
waited2 end waiting!
wait time :4000
waited3 end waiting!
wait time :6000
waited1 end waiting!

貌似看起来没有问题,但是我们看代码,发现有一句代码没有处在同步代码块中,这就会导致这句输出语句的顺序出现问题。

我们将代码稍微做点修改:

    flag[0] = "begin";
    lock.notifyAll();

    //去掉wait线程中的
    Thread.sleep(2000);
    lock.notify();

运行结果:

waited1begin wait!
waited2begin wait!
waited3begin wait!
wait time :2000
waited3 end waiting!
wait time :2000
waited2 end waiting!
wait time :2000
waited1 end waiting!

可以看到这次就是大家一起上了。

好了,今天就记到这吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值