Java 线程同步

Java 线程同步

综述

我们知道,当不同的线程对同一个变量进行操作的时候,将会有可能出现数据不统一的情况。
为了解决这种问题,Java提供了几种方式:synchronized关键字,Lock锁机制,ThreadLocal线程副本的方式。

synchronized关键字

synchronized是一种互斥锁,如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。
在Java中每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,只有获取了该对象的锁的线程才能访问。
synchronized关键字主要有如下的两种使用方式

同步代码块

通过同步代码块对多线程环境下的共享的变量进行限制,同一时间只允许一个线程对共享的变量进行修改,知道该线程释放这个锁为止。

Integer i=0;
    void set(int count){
        synchronized (i){    //对i进行加锁,同一时间仅仅允许一个线程对i进行修改
            i+=count;
        }
    }

同步方法

即通过synchronized修饰的方法,当我们通过synchronized方法修饰的时候,将对整个方法加锁,在没有释放这个锁之前,其他的线程无法对该方法进行操作。
值得注意的是因为一个对象只有一个锁,所以当一个线程获得了一个对象的锁后,其他的线程也不能访问该对象的其他的synchronized方法,但是可以访问其中的非synchronized方法。
如下是一个简单的synchronized方法的例子

int i=0;
    synchronized void set(int count){   //对方法进行synchronized修饰
        i+=count;
    }

Lock锁机制

Lock是Java5后面出现的一种新的同步访问的方法。
相较于synchronized,Lock提供了一种更加灵活的加锁机制。
当我们使用synchronized后,如果一个由于等待Io或者调用sleep等方法的问题线程被阻塞,同时他将不会释放锁,其他的线程就会一直等待。
同时如果我们通过同步代码块对一个变量进行操作时候,这个时候读操作和读操作应该不会互斥的,但是由于synchronized机制的问题,我们必须等待一个线程对该变量操作完成后,才能进行另外一个线程的操作。

这里主要介绍一下重入锁和读写锁

ReentrantLock重入锁

ReentrantLock实现了lock接口,重入锁是基于线程分配,而不是基于方法分配,例如下面的例子

Integer i=0;
    synchronized void set(int count){
        get();         //由于重入锁机制,不在需要添加synchronized 
        i+=count;

    }
    void get(){

    }

在代码中通过ReentrantLock实现Lock加锁

private Lock lock = new ReentrantLock();  
void set(){
 lock.lock();   //加锁
 try {
            for(;;) {
                //进行一些操作
            }
        } finally {
            System.out.println(thread.getName()+"释放了锁");
            lock.unlock();    //释放锁
        }
 }

值得注意的是在这里private Lock lock = new ReentrantLock(); 需要声明为类变量,如果在set函数中加上如上的代码,不会起到同步的作用,因为每一个线程执行到这里都会得到一个新的Lock实例。

tryLock()方法

tryLock方法体现了Lock方法的灵活性,tryLock会尝试获得一个锁,不论获取成功与否都会直接返回,也就不会一直被阻塞。

lockInterruptibly()方法。

在锁上等待,直到获取锁,但是会响应中断,这个方法优先考虑响应中断,而不是响应锁的普通获取或重入获取。其实也就是一个特别的锁,我们知道当线程被阻塞时候如果对其进行中断操作是没有反应的,因为这个没有sleep,wait等条件让其产生中断异常,这个时候如果当线程被阻塞的时候,我们就可以使用lock.lockInterruptibly()方法,当其被阻塞时候,如果调用其Interrupt方法,将会优先相应中断异常。

如下代码所示

void set() throws InterruptedException {
            lock.lockInterruptibly();             //通过lock.lockInterruptibly();  进行锁的调用

        try {
            for (int i=0;i<10;i++){
                Log.e("123",Thread.currentThread().getName()+i);         //循环打印10次
                try {
                    Thread.sleep(1000);                                   //休眠1秒钟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
       finally {
            lock.unlock();                                        //最后释放锁
        }

    }

class Th implements Runnable{     //测试线程类

        @Override
        public void run() {
            try {
                set();              //调用set会抛出中断异常
            } catch (InterruptedException e) {
                e.printStackTrace();
                Log.e("123",Thread.currentThread().getName()+"我被中断了");    //如果中断了打印中断
            }
            Log.e("123",Thread.currentThread().getName()+"我做完了");         //完成了操作
        }
    }

执行代码

 Thread th=new Thread(new Th(), "one");
        Thread th1=new Thread(new Th(),"two");
        th.start();
        th1.start();
        th1.interrupt();         //对th1进行中断操作。

执行结果如下

 one0
 two我被中断了
 two我做完了        //优先相应中断 ,直接捕获中断异常
 one1              //接着继续执行one的操作
 one2
one3
one4
 one5
one6
 one7
 one8
 one9
one我做完了             

ThreadLocal

ThreadLocal,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
关于ThreadLocal我在Android 的handler机制中对其进行了解读,因为他可以让每一个线程拥有自己的副本变量,所以可以很轻松的实现不同线程的并发问题。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值