zoukankan      html  css  js  c++  java
  • java并发编程-Lock

    为何有了synchronized还要有Lock

    • synchronized只有等当前线程执行完,才能释放锁,或者虚拟机出错时释放资源。
    • synchronized可能造成其它线程长时间的等待,比如获取锁的线程在执行耗时的IO操作,那么其它需要该资源的线程只能长时间的等待。
    • synchronized无法知道当前线程获取锁是否成功。

    Lock简介

    1:基本概念

    • Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
    • 锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
    • Lock 接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁
    • 锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
    • Lock 实现提供了使用 synchronized 方法和语句所没有的其它功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。
    • 所有 Lock 实现都必须 实施与内置监视器锁提供的相同内存同步语义。

    2: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 实现可能能够检测到锁的错误使用,比如会导致死锁的调用,在那种环境下还可能抛出一个 (unchecked) 异常。Lock 实现必须对环境和异常类型进行记录。
    

    一般的格式为:记住lock使用结束后一定要释放。

    Lock lock = ...;
    lock.lock();
    try{
        doSomeThing();
    }catch(Exception ex){
         
    }finally{
        lock.unlock();   
    }
    

    boolean tryLock()方法
    仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。此用法可确保如果获取了锁,则会释放锁,如果未获取锁,则不会试图将其释放。
    常用使用格式为:

     Lock lock = ...;
          if (lock.tryLock()) {
              try {
                  // manipulate protected state
              } finally {
                  lock.unlock();
              }
          } else {
              // perform alternative actions
          }
    

    boolean tryLock(long time,TimeUnit unit)方法
    如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。如果锁可用,则此方法将立即返回值 true。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在发生以下三种情况之一前,该线程将一直处于休眠状态:

    • 锁由当前线程获得
    • 其他某个线程中断当前线程,并且支持对锁获取的中断;
    • 或已超过指定的等待时间
      如果获得了锁,则返回值 true。

    如果当前线程:在进入此方法时已经设置了该线程的中断状态;或者
    在获取锁时被中断,并且支持对锁获取的中断,则将抛出 InterruptedException,并会清除当前线程的已中断状态。如果超过了指定的等待时间,则将返回值 false。如果 time 小于等于 0,该方法将完全不等待。

    void lockInterruptibly()方法
    lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。

      由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。
      

    public void method() throws InterruptedException {
        lock.lockInterruptibly();
        try {  
         doSomeThing();
        }
        finally {
            lock.unlock();
        }  
    }
    
    因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
    

    Condition newCondition()
    返回绑定到此 Lock 实例的新 Condition 实例。
    在等待条件前,锁必须由当前线程保持。调用 Condition.await() 将在等待前以原子方式释放锁,并在等待返回前重新获取锁。

    3:ReentrantLock简介
    基本概念:

    • 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
    • ReentrantLock 将由最近成功获得锁,并且还没有释放该锁的线程所拥有。当锁没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁并返回。如果当前线程已经拥有该锁,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。
    • 此类的构造方法接受一个可选的公平 参数。当设置为 true 时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。
    • 使用公平锁的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁时。还要注意的是,未定时的 tryLock 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁是可用的,此方法就可以获得成功。
    package com.csu.thread;
    
    import java.util.HashSet;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReenteantLockTest {
    	//static Lock lock=new ReentrantLock();
    	private static HashSet<Integer> hashSet=new HashSet<>();
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Sum sum=new Sum();
    		new Thread(new Runnable() {
    			@SuppressWarnings("static-access")
    			public void run() {
    				sum.add(10000);
    				
    				try {
    					Thread.currentThread().sleep(10000);
    					System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    		}).start();
    		new Thread(new Runnable() {
    			public void run() {
    				sum.add(20000);
    				System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
    			}
    		}).start();
    
    	}
    	
    	public static class Sum {
    		private Lock lock=new ReentrantLock();
    		
    		public void add(int i)
    		{
    			lock.lock();
    			int sum=0;
    			try {
    				System.out.println("当前线程:"+Thread.currentThread().getName()+"得到了锁");
    				for(int j=0;j<i;j++)
    				{
    					sum+=j;
    				}
    				
    			} catch (Exception e) {
    				// TODO: handle exception
    			}
    			finally {
    				lock.unlock();
    				System.out.println("当前线程:"+Thread.currentThread().getName()+"释放锁");
    			}
    			hashSet.add(sum);
    		}
    	}
    
    }
    

    运行结果:

    当前线程:Thread-0得到了锁
    当前线程:Thread-0释放锁
    当前线程:Thread-1得到了锁
    当前线程:Thread-1释放锁
    当前线程:Thread-1结束
    当前线程:Thread-0结束
    

    我们发现Thread-0没有结束,就及时的释放了线程获取的资源。
    tryLock():

    import java.util.HashSet;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReenteantLockTest {
    	//static Lock lock=new ReentrantLock();
    	private static HashSet<Integer> hashSet=new HashSet<>();
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Sum sum=new Sum();
    		new Thread(new Runnable() {
    			public void run() {
    				sum.add(1000000000);
    					System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
    				
    			}
    		}).start();
    		new Thread(new Runnable() {
    			public void run() {
    				sum.add(20000);
    				System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
    			}
    		}).start();
    
    	}
    	
    	public static class Sum {
    		private Lock lock=new ReentrantLock();
    		
    		public  void add(int i)
    		{
    			if(lock.tryLock()){
    			int sum=0;
    			try {
    				System.out.println("当前线程:"+Thread.currentThread().getName()+"得到了锁");
    				for(int j=0;j<i;j++)
    				{
    					sum+=j;
    				}
    				
    			} catch (Exception e) {
    				// TODO: handle exception
    			}
    			finally {
    				lock.unlock();
    				System.out.println("当前线程:"+Thread.currentThread().getName()+"释放锁");
    			}
    			hashSet.add(sum);
    			}
    			else {
    				System.out.println("当前线程:"+Thread.currentThread().getName()+"获取锁失败");
    			}
    		}
    	}
    
    }
    
    

    运行结果:

    当前线程:Thread-0得到了锁
    当前线程:Thread-1获取锁失败
    当前线程:Thread-1结束
    当前线程:Thread-0释放锁
    当前线程:Thread-0结束
    

    发现:Thread-1获取锁失败,但是它不阻塞,从结果我们可以发现:当前线程:Thread-1结束 打印出来就表示线程没有阻塞。

    lockInterruptibly()

    package com.csu.thread;
    
    import java.util.HashSet;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ReenteantLockTest {
    	//static Lock lock=new ReentrantLock();
    	private static HashSet<Integer> hashSet=new HashSet<>();
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Sum sum=new Sum();
    		Thread t1=new Thread(new Runnable() {
    			public void run() {
    				try {
    					sum.add(1000000000);
    					System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
    				} catch (InterruptedException e) {
    		            System.out.println(Thread.currentThread().getName()+"被中断");
    		        }
    				
    			}
    		});
    		Thread t2=new Thread(new Runnable() {
    			public void run() {
    				try {
    					sum.add(20000);
    					System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					System.out.println(Thread.currentThread().getName()+"被中断");
    				}
    				
    			}
    		});
    		t1.start();
    		t2.start();
    		t2.interrupt();
    		
    
    	}
    	
    	public static class Sum {
    		private Lock lock=new ReentrantLock();
    		
    		@SuppressWarnings("static-access")
    		public  void add(int i) throws InterruptedException
    		{
    			lock.lockInterruptibly();
    			int sum=0;
    			try {
    				System.out.println("当前线程:"+Thread.currentThread().getName()+"得到了锁");
    				for(int j=0;j<i;j++)
    				{
    					sum+=j;
    				}
    				Thread.currentThread().sleep(10000);
    			} catch (Exception e) {
    				// TODO: handle exception
    			}
    			finally {
    				lock.unlock();
    				System.out.println("当前线程:"+Thread.currentThread().getName()+"释放锁");
    			}
    			hashSet.add(sum);
    			
    		}
    	}	
    }
    
    

    运行结果:

    当前线程:Thread-0得到了锁
    Thread-1被中断
    当前线程:Thread-0释放锁
    当前线程:Thread-0结束
    

    是的Thread-1被成功的中断了。
    当我们注释掉这段代码运行发现:t2.interrupt();

    当前线程:Thread-0得到了锁
    当前线程:Thread-0释放锁
    当前线程:Thread-1得到了锁
    当前线程:Thread-0结束
    当前线程:Thread-1释放锁
    当前线程:Thread-1结束
    

    通过结果发现,如果我们不中断等待线程,则lockInterruptibly是阻塞的,它会等待获取锁。

    4:ReadWriteLock

    • ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
    • 所有 ReadWriteLock 实现都必须保证 writeLock 操作的内存同步效果也要保持与相关 readLock 的联系。也就是说,成功获取读锁的线程会看到写入锁之前版本所做的所有更新。
    • 读-写锁允许对共享数据进行更高级别的并发访问。
    • 尽管读-写锁的基本操作是直截了当的,但实现仍然必须作出许多决策,这些决策可能会影响给定应用程序中读-写锁的效果。这些策略的例子包括:
      1:在 writer 释放写入锁时,reader 和 writer 都处于等待状态,在这时要确定是授予读取锁还是授予写入锁。Writer 优先比较普遍,因为预期写入所需的时间较短并且不那么频。
      2:在 reader 处于活动状态而 writer 处于等待状态时,确定是否向请求读取锁的 reader 授予读取锁。Reader 优先会无限期地延迟 writer,而 writer 优先会减少可能的并发。
      3:确定是否重新进入锁:可以使用带有写入锁的线程重新获取它吗?可以在保持写入锁的同时获取读取锁吗?可以重新进入写入锁本身吗?
      4:可以将写入锁在不允许其他 writer 干涉的情况下降级为读取锁吗?可以优先于其他等待的 reader 或 writer 将读取锁升级为写入锁吗?

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

    public interface ReadWriteLock {
        /**
         * Returns the lock used for reading.
         *
         * @return the lock used for reading.
         */
        Lock readLock();
     
        /**
         * Returns the lock used for writing.
         *
         * @return the lock used for writing.
         */
        Lock writeLock();
    }
    

    一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的ReentrantReadWriteLock实现了ReadWriteLock接口。
    5:ReentrantReadWriteLock

    • 此类不会将读取者优先或写入者优先强加给锁访问的排序。但是,它确实支持可选的公平 策略。
    • 此锁允许 reader 和 writer 按照 ReentrantLock 的样式重新获取读取锁或写入锁。在写入线程保持的所有写入锁都已经释放后,才允许重入 reader 使用它们。
    • writer 可以获取读取锁,但反过来则不成立。
    • 重入还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不可能的。
    • 读取锁和写入锁都支持锁获取期间的中断。
    • 写入锁提供了一个 Condition 实现,对于写入锁来说,该实现的行为与 ReentrantLock.newCondition() 提供的 Condition 实现对 ReentrantLock 所做的行为相同。当然,此 Condition 只能用于写入锁。
    • 读取锁不支持 Condition,readLock().newCondition() 会抛出 UnsupportedOperationException。
      实例1:下面的代码展示了如何利用重入来执行升级缓存后的锁降级
    class CachedData {
       Object data;
       volatile boolean cacheValid;
       ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
       void processCachedData() {
         rwl.readLock().lock();
         if (!cacheValid) {
            // Must release read lock before acquiring write lock
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            // Recheck state because another thread might have acquired
            //   write lock and changed state before we did.
            if (!cacheValid) {
              data = ...
              cacheValid = true;
            }
            // Downgrade by acquiring read lock before releasing write lock
            rwl.readLock().lock();
            rwl.writeLock().unlock(); // Unlock write, still hold read
         }
    
         use(data);
         rwl.readLock().unlock();
       }
     }
    

    实例二:在使用某些种类的 Collection 时,可以使用 ReentrantReadWriteLock 来提高并发性。

    class RWDictionary {
        private final Map<String, Data> m = new TreeMap<String, Data>();
        private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        private final Lock r = rwl.readLock();
        private final Lock w = rwl.writeLock();
    
        public Data get(String key) {
            r.lock();
            try { return m.get(key); }
            finally { r.unlock(); }
        }
        public String[] allKeys() {
            r.lock();
            try { return m.keySet().toArray(); }
            finally { r.unlock(); }
        }
        public Data put(String key, Data value) {
            w.lock();
            try { return m.put(key, value); }
            finally { w.unlock(); }
        }
        public void clear() {
            w.lock();
            try { m.clear(); }
            finally { w.unlock(); }
        }
     }
    

    通常,在预期 collection 很大,读取者线程访问它的次数多于写入者线程,并且 entail 操作的开销高于同步开销时,这很值得一试。
    实例三:大量读取锁
    1:用同步关键字

    public class ReadWriterLockTest {
    
    	public static void main(String[] args)  {
            final ReadWriterLockTest test = new ReadWriterLockTest();
             
            new Thread(){
                public void run() {
                    test.get(Thread.currentThread());
                };
            }.start();
             
            new Thread(){
                public void run() {
                    test.get(Thread.currentThread());
                };
            }.start();
             
        }  
         
        public synchronized void get(Thread thread) {
            long start = System.currentTimeMillis();
            while(System.currentTimeMillis() - start <= 1) {
                System.out.println(thread.getName()+"正在进行读操作");
            }
            System.out.println(thread.getName()+"读操作完毕");
        }
    
    }
    
    
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0读操作完毕
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1读操作完毕
    

    我们发现,直到thread1执行完读操作之后,才会打印thread2执行读操作的信息。
    2:改为ReentrantReadWriteLock

    package com.csu.thread;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class ReadWriterLockTest {
    	private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
    	public static void main(String[] args)  {
            final ReadWriterLockTest test = new ReadWriterLockTest();
             
            new Thread(){
                public void run() {
                    test.get(Thread.currentThread());
                };
            }.start();
             
            new Thread(){
                public void run() {
                    test.get(Thread.currentThread());
                };
            }.start();
             
        }  
         
        public void get(Thread thread) {
        	rwl.readLock().lock();
        	try {
        		 long start = System.currentTimeMillis();
        	        while(System.currentTimeMillis() - start <= 1) {
        	            System.out.println(thread.getName()+"正在进行读操作");
        	        }
        	        System.out.println(thread.getName()+"读操作完毕");
    			
    		} catch (Exception e) {
    			// TODO: handle exception
    		}finally
        	{
    			rwl.readLock().unlock();
        	}
            
        }
    
    }
    

    运行结果:

    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-0正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-0读操作完毕
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1正在进行读操作
    Thread-1读操作完毕
    

    说明thread1和thread2在同时进行读操作。这样就大大提升了读操作的效率。不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
    如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

    6.Lock和synchronized的选择
    总结来说,Lock和synchronized有以下几点不同:
      1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
      2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
      3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
      4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
      5)Lock可以提高多个线程进行读操作的效率。
      在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
      
    7:锁的相关概念介绍
    1.可重入锁

      如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
      

    class MyClass {
        public synchronized void method1() {
            method2();
        }
         
        public synchronized void method2() {
             
        }
    }
    

    上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。

      而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。
      
    2.可中断锁

      可中断锁:顾名思义,就是可以相应中断的锁。
      在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
      如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
      在前面演示lockInterruptibly()的用法时已经体现了Lock的可中断性。
      
    3.公平锁

     公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
    

       非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
      在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
      而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
      
    4.读写锁

      读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
      正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
      ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
      可以通过readLock()获取读锁,通过writeLock()获取写锁。
      上面已经演示过了读写锁的使用方法,在此不再赘述。
      

    引用块内容
    http://www.cnblogs.com/dolphin0520/p/3923167.html
    java核心技术 卷I

  • 相关阅读:
    Codeforces Round #624 (Div. 3) D. Three Integers
    Codeforces Round #624 (Div. 3) C. Perform the Combo(前缀和)
    Codeforces Round #624 (Div. 3) B. WeirdSort(排序)
    Codeforces Round #624 (Div. 3) A. Add Odd or Subtract Even(水题)
    洛谷P1130红牌(简单DP)
    洛谷P1044栈(DP)
    Codeforces 1315C Restoring Permutation
    MATLAB字符串处理
    MATLAB矩阵操作和算术运算符
    MATLAB基本概念和变量
  • 原文地址:https://www.cnblogs.com/csuwater/p/5410148.html
Copyright © 2011-2022 走看看