多线程同步的方法

一,概述

1,什么是线程同步?

当使用多个线程来访问同一个数据时,这个数据在被一个线程访问完成前不允许被其他线程访问。这就叫同步。

2,什么情况下需要同步?

* 当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
* 如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.

3,多线程同步的方法

多线程同步的方法一共有五个:
1. 使用synchronized 关键字构成同步方法。
2. 使用synchronized 关键字构成同步代码块。
3. 使用ReentrantLock类构成同步代码块。
4. 使用volatile关键字修饰变量。
5. 如果使用ThreadLocal对象管理变量。

以上5种方法中使用最多的是前三种,第5种在Handler机制中有使用,第4种很少使用。下面详细讲述前三种方法。

二,不使用同步时出现的问题

下面写一个demo,模拟线程不同步带来的问题。
首先定义一个成员变量和一个成员方法,如下:

    public int num = 100;
    public  void print(){
        if(num >0){
            try {
                Thread.sleep(1000);//sleep(1000)可以让结果更清晰
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
            Log.d(TAG,"num==============="+num);
        }
    }

代码很简单,这里不做解释。

然后在点击事件中开启两个子线程,都调用print()方法,代码如下:

    public void click(View v){
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    print();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    print();
                }
            }
        }).start();
    }

我们期望的结果中不能出现num=0的情况,而在实际情况中有很大的概率出现了num=0。

问题原因分析:
当有多个线程并发时,哪个线程被执行是随机的。假设此时num=1,线程1抢到了执行权,线程1可以进入到if代码块中,当线程1正在执行sleep(1000)时执行权被线程2抢走了,而此时num仍然等于1,所以线程2也可以进入if代码块中,线程2中代码完全执行,此时num就等于0。此时线程1又获取到了执行权,由于线程1已经进入了if代码块中,此时会继续向下执行num–,所以就出现了num等于0的情况。

显然这是一个严重的问题,假如这是一个卖票系统,当票数等于0时还在卖票,就会出现问题。所以我们必须解决解决办法就是使用同步方法。

三, 使用synchronized 关键字构成同步方法。

改写后的print方法如下:

    public synchronized void print(){//使用synchronized 关键字修饰方法
        if(num >0){
            try {
                Thread.sleep(1000);//sleep(1000)可以让结果更清晰
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
            Log.d(TAG,"num==============="+num);
        }
    }

此时再次调用print方法就不会出现num=0的情况。
解析:当方法使用synchronized 关键字修饰后,这个方法称为同步方法。当一个线程调用同步方法时,只有这个方法执行完毕后才会释放执行权。此时就实现了同步的作用。

四,使用synchronized 关键字构成同步代码块

改写后的print方法如下:

    public  void print(){
        synchronized (this) {//添加同步锁
            if (num > 0) {
                try {
                    Thread.sleep(1000);//sleep(1000)可以让结果更清晰
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
                Log.d(TAG, "num===============" + num);
            }
        }
    }

此时再次调用print方法也不会出现num=0的情况。
解析:此时的方法虽然不是同步方法,但被synchronized 关键字包裹的代码块称为同步代码块。当一个线程调用方法执行到同步代码块时,只有同步代码块执行完毕后才会释放执行权。此时就实现了同步的作用。

五,使用ReentrantLock类构成同步代码块

ReentrantLock是java5.0后提供的一个类,它有两个重要的方法lock和unlock,使用这两个方法也可以给代码块加锁。使用示例如下:

private ReentrantLock lock = new ReentrantLock();
    public void print(){
        lock.lock();//加锁
            if (num > 0) {
                try {
                    Thread.sleep(1000);//sleep(1000)可以让结果更清晰
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
                Log.d(TAG, "num===============" + num);
            }
        lock.unlock();//解锁
    }

此时再次调用print方法也不会出现num=0的情况。
解析:ReentrantLock 类具有锁机制,当调用lock方法时这个线程就不会使用执行器,只有遇到unlock时才有可能失去执行权,所以这种方法也可以实现线程的同步。
注:当遇到unlock时这个线程才有可能失去执行权,但不一定会失去,有可能会继续拥有执行权。

六,Synchronized与ReentrantLock的区别

Synchronized是java中提供的一个关键字。ReentrantLock是java5.0后提供的一个类,这个类是java.util.concurrent.locks包中的,具有lock和unlock两个重要的方法。

二者在性能上的区别是:
1,Synchronized可能出现死锁现象。
2,ReentrantLock具有lockInterruptibly方法,可以中断锁等候。若线程一获得了锁,线程1执行的内容又比较多,此时若线程2不想等待,就可以中断锁,可以优化性能。而且不会出现死锁现象。在线程间通讯时ReentrantLock提供了Condition实例对象,让等待唤醒机制操作更加灵活。

七,线程安全

1,什么是线程安全?

线程安全是指:同一个数据在某一个时间段只能被一个线程操作。或者说一个数据被一个线程访问结束后才能被其他线程访问。

2,线程安全与线程同步的区别?

线程同步是一种技术方法。
线程安全是一种实现的效果。
使用了线程同步保证了线程安全。

3,常见的线程安全的例子

  • Vector是线程安全的,ArrayList是线程不安全的
  • StringBuffer是线程安全的,StringBuilder是线程不安全的
  • Hashtable是线程安全的,HashMap是线程不安全的
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值