Java线程同步-synchronized与lock

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_29078329/article/details/78940788

一、线程同步的概念、为什么需要线程同步

        同步的概念是在发出一个功能调用后,在没有得到结果之前,该调用就不返回,也就是事情要一件一件做,等前一件做完了才能做下一件事。线程同步指的是同一时刻只有一个线程能进入临界区(访问共享数据的代码块),当前线程执行完成,并且释放了对象锁,其他等待进入临界区的线程才能执行。

        与同步相对的概念是异步,异步是指在发出一个功能调用后,被调用的对象不能立刻返回结果,在没有得到返回结果之前,调用者还可以执行别的操作,被调用者执行完成后,通过状态、通知和回调来通知调用者。异步线程指的是,当程序要执行一个比较耗时的任务时(IO操作、网络请求),程序会开启一个子线程执行这个耗时的任务,主线程继续执行其他的操作,等子线程执行完成后,再通知主线程,异步操作能提高程序的效率。

那么为什么会出现线程同步呢?请看之前总结的帖子:http://blog.csdn.net/qq_29078329/article/details/73611110


二、线程同步-synchronized

synchronized是Java中的一个关键字,用于实现线程同步,synchronized常用来:

1、修饰代码块,synchronized(this) {    },作用的范围是{  }括起来的代码,作用的对象是当前对象。一个线程在访问当前对象的synchronized代码块时,其他线程会被阻塞。

public class SyncThread implements Runnable {
   private  int count;

   public  void run() {
      synchronized(this) {
         for (int i = 0; i < 5; i++) {
              System.out.println(Thread.currentThread().getName() + ":" + (count++));
         }
      }
   }

   public int getCount() {
      return count;
   }
}

调用:

public static void main(String[] args) {
	SyncThread syncThread = new SyncThread();
	new Thread(syncThread,"thread1").start();
	new Thread(syncThread,"thread2").start();
}  
结果:

thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9

两个线程thread1和thread2同时访问同一个对象(syncThread)的synchronized修饰的代码块,同一时刻只能有一个线程进入,另一个线程受阻塞,被阻塞的线程必须等待当前线程执行完synchronized代码块以后才能执行该代码块。我们把调用代码改成如下形式,再测试一下:

public static void main(String[] args) {
	new Thread(new SyncThread(),"thread1").start();
	new Thread(new SyncThread(),"thread2").start();
}
结果:

thread2:0
thread1:0
thread2:1
thread1:1
thread2:2
thread1:2
thread2:3
thread1:3
thread2:4
thread1:4

        这时创建了两个SyncThread对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized(this) {  },而线程thread2执行的是syncThread2对象中的synchronized(this) {   };我们知道synchronized锁定的是对象,这时会有两把锁分别锁定syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。

        此外,当一个线程访问对象的synchronized代码块时,另一个线程仍然可以访问该对象中的非synchronized代码块,如下:

public class SyncThread implements Runnable {
   private  int count;

   public void countAdd() {
	  synchronized(this) {
	         for (int i = 0; i < 5; i ++) {
	            try {
	               System.out.println(Thread.currentThread().getName() + ":countAdd:" + (count++));
	               Thread.sleep(100);
	            } catch (Exception e) {
	               e.printStackTrace();
	            }
	         }
	   }
	}
   public void printCount() {
	      for (int i = 0; i < 5; i ++) {
	         try {
	            System.out.println(Thread.currentThread().getName() + ":printCount:" + count);
	            Thread.sleep(100);
	         } catch (Exception e) {
	            e.printStackTrace();
	         }
	      }
   }

    public void run() {
	      String threadName = Thread.currentThread().getName();
	      if (threadName.equals("thread1")) {
	         countAdd();
	      } else if (threadName.equals("thread2")) {
	         printCount();
	      }
    }
}

调用:

public static void main(String[] args) {
	SyncThread syncThread = new SyncThread();
	new Thread(syncThread,"thread1").start();
	new Thread(syncThread,"thread2").start();
} 

结果:

thread1:countAdd:0
thread2:printCount:1
thread1:countAdd:1
thread2:printCount:2
thread2:printCount:2
thread1:countAdd:2
thread1:countAdd:3
thread2:printCount:4
thread2:printCount:4
thread1:countAdd:4

由测试结果可以看出,一个线程在访问对象的synchronized代码时,其他线程可以同时访问该对象的非同步代码。


2、synchronized修饰成员方法,public synchronized void method(){  },这种情况和修饰代码块类似,只是修饰范围是整个方法,作用的对象依然是当前对象。上面的例子可以改为下面的代码,效果是一样的:

public synchronized void countAdd() {
	   for (int i = 0; i < 5; i ++) {
	        try {
	                System.out.println(Thread.currentThread().getName() + ":countAdd:" + (count++));
	                Thread.sleep(100);
	            } catch (Exception e) {
	                e.printStackTrace();
	            }
	   }
}
也就是public synchronized void method(){  }与public void method(){synchronized(this) {  }  }作用是一样的。
 同步方法时,synchronized关键字被不能继承,如果父类中的方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,必须显式地在子类的这个方法中加上synchronized关键字才可以实现子类的相应方法是同步的,或者在子类方法中用super关键字调用父类同步的方法,子类的方法也就相当于同步了。

class Parent {
   public synchronized void method() { }
}
class Child extends Parent {
   public synchronized void method() { }
}

class Parent {
   public synchronized void method() {   }
}
class Child extends Parent {
   public void method() { super.method();   }
} 


3、Synchronized修饰静态方法,public synchronized static void method() {  },由于静态方法是属于类的,所有synchronized修饰的静态方法锁定的是该类的所有对象。

public class SyncThread implements Runnable {
	private static int count;

	public synchronized static void method() {
	      for (int i = 0; i < 5; i ++) {
	         try {
	            System.out.println(Thread.currentThread().getName() + ":" + (count++));
	            Thread.sleep(100);
	         } catch (InterruptedException e) {
	            e.printStackTrace();
	         }
	      }
	}

	public  void run() {
	    method();
	}
}

调用:

public static void main(String[] args) {
	new Thread(new SyncThread(),"thread1").start();
	new Thread(new SyncThread(),"thread2").start();
} 

结果:

thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9

syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁,当thread1进入method后,thread2再进入method时会被阻塞。


4、synchronized作用于类,synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁,形式如下:

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         
      }
   }
}

将3中的例子改写,将synchronized修饰静态方法改成修饰类,效果是一样的:

public class SyncThread implements Runnable {
	private static int count;

	public void method() {
		synchronized(SyncThread.class) {
			for (int i = 0; i < 5; i ++) {
		         try {
		            System.out.println(Thread.currentThread().getName() + ":" + (count++));
		            Thread.sleep(100);
		         } catch (InterruptedException e) {
		            e.printStackTrace();
		         }
		      }
		}
	      
	}

	public  void run() {
	    method();
	}
}

调用:

public static void main(String[] args) {
	new Thread(new SyncThread(),"thread1").start();
	new Thread(new SyncThread(),"thread2").start();
} 

结果:

thread1:0
thread1:1
thread1:2
thread1:3
thread1:4
thread2:5
thread2:6
thread2:7
thread2:8
thread2:9

synchronized修饰一个类,这种情况是给这个类加锁,加锁的类的所有对象用的是同一把锁。一个线程进入了加锁类任意一个实例的synchronized修饰的代码,其他线程就会阻塞,不能同时进入这个类其他实例的这段同步的代码。


5、synchronized修饰一个对象,这种情况是给这个对象加锁,一个线程拿到这个对象的锁之后,就可以访问这个对象的代码,其他的线程等待,当前线程释放对象的锁之后,其他线程才能获得这个对象的锁。

/**
 * 银行账户类
 */
class Account {
   String name;
   float amount;

   public Account(String name, float amount) {
      this.name = name;
      this.amount = amount;
   }
   //存钱
   public  void deposit(float amt) {
      amount += amt;
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   //取钱
   public  void withdraw(float amt) {
      amount -= amt;
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

   public float getBalance() {
      return amount;
   }
}

/**
 * 账户操作类
 */
class AccountOperator implements Runnable{
   private Account account;
   public AccountOperator(Account account) {
      this.account = account;
   }

   public void run() {
      synchronized (account) {
         account.deposit(500);
         account.withdraw(500);
         System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
      }
   }
}




调用:

Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);

final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
   threads[i] = new Thread(accountOperator, "Thread" + i);
   threads[i].start();
}

结果:

Thread3:10000.0 
Thread2:10000.0 
Thread1:10000.0 
Thread4:10000.0 
Thread0:10000.0

         在AccountOperator 类中的run方法里,我们用synchronized 给account对象加了锁。当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束,也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。

        下面的例子,两个线程会同时执行,thread1拿到的是Object对象的锁,thread2拿到的是当前对象的锁,这是两把不同的锁,两把锁是互不干扰的,不形成互斥,所以两个线程会同时执行。

public class Main {
	
	Object obj = new Object();
	
	public void method1() {
		synchronized(obj) {
			for(int i=0;i<5;i++) {
				System.out.println("method1:"+i);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
	}
	
	public void method2() {
		synchronized(this) {
			for(int i=0;i<5;i++) {
				System.out.println("method2:"+i);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			
		}
	}
	public static void main(String[] args) {
		final Main mainClass = new Main();
		new Thread(new Runnable() {
			public void run() {
				mainClass.method1();
			}
		}).start();
		new Thread(new Runnable() {
			public void run() {
				mainClass.method2();
			}
		}).start();
    }  
	
	
}

结果:

method2:0
method1:0
method1:1
method2:1
method2:2
method1:2
method2:3
method1:3
method2:4
method1:4

总结:

(1)synchronized作用于代码块、成员方法时取得的是当前对象的锁;synchronized作用于某个对象时,取得的是这个对象的锁;synchronized作用于静态方法、类时,取得的是这个类的锁,这个类的所有对象共用同一把锁。  
(2)实现同步需要很大的系统开销,甚至可能造成死锁,应该尽量避免无谓的同步控制。

三、线程同步-lock

首先我们来说一说为什么会出现Lock接口,之所以出现Lock接口,是因为synchronized同步方式存在某些不足。

1、如果一个代码块被synchronized修饰,当一个线程获取了对应的锁,其他线程只能一直等待获取锁的线程释放锁,而获取锁的线程释放锁只会有两种情况:

1)获取锁的线程执行完了代码块,然后线程释放对锁的占有

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

    如果获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便就只能一直等待下去,这样会影响程序执行效率,因此就需要有一种机制可以不让等待的线程无期限地等待下去,、Lock接口可以实现,比如让线程只等待一定的时间或者让等待的线程能够响应中断。

2、当有多个线程读写文件时,读操作和写操作会发生冲突,写操作和写操作会发生冲突,但是读操作和读操作不会发生冲突。但是采用synchronized来实现同步的话,就会导致一个问题,如果多个线程都只是进行读操作,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。因此就需要一种机制来使得多个线程可以同时进行读操作,通过ReadWriteLock接口就可以办到。

3、通过Lock接口可以知道线程有没有成功获取到锁,这个是synchronized无法办到的。

下面我们就来分析一下java.util.concurrent.locks包中常用的类和接口。

1、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()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用来获取锁的,unLock()方法是用来释放锁的。采用Lock接口同步线程,在发生异常时,JVM不会自动释放锁,必须由程序员主动去释放锁,因此使用Lock接口必须在try{ }catch{ }块中进行,并且将释放锁的操作放在finally{ }块中进行,以保证锁一定被被释放,防止死锁的发生。


1)lock()方法用来获取锁,如果锁已被其他线程获取,则进行等待,通常使用lock()方法来进行同步的话,是以下面这种形式去使用的:

Lock lock = ...;
lock.lock();
try{
    //处理任务
}catch(Exception ex){
     
}finally{
    lock.unlock();   //释放锁
}
2)tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回,在拿不到锁时不会一直等待。tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

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

3)当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只能等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。因此当通过lockInterruptibly()方法获取某个锁时,只有等待锁的线程是可以响应中断的,线程获取了锁之后,是不会被interrupt()方法中断的,而用synchronized修饰的话,当线程等待锁时是无法被中断的,只能一直等待下去。由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。

public void method() throws InterruptedException {
    lock.lockInterruptibly();
    try {  
     //.....
    }
    finally {
        lock.unlock();
    }  
}

2、ReentrantLock

ReentrantLock(可重入锁)是唯一实现了Lock接口的类,lock()方法使用如下:

import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
	private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意这个地方,lock声明为成员变量
    public static void main(String[] args)  {
        final Main test = new Main();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  
     
    public void insert(Thread thread) {
        lock.lock();
        try {
            System.out.println(thread.getName()+"得到了锁");
            for(int i=0;i<5;i++) {
                arrayList.add(i);
            }
        } catch (Exception e) {
            // TODO: handle exception
        }finally {
            System.out.println(thread.getName()+"释放了锁");
            lock.unlock();
        }
    }
}

结果:

Thread-0得到了锁
Thread-0释放了锁
Thread-1得到了锁
Thread-1释放了锁

tryLock()方法使用如下:

import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
	private ArrayList<Integer> arrayList = new ArrayList<Integer>();
    private Lock lock = new ReentrantLock();    //注意这个地方,lock声明为成员变量
    public static void main(String[] args)  {
        final Main test = new Main();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
         
        new Thread(){
            public void run() {
                test.insert(Thread.currentThread());
            };
        }.start();
    }  
     
    public void insert(Thread thread) {
        if(lock.tryLock()) {
            try {
                System.out.println(thread.getName()+"得到了锁");
                for(int i=0;i<5;i++) {
                    arrayList.add(i);
                }
            } catch (Exception e) {
                // TODO: handle exception
            }finally {
                System.out.println(thread.getName()+"释放了锁");
                lock.unlock();
            }
        } else {
            System.out.println(thread.getName()+"获取锁失败");
        }
    }
}

结果:

Thread-0得到了锁
Thread-1获取锁失败
Thread-0释放了锁

lockInterruptibly()使用如下:

import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
    private Lock lock = new ReentrantLock();   
    public static void main(String[] args)  {
    	Main main = new Main();
        MyThread thread1 = new MyThread(main);
        MyThread thread2 = new MyThread(main);
        thread1.start();
        thread2.start();
        thread2.interrupt();
    }  
     
    public void insert(Thread thread) throws InterruptedException{
        lock.lockInterruptibly();  
        try {  
            System.out.println(thread.getName()+"得到了锁");
        }catch(Exception e) {
        	e.printStackTrace();
        }
        finally {
            System.out.println(Thread.currentThread().getName()+"执行finally");
            lock.unlock();
            System.out.println(thread.getName()+"释放了锁");
        }  
    }
}
 
class MyThread extends Thread {
    private Main test = null;
    public MyThread(Main test) {
        this.test = test;
    }
    @Override
    public void run() {
         
        try {
            test.insert(Thread.currentThread());
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+"被中断");
        }
    }
}

结果:

Thread-1被中断
Thread-0得到了锁
Thread-0执行finally
Thread-0释放了锁

Thread-0、Thread-1同时获取锁,但是Thread-0获取到了,接着执行任务,finally中释放锁;Thread-1未获取到锁,进入等待状态,随后被中断。


3、ReadWriteLock

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接口。


4、ReentrantReadWriteLock

假如有多个线程要同时进行读操作的话,先看一下synchronized达到的效果:

import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Main {
	private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
    public static void main(String[] args)  {
        final Main test = new Main();
         
        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) {
        for(int i=0;i<5;i++) {
        	System.out.println(thread.getName()+"正在进行读操作");
        }
        System.out.println(thread.getName()+"读操作完毕");
    }
}

结果:

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

两个线程同时调用一个对象的synchronized方法,只能有一个线程拿到对象锁,另一个线程被阻塞。改成读写锁的话:

import java.util.ArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Main {
	private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
    public static void main(String[] args)  {
        final Main test = new Main();
         
        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 {
        	for(int i=0;i<5;i++) {
        		Thread.currentThread().sleep(10);
        		System.out.println(thread.getName()+"正在进行读操作");
        	}
            System.out.println(thread.getName()+"读操作完毕");
        } catch(Exception e) {
        	
        }finally {
        	System.out.println(thread.getName()+"释放了锁");
            rwl.readLock().unlock();
        }
    }
}
结果:

Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-1正在进行读操作
Thread-0正在进行读操作
Thread-0正在进行读操作
Thread-0读操作完毕
Thread-0释放了锁
Thread-1正在进行读操作
Thread-1读操作完毕
Thread-1释放了锁

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


四、synchronized、lock比较

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

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

3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

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

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


五、锁的相关概念

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、可中断锁

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

3、公平锁

公平锁即尽量以请求锁的顺序来获取锁,比如有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这种就是公平锁。非公平锁无法保证锁的获取是按照请求锁的顺序进行的,这样可能会导致某些线程永远获取不到锁。synchronized是非公平锁,它无法保证等待的线程获取锁的顺序;ReentrantLock和ReentrantReadWriteLock默认情况下是非公平锁,但是可以设置为公平锁。





猜你喜欢

转载自blog.csdn.net/qq_29078329/article/details/78940788