java同步机制

最近看了一下java的同步机制,记录一下,防止遗忘。

为什么要有同步机制?

大家都知道多线程操作,试想这样一个场景:售票
上海-北京只剩下一张票,有三个人在同时抢票,抢到之后系统进行一系列操作(比如算钱,占位等),然后对票数总量减一,告诉其他人没票了。
看上去没毛病,但一旦用上多线程,毛病就来了

三个线程在访问票数总量时,由于系统没计算完或者在占位,导致没能及时将总量减一,所以三个人都抢到票了。

最终的结果就是,有两个用户提示抢到票了,但是占位失败了,这是多么糟糕的用户体验。

怎么解决?粗暴一点就是同一时间只能有一个线程读写票数总量就可以了,这里用到了java的synchronized字段。

Synchronized
  • synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果 再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
  • 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
  • 每个对象只有一个锁(lock)与之相关联。
  • 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
Synchronized用法

大概分为三种,首先介绍一下第一种

对对象加锁:
    /**
     * 锁定调用方法的对象
     * 当一个线程调用该方法时,其他线程无法访问持有该TestMethod对象锁的任何方法
     * 需要注意的是,该锁只是锁定当前对象,如果新建了一个其他对象,则线程之间访问互不干扰
     * */
    public synchronized void syncMethodTest() {
        Log.e("TestMethods", "syncMethodTest开始执行");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void syncMethodTest() {
        synchronized (this) {
            Log.e("TestMethods", "syncMethodTest开始执行");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void syncMethodTest2() {
        Log.e("TestMethods", "syncMethodTest2开始执行");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

上面syncMethodTest两种加锁方式的效果是一样的,都是锁定对象,这种情况下,不同线程不可以同时访问同一对象的加锁方法,不仅是同一个方法,只要是锁定该对象的方法,都不可以同时访问,比如两个线程无法同时访问同一个对象的syncMethodTest方法,也不可以同时访问同一个对象的syncMethodTest和syncMethodTest2方法。

但如果是两个对象,不同线程之间访问的是不同对象的synchronized方法,他们之间是不会影响的,可以同时访问。

第二种是同步代码块:
    private byte[] lock = new byte[0];

    /**
     * 锁定变量
     * 当一个线程调用该方法时,其他线程无法访问持有该变量锁的任何方法
     * 需要注意的是,lock变量为对象属性,如果对象不同,比如两个线程中的两个TestMethod对象
     * 同时调用该方法,则互不影响,但如果是同一个对象,则需要等其中一个先释放锁才能继续执行
     * */
    public void syncByteTest() {
        synchronized (lock) {
            Log.e("TestMethods", "syncByteTest: 开始执行");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

通过第一种对对象加锁,有一个缺点,当我锁定对象时,对象的所有synchronized方法都将被锁定,浪费开销的同时又影响了其他无关函数的调用,所以更为搞笑的方法是锁定代码块。

如上所示,定义一个特殊的变量,给这个变量加锁,锁定的只是一个代码块而已,不同线程之间无法同时访问该代码块,但对象的其他方法还是可以正常访问的。

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

第三种是锁定class
    /**
     * 锁定class
     * 当一个线程调用该方法时,其他线程无法访问持有该class锁的静态变量和静态方法
     * 需要注意的是,class只有一个,这也意味着持有该锁的任何方法都不能同时访问
     * 持有class锁的方法必须争夺到锁会后才能运行
     * */
    public synchronized static void syncStaticTest() {
        Log.e("TestMethods", "syncStaticTest: 开始执行");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void syncStaticTest() {
        synchronized(TestMethods.class) {
            Log.e("TestMethods", "syncStaticTest: 开始执行");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }    
        }
    }

上述两种锁定方式是等价的,这种锁一般用在静态方法中,一旦对class锁定,该对象的所有静态方法,静态变量都不能访问,包括通过对象去访问也不行。

具体效果,读者可以通过test方法去测试一下:

public class SyncronizeTest {
    TestMethods testMethods = new TestMethods();

    public void test() {
        Thread thread1 = new Thread(runnable1);
        Thread thread2 = new Thread(runnable2);
        thread1.start();
        thread2.start();
    }

    Runnable runnable1 = new Runnable() {
        @Override
        public void run() {
            /**
             * 锁定调用方法的对象
             * */
            testMethods.syncMethodTest();

            /**
             * 锁定变量
             * */
            // testMethods.syncByteTest();

            /**
             * 锁定class
             * */
            // TestMethods.syncStaticTest();
        }
    };

    Runnable runnable2 = new Runnable() {
        @Override
        public void run() {
            /**
             * 锁定调用方法的对象
             * */
             testMethods.syncMethodTest();

            /**
             * 锁定变量
             * */
             // testMethods.syncByteTest();

            /**
             * 锁定class
             * */
            // TestMethods.syncStaticTest();
        }
    };
}

快过年了,今天先写到这里,过完年再补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值