java并发编程详解汪文君,JAVA并发编程

  java并发编程详解汪文君,JAVA并发编程

  如何解决写爬虫IP受阻的问题?立即使用。

  一.synchronized的缺陷

  Synchronized是java中的一个关键字,也就是说,它是Java语言的一个内置特性。那么为什么会发生锁定呢?

  如果一个代码块被synchronized修饰,那么当一个线程获得相应的锁并执行该代码块时,其他线程只能一直等待,等待获得锁的线程释放锁,但是这里获得锁的线程只有在两种情况下才会释放锁:

  1)获得锁的线程完成了代码块的执行,然后该线程释放了它对锁的占有;

  2)线程执行出现异常。这时JVM会让线程自动释放锁。

  推荐:java基础课程

  然后,如果获得锁的线程由于等待IO或其他原因(如调用sleep方法)而被阻塞,但锁没有被释放,其他线程将不得不空闲等待。想象一下这是如何影响程序执行效率的。

  因此,需要有一种机制来防止等待线程无限期等待(例如,只等待一定的时间或者能够响应一个中断),这可以通过Lock来实现。

  再比如:有多个线程读写文件时,读操作和写操作会有冲突,但读操作和写操作不会有冲突。

  但是,使用synchronized关键字来实现同步会导致一个问题:

  如果多个线程都只是在读,那么当一个线程在读的时候,其他线程只能等待,不能读。

  所以需要一种机制来保证当多个线程都只是读的时候,线程之间不会发生冲突,这可以通过Lock来实现。

  另外,通过锁可以知道线程是否成功获取了锁。这个同步做不到。

  综上所述,Lock提供的功能比synchronized多。但要注意以下几点:

  1)锁不是Java语言内置的。synchronized是Java语言的关键词,所以是内置特性。Lock是一个类,通过它可以实现同步访问;

  lock和synchronized有非常大的区别。使用synchronized不需要用户手动释放锁。当synchronized方法或synchronized代码块完成时,系统会自动让线程释放锁;锁定要求用户手动解锁。如果不主动释放锁,可能会导致死锁。

  二.java.util.concurrent.locks包下常用的类

  让我们讨论一下java.util.concurrent.locks包中常用的类和接口。

  1.Lock

  首先要说明的是锁。通过查看Lock的源代码,可以看出Lock是一个接口:

  公共接口锁{

  void lock();

  void lockInterruptibly()引发InterruptedException

  布尔tryLock();

  布尔tryLock(long time,TimeUnit单位)抛出InterruptedException

  void unlock();

  condition new condition();

  }下面我们一个一个的说一下锁界面中各个方法的用法。Lock()、tryLock()、tryLock(长时间,时间单位单位)和lockinterrupt()用于获取锁。unLock()方法用于释放锁。newCondition()方法在这里暂时不做描述,在下面的线程协作文章中会有描述。

  Lock中声明了四个方法来获取锁,那么这四个方法有什么区别呢?

  首先,lock()方法是最常用的方法之一,用来获取锁。如果锁已被另一个线程获取,请等待。

  如前所述,如果采用锁,必须主动释放锁,发生异常时不会自动释放锁。一般来说,锁的使用必须在try{}catch{}块中进行,释放锁的操作必须在finally块中进行,以保证锁必须被释放,防止死锁。通常,如果锁用于同步,它以下列形式使用:

  锁定锁定=.

  lock . lock();

  尝试{

  //处理任务

  }catch(Exception ex){

  }最后{

  lock . unlock();//释放锁定

  }tryLock()方法有一个返回值,表明它用于尝试获取锁。如果获取成功,则返回true如果获取失败(即锁已被其他线程获取),将返回false,这意味着无论如何该方法都将立即返回。你不会一直等在那里,直到你拿不到锁。

  TryLock(long time,TimeUnit unit)方法和TryLock()方法类似,只是这个方法在不能获得锁的时候会等待一定的时间,如果在期限内不能获得锁就会返回false。如果最初或在等待期间获得锁,则返回true。

  因此,一般来说,锁是由tryLock获取的,如下所示:

  锁定锁定=.

  if(lock.tryLock()) {

  尝试{

  //处理任务

  }catch(Exception ex){

  }最后{

  lock . unlock();//释放锁定

  }

  }否则{

  //如果不能得到锁,直接做别的

  } lockInterruptibly()方法比较特殊。用这种方法获取锁时,如果线程正在等待获取锁,线程可以响应中断,即中断线程的等待状态。

  也就是说,当两个线程要同时通过lock.lockinterrupt()获取一个锁时,如果此时线程A获取了锁,而线程B只是等待,那么在线程B上调用threadB.interrupt()方法就可以中断线程B的等待进程。

  因为在lockinterrupt()的声明中引发了异常,所以lock.lockinterrupt()必须放在try块中,或者必须在调用lockinterrupt()的方法之外声明InterruptedException。

  因此,lockinterrupt()的一般用法形式如下:

  公共void方法()引发InterruptedException {

  lock . lock interruptible();

  尝试{

  //.

  }

  最后{

  lock . unlock();

  }

  }注意,当一个线程获得锁时,它不会被interrupt()方法中断。因为我在上一篇文章中说过,单独调用interrupt()方法是无法中断正在运行进程中的线程的,只能中断阻塞进程中的线程。

  因此,当lockinterrupt()方法获取锁时,如果无法获取,它只能通过等待来响应中断。

  有了同步装饰,当一个线程在等待锁的时候,就不能被中断;它必须永远等待。

  2.ReentrantLock

  ReentrantLock的意思是“重入锁”。下一节将描述可重入锁的概念。ReentrantLock是唯一实现Lock接口的类,ReentrantLock提供了更多的方法。下面是一些例子来看看如何使用ReentrantLock。

  例如,lock()的正确用法

  公共类测试{

  private ArrayListInteger arrayList=new ArrayListInteger();

  公共静态void main(String[] args) {

  最终测试Test=new Test();

  新线程(){

  公共无效运行(){

  test . insert(thread . current thread());

  };

  }.start();

  新线程(){

  公共无效运行(){

  test . insert(thread . current thread());

  };

  }.start();

  }

  公共空插件(螺纹螺纹){

  lock lock=new reentrant lock();//注意这个地方

  lock . lock();

  尝试{

  system . out . println(thread . getname()获得锁);

  for(int I=0;i5;i ) {

  ArrayList . add(I);

  }

  } catch(异常e) {

  //TODO:处理异常

  }最后{

  system . out . println(thread . getname()释放锁);

  lock . unlock();

  }

  }

  }输出结果:

  insert方法中的lock变量是一个局部变量,每个线程在执行该方法时都会保存一个副本。当然,每个线程在执行lock.lock()时都会获得不同的锁,所以不会有冲突。

  知道原因就更容易改变。您只需要将lock声明为该类的一个属性。

  公共类测试{

  private ArrayListInteger arrayList=new ArrayListInteger();

  私有锁Lock=new reentrant Lock();//注意这个地方

  公共静态void main(String[] args) {

  最终测试Test=new Test();

  新线程(){

  公共无效运行(){

  测试。插入(螺纹。当前线程());

  };

  }.start();

  新线程(){

  公共无效运行(){

  测试。插入(螺纹。当前线程());

  };

  }.start();

  }

  公共空插件(螺纹螺纹){

  锁定。lock();

  尝试{

  系统。出去。println(线程。getname()得到了锁);

  for(int I=0;i5;i ) {

  数组列表。添加;

  }

  } catch(异常e) {

  //TODO:处理异常

  }最后{

  系统。出去。println(线程。getname()释放了锁);

  锁定。unlock();

  }

  }

  }这样就是正确地使用锁的方法了。

  例子2、tryLock()的使用方法

  公共类测试{

  private ArrayListInteger arrayList=new ArrayListInteger();

  私有锁Lock=new reentrant Lock();//注意这个地方

  公共静态void main(String[] args) {

  最终测试Test=new Test();

  新线程(){

  公共无效运行(){

  测试。插入(螺纹。当前线程());

  };

  }.start();

  新线程(){

  公共无效运行(){

  测试。插入(螺纹。当前线程());

  };

  }.start();

  }

  公共空插件(螺纹螺纹){

  if(lock.tryLock()) {

  尝试{

  系统。出去。println(线程。getname()得到了锁);

  for(int I=0;i5;i ) {

  数组列表。添加;

  }

  } catch(异常e) {

  //TODO:处理异常

  }最后{

  系统。出去。println(线程。getname()释放了锁);

  锁定。unlock();

  }

  }否则{

  系统。出去。println(线程。getname()获取锁失败);

  }

  }

  }输出结果:

  例子3、可中断地锁定()响应中断的使用方法:

  公共类测试{

  私有锁Lock=new reentrant Lock();

  公共静态void main(String[] args) {

  测试Test=new Test();

  MyThread thread 1=new MyThread(测试);

  MyThread thread 2=new MyThread(测试);

  线程1。start();

  线程2。start();

  尝试{

  线程。睡眠(2000年);

  } catch (InterruptedException e) {

  e。printstacktrace();

  }

  thread2.interrupt()。

  }

  公共空的插入(线程线程)引发中断的异常{

  锁定。lock interruptible();//注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将中断异常抛出

  尝试{

  系统。出去。println(线程。getname()得到了锁);

  长启动时间=系统。当前时间毫秒();

  for(;) {

  如果(系统。当前时间毫秒()-开始时间=整数.最大值)

  打破;

  //插入数据

  }

  }

  最后{

  系统。出去。println(线程。当前线程().getName()执行最后);

  锁定。unlock();

  系统。出去。println(线程。getname()释放了锁);

  }

  }

  }

  类神话故事扩展线程{

  私有测试测试=空

  公共MyThread(测试测试){

  测试=测试

  }

  @覆盖

  公共无效运行(){

  尝试{

  测试。插入(螺纹。当前线程());

  } catch (InterruptedException e) {

  系统。出去。println(线程。当前线程().getName()被中断);

  }

  }

  }运行之后,发现线程2能够被正确中断。

  3.ReadWriteLock

  读写锁也是一个接口,在它里面只定义了两个方法:

  公共接口读写锁{

  /**

  *返回用于读取的锁。

  *

  * @返回用于读取的锁。

  */

  lock read lock();

  /**

  *返回用于写入的锁。

  *

  * @返回用于写入的锁。

  */

  lock writeLock();

  }一个用于获取读锁,另一个用于获取写锁。也就是把文件的读写操作分开,给线程分配两个锁,让多个线程同时读取。下面的ReentrantReadWriteLock实现了ReadWriteLock接口。

  4.ReentrantReadWriteLock

  ReentrantReadWriteLock提供了很多丰富的方法,但是主要有两个方法:readLock()和WriteLock()来获得读锁和写锁。

  我们通过下面的例子来看看ReentrantReadWriteLock的具体用法。

  如果有多个线程要同时读取,先看看synchronized的效果:

  公共类测试{

  private reentrantreadwritellock rwl=new reentrantreadwritellock();

  公共静态void main(String[] args) {

  最终测试Test=new Test();

  新线程(){

  公共无效运行(){

  test . get(thread . current thread());

  };

  }.start();

  新线程(){

  公共无效运行(){

  test . get(thread . current thread());

  };

  }.start();

  }

  公共同步void get(线程thread) {

  long start=system . current time millis();

  while(system . current time millis()-start=1){

  system . out . println(thread . getname()正在读取);

  }

  system . out . println(thread . getname()读取操作完成);

  }

  }这个程序的输出结果将是,在thread1完成读取之前,不会打印thread2执行的读取操作的信息。

  不使用读写锁:

  公共类测试{

  private reentrantreadwritellock rwl=new reentrantreadwritellock();

  公共静态void main(String[] args) {

  最终测试Test=new Test();

  新线程(){

  公共无效运行(){

  test . get(thread . current thread());

  };

  }.start();

  新线程(){

  公共无效运行(){

  test . get(thread . current thread());

  };

  }.start();

  }

  public void get(Thread thread) {

  rwl.readLock()。lock();

  尝试{

  long start=system . current time millis();

  while(system . current time millis()-start=1){

  system . out . println(thread . getname()正在读取);

  }

  system . out . println(thread . getname()读取操作完成);

  }最后{

  rwl.readLock()。unlock();

  }

  }

  }此时打印的结果是:

  说明线程1和线程2同时在读取。

  这大大提高了读取操作的效率。

  但需要注意的是,如果一个线程已经占用了读锁,此时如果其他线程想要申请写锁,申请写锁的线程会一直等待读锁的释放。

  如果一个线程已经占用了写锁,如果此时其他线程申请了写锁或读锁,被申请的线程将一直等待释放写锁。

  对ReentrantReadWriteLock类的其他方法感兴趣的朋友可以自行查阅API文档。

  5.Lock和synchronized的选择

  总之,锁定和同步之间存在以下差异:

  1)Lock是一个接口,synchronized是Java中的关键词。synchronized是内置的语言实现;

  2)发生异常时,synchronized会自动释放线程持有的锁,所以不会导致死锁;但是当锁发生异常时,如果没有通过unLock()释放锁,很可能会造成死锁,所以在使用锁时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,synchronized则不能。使用synchronized时,等待线程会一直等待,无法响应中断;

  4)可以通过锁知道自己是否成功获取了锁,synchronized则不能。

  5)锁可以提高多线程读取的效率。

  在性能方面,如果资源竞争不激烈,两者的性能差不多,而当资源竞争激烈时(即大量线程同时竞争),Lock的性能远远优于synchronized。所以具体使用要根据适当的情况来选择。

  三.锁的相关概念介绍

  在前一节中,介绍了锁的基本用法。本节介绍几个与锁相关的概念。

  1.可重入锁

  如果锁是可重入的,则称为可重入锁。同步锁和可重入锁都是可重入锁。在我看来,reentrant实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行一个同步的方法,比如method1,另一个同步的方法method2会在method1中被调用。此时,线程不必再次申请锁,而是可以直接执行方法method2。

  看看下面的代码就明白了:

  MyClass类{

  公共同步void方法1() {

  method 2();

  }

  公共同步void方法2() {

  }

  }上面代码中的两个方法method1和method2是用synchronized修饰的。如果在某个时刻,线程A执行了method1,那么线程A获得了这个对象的锁。因为method2也是一个synchronized方法,如果synchronized没有可重入性,那么线程A需要重新申请锁。但是这样会产生一个问题,因为线程A已经持有了对象的锁,并且正在申请对象的锁,这样线程A就要等待永远不会被获取的锁。

  但是,因为synchronized和Lock都是可重入的,所以不会出现上述现象。

  2.可中断锁

  可中断锁(Interruptible lock):顾名思义,是一种可以相应中断的锁。

  在Java中,synchronized不是可中断锁,但是Lock是可中断锁。

  如果一个线程A正在执行锁中的代码,另一个线程B正在等待获取锁,可能是因为等待时间太长,线程B不想等待,想先处理其他的事情。我们可以让它自己中断,或者在另一个线程中中断。这是一个可中断的锁。

  在前面使用LockInterruptibly()的演示中,已经体现了lock的可中断性。

  3.公平锁

  公平锁定意味着尝试按照请求的顺序获取锁。例如,有多个线程同时等待一个锁。当锁被释放时,等待时间最长的线程(第一个请求线程)将获得锁。这是一把漂亮的锁。

  不公平锁不能保证锁是按照请求锁的顺序获得的。这可能会导致一个或一些线程永远无法获得锁。

  在Java中,synchronized是一个不公平锁,不能保证等待线程获取锁的顺序。

  对于ReentrantLock和ReentrantReadWriteLock,默认情况下是不公平锁,但可以设置为公平锁。

  只需查看这两个类的源代码:

  ReentrantLock中定义了两个静态内部类,一个不是FairSync,一个是FairSync,分别用来实现不公平锁和公平锁。

  在创建ReentrantLock对象时,我们可以通过以下方式设置锁的公平性:

  reentrant lock lock=new reentrant lock(true);如果参数为真,则表示公平锁;如果是fasle,说明锁不公平。默认情况下,如果使用无参数的构造函数,这是一个不公平的锁。

  此外,ReentrantLock类中定义了许多方法,例如:

  IsFair() //确定锁是否公平。

  IsLocked() //确定锁是否已被任何线程获取

  EldbyCurrentThread()//确定锁是否已被当前线程获取。

  HasQueuedThreads() //确定是否有线程正在等待锁。

  ReentrantReadWriteLock中也有类似的方法,也可以设置为公平锁和不公平锁。但是,请记住,ReentrantReadWriteLock不实现Lock接口,它实现ReadWriteLock接口。

  4.读写锁

  读写锁将对资源(如文件)的访问分成两个锁,一个读锁和一个写锁。

  因为有读写锁,多线程之间的读操作不会冲突。

  Read lock是一个ReadWriteLock,是一个接口,ReentrantReadWriteLock实现了这个接口。

  您可以通过readLock()获得readlock,通过writeLock()获得writelock。

  读写锁的用法上面已经演示过了,这里不再赘述。

  原文地址:http://www.cnblogs.com/dolphin0520/p/3923167.html以上是java并发编程的详细内容。更多请关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: