Java并发编程-synchronzied和lock

线程安全是并发编程必须要关注的问题.多个线程同时访问共享资源,就会出现共享资源被修改情况,其他线程无法接受到修改后的结果,导致错误的运行结果.比如共享变量,共享文件的写操作等等.锁机制保证了一个共享数据同时只能被一个线程操作,其他线程只能等待当前线程释放锁,然后再获取锁,进而操作或者访问共享数据.

在JDK1.5之前都是使用synchronzied和volatile关键字来实现共享对象访问机制, lock并不是为了替代内置锁,而是提供内置锁具有的一些额外的功能.

synchronzied

synchronzied是java内置的关键字,是jvm层面的,对于synchronzied理解有三点:

(1)synchronzied可以修饰代码块,方法,静态方法和类.只有一个线程能访问到共享数据.线程A要请求一个被线程B占用的锁,线程A会一直等待或者阻塞直到线程B释放锁,也就是说B如果不释放锁的时候,A会永远的等待下去.不能中断那些等待获取锁的线程,并且在请求锁失败的情况下,会无限等待.

(2)synchronzied的锁不需要手动释放,有两种情况下,获取锁的线程会释放锁.其一是当被synchronzied修饰的代码执行完,线程会释放锁,其二是线程执行同步代码过程中发生异常,jvm会让线程释放锁.

(3)多个线程进行读取操作时相互之间不会冲突,而synchronized实现的同步,当一个线程进行读操作的时候,其他线程只能等待无法进行读取操作,这种情况下,效率比较低下.

由此看见,synchronzied实现的同步有很多的限制,lock就提供了比synchronized更多的功能,打破了这种限制,但是使用lock实现同步某种程度上比内部锁更加复杂.

Lock

Lock是java.util.concurrent.locks下面的一个接口,使用此接口及实现类实现同步,Lock接口的源码如下:

public interface Lock {
  void lock();
  void lockInterruptibly() throws InterruptedException;
  boolean tryLock();
  boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  void unlock();
  Condition newCondition();
}

 

  • lock()方法是比较常见的方法,用来获取锁,如果锁被其他的线程占用,线程将处于休眠状态.采用lock,必须手动释放锁,但也不排除出现异常的情况,所以lock以try{}finally{}的形式使用,在finally中进行锁的释放,保证发生异常时锁也会得到释放,避免了死锁的发生.使用方式如下:
 Lock lock = ...;
  ...
  lock.lock();
  try {
    //更新对象状态,捕获异常
  }finally {
    lock.unlock();//释放锁
  }
  • lockInterruptibly() throws InterruptedException方法表示的锁可用情况下,会立即获取锁.如果线程处于等待获取锁的状态 ,线程能够响应中断,即中断线程等待状态.如果锁被线程A占用,线程B使用lock.lockInterruptibly()获取锁的时候,会一直处于等待状态,此时其他线程可调用interrupt()方法能中断线程B的等待状态.                                                                                在获取锁过程线程被中断,抛出异常InterruptedException,使用方式try{}finally{}形式,或者在调用lockInterruptibly()方法外抛出异常.
public void threadDemo() throws InterruptedException {
    Lock lock = ...;
    ...
    lock.lockInterruptibly();
    try {  
     .....
    }
    finally {
        lock.unlock();
    }  
}     
  • tryLock()方法也是用来获取锁,锁可用情况下,获取锁,并且立即返回true,锁不可用情况下,会立即返回false.相比lock()方法,获取不到锁情况下不会一直等待.
  • tryLock(long time,TimeUnit unit) throws InterruptedException.其中time是等待锁的最长时间,unit是参数的时间单位.方法表示锁在给定等待时间time内空闲,并且当前线程未被中断,获得了锁时,方法会立即返回true.在给定的时间time内获取不到锁,会返回false.如果将时间设置小于等于0,方法会立即返回.
Lock lock = ...;
  ...
  lock.lock();
  if(lock.tryLock()) {
    try {
      //
    }finally {
      lock.unlock();
    }
  }else {
    //不能获取到锁的情况下.
  }
  • Condition是一个接口,作用是锁进行更加精确的控制,能够控制多线程的休眠和唤醒,对于一个锁可以创建多个Condition,不同情况下使用不同的Condition.Condition接口里面有与Object相对应的休眠和唤醒的方法.不同的是Object中唤醒和等待的方法经常与synchronzied搭配使用.
   Condition   Object     作用
   await        wait     线程休眠
   signal      notify    唤醒一个线程
   signalAll   notifyAll 唤醒所有线程

 Lock使用案例

     主要来看一下Lock的使用方式.Lock是接口,经常使用的是它的实现类ReentrantLock.ReentrantLock提供了与synchronzied相同的互斥和内存可见性.

    1.lock()方法使用如下:

public class SellTicketTask implements Runnable {
  private int ticket = 10;
  private Lock lock = new ReentrantLock();
  public void run() {
    try {
      if (ticket > 0) {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + "拿到锁");
        ticket--;
        System.out.println("剩余票" + ticket);
      }
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
      System.out.println(Thread.currentThread().getName() + "释放锁");
    }
  }
}
public class LockDemo2 {
  public static void main(String[] args) {
    SellTicketTask task = new SellTicketTask();
    new Thread(task).start();
    new Thread(task).start();
  }
}

运行结果:

Thread-0拿到锁
剩余票9
Thread-0释放锁
Thread-1拿到锁
剩余票8
Thread-1释放锁

这个demo之前,有一个错误的使用Lock的案例,还是把代码贴在这里,防止犯同样的错误.如下:

public class LockDemo {
  //private Lock lock = new ReentrantLock();//正确的方法
  private int ticket = 10;
  public static void main(String[] args) {
   final LockDemo lockDemo = new LockDemo();
    new Thread() {
      public void run() {
        lockDemo.sellTicket(Thread.currentThread());
      }
    }.start();
     new Thread() {
       public void run() {
         lockDemo.sellTicket(Thread.currentThread());
       }
     }.start();
  }
  
  private void sellTicket(Thread currentThread) {
    //Lock lock =new ReentrantLock();非正确方式.当把锁放在这里的时候,两个线程分别调用分别会产生两个锁
    lock.lock();
    try {
      System.out.println(currentThread.getName()+"得到了锁");
      ticket--;
      System.out.println("剩余票数"+ticket);
    }catch(Exception e) {
      e.printStackTrace();
    }finally {
      System.out.println(currentThread.getName()+"释放了锁");
      lock.unlock();
    }
  }

运行会产生这样的结果:

Thread-1得到了锁
Thread-0得到了锁
剩余票数9
剩余票数8
Thread-1释放了锁
Thread-0释放了锁

当把锁放在sellTicket()方法里面的时候,会产生上面得结果.之所产生这样的结果,稍微分析可以知道.两个线程分别调用sellTicket()方法的时候,会导致每个线程都会运行Lock lock= new ReetrantLock(),每个线程都产生一个锁.我们目的就是为了同时只有一个线程访问共享数据,所以要使用同一个锁.将lock变量声明为类的局部变量

    2.tryLock()方法的使用案例

public class LockDemo {
 private Lock lock = new ReentrantLock();
  private int ticket = 10;
  public static void main(String[] args) {
   final LockDemo lockDemo = new LockDemo();
    new Thread() {
      public void run() {
        lockDemo.sellTicket(Thread.currentThread());
      }
    }.start();
     new Thread() {
       public void run() {
         lockDemo.sellTicket(Thread.currentThread());
       }
     }.start();
  }
  
  private void sellTicket(Thread currentThread) {
    if(lock.tryLock()) {
      try {
        System.out.println(currentThread.getName()+"得到了锁");
        ticket--;
        System.out.println("剩余票数"+ticket);
      }catch(Exception e) {
        e.printStackTrace();
      }finally {
        System.out.println(currentThread.getName()+"释放了锁");
        lock.unlock();
      }
    }else {
      System.out.println(currentThread.getName()+"获取锁失败");
    }
  }
}

运行结果:

Thread-0得到了锁
剩余票数9
Thread-0释放了锁
Thread-1获取锁失败

    3.使用LockInterruptebily()方法的案例

public class LockDemo {
  private Lock lock = new ReentrantLock();
  private int ticket = 10;

  public static void main(String[] args) {
    final LockDemo lockDemo = new LockDemo();
    Thread thread1 = new Thread() {
      public void run() {
        try {
          lockDemo.sellTicket(Thread.currentThread());
        } catch (InterruptedException e) {
          System.out.println(Thread.currentThread().getName() + "线程被中断");
        }
      }
    };
    Thread thread2 = new Thread() {
      public void run() {
        try {
          lockDemo.sellTicket(Thread.currentThread());
        } catch (InterruptedException e) {
          System.out.println(Thread.currentThread().getName() + "线程被中断");
        }
      }
    };
    thread1.start();
    thread2.start();
    thread2.interrupt(); // 中断第二个线程
  }

  private void sellTicket(Thread currentThread) throws InterruptedException {
    lock.lockInterruptibly();
    try {
      System.out.println(currentThread.getName() + "得到了锁");
      ticket--;
      System.out.println("剩余票数" + ticket);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      System.out.println(currentThread.getName() + "释放了锁");
      lock.unlock();
    }
  }
}

运行结果:

Thread-1线程被中断
Thread-0得到了锁
剩余票数9
Thread-0释放了锁

synchronzied与Lock的区别

可以得到,synchronized与lock有几点区别:

  1. synchronzied关键字是java内置的,是jvm层面的,而lock是一个接口.
  2. synchronzied是无法判断是否获取了锁,但lock可以判断是否获取了锁,知道锁的状态.
  3. synchronzied不管是执行同步代码块或者发生异常释放锁,都会自动释放锁,不需要手动干预.而lock需要使用try{}finally{}形式,在finally保证锁被释放,否则可能发生死锁现象.
  4. synchronzied在同一时间只能有一个线程访问,lock在读取锁时,可以让多个线程同时访问.
  5. synchronzied没有中断线程的机制,获取不到锁时会一直等待下去.lock可以中断等待获取锁的线程状态.

所以在内部锁不满足需求的时候,或者说需要使用到可定时,可轮询与可中断锁获取操作,公平队列或者非结构的锁时,可以使用Lock,否则的还是使用更加简洁的synchronzied.

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值