java多线程-Lock

1.synchronize缺陷

      代码块被synchronize修饰时,当一个线程获取了对应的线程锁并执行该代码块时,其他线程只能一直等待,直到获取锁的线程释放锁,而这里得到锁的线程释放锁只有两种情况:

    (1)得到锁的线程执行完了该代码块,然后线程释放线程锁

    (2)线程执行发生异常,此时JVM会让线程自动释放锁

     如果这个得到锁的线程由于要等待IO或者其他原因(如调用了sleep方法)被阻塞了,但是又没有释放锁,其他线程只能干巴巴等待,这非常影响程序执行的效率。因此,就需要有一种机制可以不让等待的线程一直无限期等待下去(比如只等待一段时间或者能够响应中断),通过Lock就可以实现

     再有一个例子:

            当多个线程读写文件时,读写、写写都会发生冲突,但是读读操作不会发生冲突。但是如果采用synchronize关键字来实现同步的话,就会导致一个问题:如果多个线程只是进行读操作,当一个线程正在进行读操作时,其他线程只能等待而无法进行读操作。因此,就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,Lock就可办到。另外,通过Lock可以知道有没有成功获取到锁。这个是synchronize无法办到的。

     

2.synchronize与Lock的区别

      (1)synchronize是Java的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问

      (2)synchronize不需要用户手动去释放锁:当synchronize方法或代码块执行完后,系统会让线程自动释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放,则会造成死锁现象


3.锁分类

(1)可重入锁

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

        看看如下代码:

class MyClass
{
    public synchronized void method1() 
    {
        method2();
    }
     
    public synchronized void method2() 
    {
         
    }
}
       上述代码中的两个方法都是用了synchronized,加入某一止咳,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,加入synchronized不具有可重入性,此时线程A需要重新申请锁。但是这样就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请该对象的锁,这样线程A会直等待而永远不会得到申请的锁。

      由于synchronized和Lock都具备可重入性,所以不会发生上述现象


(2)可中断锁

        如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,B不想再等了,想先去处理其他事情,我们可以让他中断自己或者在别的线程中中断它,这种就是可中断锁。

        synchronized是不可中断锁,Lock是可中断锁

(3)公平锁

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

        synchronized是非公平锁,也就是说无法保证锁的获取是按照请求锁的顺序进行的,这可能导致一些线程永远获取不到锁。

        ReentrantLock和ReentrantReadWriteLock,默认是非公平锁,但是可以设置为公平锁。

        在ReentrantLock中定义了公平和非公平锁两个类

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

 static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
        我们在创建ReentrantLock对象时,可以通过以下方式设置锁的公平性

ReentrantLock lock = new ReentrantLock(true);
         true为公平锁,false为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。

    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
       另外,在ReentrantLock中定义了很多方法:

    public final boolean isFair() {             //用来判断是否是公平锁
        return sync instanceof FairSync;
    }

    public boolean isLocked() {                //用来判断锁是否已经被获取了
        return sync.isLocked();
    }
      
    public final boolean hasQueuedThreads() {   //判断是否有线程在等待该锁
        return sync.hasQueuedThreads();
    }
      ReentrantReadWriteLock也有类似的方法,同样也可以设置为公平锁和非公平锁。注意,ReentrantReadWriteLock并没有实现Lock接口,实现的是ReadWriteLock

(4)读写锁

        读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。正因为有了读写所,才使得多个线程之间的读操作不会发生冲突

3.Lock

      通过代码查看,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,lockInterruptibly,tryLock都是用来获取锁的,unLock是用来释放锁的

       (1)lock()

               使用最多的方法,就是为了获取锁,如果锁已经被其他线程获取,则进行等待

               如果采用Lock,则必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此,一般来说,使用lock必须在try、catch中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被释放,防止死锁的发生。

Lock lock = ...;
lock.lock();
try
{
    //处理任务
}
catch(Exception ex)
{
     
}
finally
{
    lock.unlock();   //释放锁
}

       (2)tryLock()

               有返回值,用来表示是否获得了锁,如果返回true,则说明获取成功,如果获取失败(锁已经被其他线程获取),则返回false。也就是说,这个方法无论如何都会立即返回,拿不到不会一直等

Lock lock = ...;
if(lock.tryLock())
{
     try
     {
         //处理任务
     }
     catch(Exception ex)
     {
         
     }
     finally
     {
         lock.unlock();   //释放锁
     } 
}
else 
{
    //如果不能获取锁,则直接做其他事情
}

       (3)tryLock(long time, TimeUnit unit)

               和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到或者在等待时间内拿到了锁,就返回false


       (4)lockInterruptibly()

               当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断。其实也就是说两个线程A、B同时通过lock.lockInterruptibly()想获取某个锁时,假如A获取到了锁,而B在等待,那么对B调用threadB.interrput()方法能够中断B线程的等待过程

public void method() throws InterruptedException 
{
    lock.lockInterruptibly();
    try 
    {  
     //.....
    }
    finally 
    {
        lock.unlock();
    }  
}
            注意: 当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为单独调用interrupt()方法只能中断阻塞中的线程,而不能中断正在运行的线程。 因此当通过lockInterruptibly()获取线程锁时,如果不能获取到,只有在进行等待的状态下,是可以响应中断的。

           而用synchronize修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只能一直等待下去。


4.ReentrantLock

      意思是可重入锁。ReentrantLock是唯一实现了Lock接口的类。

      (1)使用lock()获取锁

public class TestLock 
{
	List<Integer> list = new ArrayList<Integer>();
	public static void main(String[] args) 
	{
		final TestLock test = new TestLock();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.insert(Thread.currentThread());
			}
		}).start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.insert(Thread.currentThread());
			}
		}).start();
	}

	protected void insert(Thread currentThread) 
	{
		Lock lock = new ReentrantLock();
		lock.lock();
		
		try
		{
			System.out.println(currentThread.getName()+" get lock");
			for(int i=0; i<5;i++)
				list.add(i);
		}
		catch(Exception e)
		{}
		finally
		{
			System.out.println(currentThread.getName()+" release lock");
			lock.unlock();
		}
	}
}
         结果为:

Thread-0 get lock
Thread-1 get lock
Thread-1 release lock
Thread-0 release lock
        为何是这样呢?因为各个线程使用的是自己的Lock,那么肯定就起不到同步的作用了,所以将lock改为成员变量即可

public class TestLock 
{
	List<Integer> list = new ArrayList<Integer>();
	Lock lock = new ReentrantLock();
	public static void main(String[] args) 
	{
		final TestLock test = new TestLock();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.insert(Thread.currentThread());
			}
		}).start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.insert(Thread.currentThread());
			}
		}).start();
	}

	protected void insert(Thread currentThread) 
	{
		lock.lock();
		
		try
		{
			System.out.println(currentThread.getName()+" get lock");
			for(int i=0; i<5;i++)
				list.add(i);
		}
		catch(Exception e)
		{}
		finally
		{
			System.out.println(currentThread.getName()+" release lock");
			lock.unlock();
		}
	}
}
           结果为:

Thread-0 get lock
Thread-0 release lock
Thread-1 get lock
Thread-1 release lock

      (2)使用tryLock()获取锁
public class TestLock 
{
	static List<Integer> list = new ArrayList<Integer>();
	Lock lock = new ReentrantLock();
	public static void main(String[] args) 
	{
		final TestLock test = new TestLock();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.insert(Thread.currentThread());
			}
		}).start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.insert(Thread.currentThread());
			}
		}).start();
	}

	protected void insert(Thread currentThread) 
	{
		if(lock.tryLock())
		{
			try
			{
				System.out.println(currentThread.getName()+" get lock");
				for(int i=0; i<5;i++)
					list.add(i);
			}
			catch(Exception e)
			{}
			finally
			{
				System.out.println(currentThread.getName()+" release lock");
				lock.unlock();
			}
		}
		else
		{
			System.out.println(currentThread.getName()+" can get lock");
		}
	}
}
        结果为:

Thread-0 get lock
Thread-0 release lock
Thread-1 get lock
Thread-1 release lock

      (3)使用lockInterruptibly()获取锁
public class TestLock 
{
	static List<Integer> list = new ArrayList<Integer>();
	Lock lock = new ReentrantLock();
	public static void main(String[] args) 
	{
		final TestLock test = new TestLock();
		
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.insert(Thread.currentThread());
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.insert(Thread.currentThread());
			}
		});
		
		t1.start();
		t2.start();
		
		try 
		{
			Thread.sleep(20);
		}
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
		t2.interrupt();
	}

	protected void insert(Thread currentThread) 
    {
		try 
		{
			lock.lockInterruptibly();
			System.out.println(currentThread.getName() + " get lock");
			for (int i = 0; i < 5; i++)
				list.add(i);
		} 
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
		finally 
		{
			System.out.println(currentThread.getName() + " release lock");
			lock.unlock();
		}
	}
}
         也就是说在20ms之后还没有得到锁,那么就interrupt线程。



5.ReadWriteLock

    ReadWriteLock也是一个接口,其中只定义了2个方法:

public interface ReadWriteLock {
    /**
     * @return the lock used for reading.
     */
    Lock readLock();
 
    /**
     * @return the lock used for writing.
     */
    Lock writeLock();
}
         一个用来获取读锁,一个用来获取写锁。也就是说,将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。

         ReentrantReadWriteLock是ReadWriteLock接口的一个实现类。加入有多个线程要同时进行读操作的话,先看一下synchronized达到的效果。

public class TestLock 
{
	public static void main(String[] args) 
	{
		final TestLock test = new TestLock();
		
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.get(Thread.currentThread());
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.get(Thread.currentThread());
			}
		});
		
		t1.start();
		t2.start();
		
		try 
		{
			Thread.sleep(20);
		}
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
		t2.interrupt();
	}

	synchronized protected void get(Thread currentThread) 
	{
		long start = System.currentTimeMillis();
		while(System.currentTimeMillis()-start<=1)
			System.out.println(currentThread.getName()+"正在进行读操作");
		
		System.out.println(currentThread.getName()+"完成读操作");
	}
}
              结果为:

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完成读操作
             也就是说,知道thread1读操作完成之后,才会执行thread2的读操作

             下面使用读写锁:

public class TestLock 
{
	ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
	
	public static void main(String[] args) 
	{
		final TestLock test = new TestLock();
		
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.get(Thread.currentThread());
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				test.get(Thread.currentThread());
			}
		});
		
		t1.start();
		t2.start();
		
		try 
		{
			Thread.sleep(20);
		}
		catch (InterruptedException e) 
		{
			e.printStackTrace();
		}
		t2.interrupt();
	}

	protected void get(Thread currentThread) 
	{
		rwl.readLock().lock();
		
		try
		{
			long start = System.currentTimeMillis();
			while(System.currentTimeMillis()-start<=1)
				System.out.println(currentThread.getName()+"正在进行读操作");
			
			System.out.println(currentThread.getName()+"完成读操作");
		}
		catch(Exception e)
		{
			
		}
		finally
		{
			rwl.readLock().unlock();
		}
		
	}
}
            结果为:

Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
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-0正在进行读操作
Thread-0完成读操作
            说明两个线程可以同时进行读操作,这样就大大提高了读操作的效率。

            注意:

            (1)如果有一个线程已经占用了读锁,此时如果其他线程要申请写锁,则申请写锁的线程会一直等待释放读锁
            (2)如果一个线程已经占用了写锁,此时其他线程如果申请读锁或者写锁,则申请的线程会一直等待释放写锁


6.Lock与synchronized区别

    (1)Lock是一个接口,而synchronized是java中的关键字

    (2)synchronized发生异常时,会自动释放线程中占有的锁,因此不会导致线程死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能会造成死锁现象,因此使用使用Lock需要在finally中释放锁
    (3)Lock可以让等待锁的线程中断,而synchronized不能,使用synchronized时,等待的线程会一直等待下去,不能够响应中断

    (4)通过Lock可以知道有没有成功获得锁,而synchronized无法办到

    (5)Lock可以提高多个线程进行读操作的效率

            从性能上来说,如果资源竞争不激烈,两者的性能差不多,但是当有大量线程同时竞争时,此时Lock的性能要远远优于synchronized
















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值