java多线程系列六之“锁”类多样

.1.synchronized--隐式锁,又称线程同步

synchronized是Java语言的关键字1,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。解决的是多线程并发时候的“时序性问题”。相对显示锁,不需要加锁与解锁操作

synchronized的用法,修饰地方只有两个;

一是在方法声明时使用,放在范围操作符(public)之后,返回类型声明(Void等)之前的方法名上面,代码如下:

 

1
2
3
public synchronized void synMethod(){
	//方法体
}

 

二是修饰在代码块上面的,对某一代码使用synchronized(Object),指定加锁对象:

 

1
2
3
4
5
6
public  void synMethod(){
	
	synchronized(Object){//Object可以是任意对象,可以是参数本身,可以是当前对象this,也可以是指定的对象
//一次只能有一个线程进入
	}
}

 

 下面是它的一些规则。注意下面是this参数

java多线程系列六之“锁”类多样_第1张图片

下面举例加以说明

计数器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Count{
	public int num=0;
	public synchronized void methodOne(){
		try{
			Thread.sleep(51);
		}catch(InterruptedException e){

		}
		num+=1;
		System.out.println(Thread.currentThread().getName()+"-"+num);
	}
	public void methodTwo(){
		synchronized(this){
			try{
				Thread.sleep(51);
			}catch(InterruptedException e){

			}
			num+=1;
			System.out.println(Thread.currentThread().getName()+"-"+num);
		}
	}
}

 

线程类

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public class TreadTest extends Thread{
	private Count count;
public TreadTest(Count count){
	this.count=count;
}

public void run(){
try{      count.methodOne();
			Thread.sleep(51);
			count.methodTwo();
		}catch(InterruptedException e){

		}


}
}

 

测试Demo

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class ThreadMainTest{
	public static void main(String[] args){
		Count count=new Count();
		for (int i=0;i<5;i++) {
			TreadTest task=new TreadTest(count);
			task.start();
		}
		try{
			Thread.sleep(1001);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		
}
}

 

结果应该是10,运行结果是10.线程安全

java多线程系列六之“锁”类多样_第2张图片

看一个错误例子。将上面的Count类改一下,改为

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Count{
	public int num=0;
	private byte[] lock=new byte[1];
	public synchronized void methodOne(){
		try{
			Thread.sleep(51);
		}catch(InterruptedException e){

		}
		num+=1;
		System.out.println(Thread.currentThread().getName()+"-"+num);
	}
	public void methodTwo(){
		synchronized(lock){
			try{
				Thread.sleep(51);
			}catch(InterruptedException e){

			}
			num+=1;
			System.out.println(Thread.currentThread().getName()+"-"+num);
		}
	}
}

 

运行结果如下。应该是10,现在为9,线程不安全

java多线程系列六之“锁”类多样_第3张图片

为什么会出现这种情况呢。因为它锁定的对象不一样,所以不建议用参数作为锁的对象,那样子,你的同步锁只会对这个方法有用,而失去了synchronized锁定对象的意义了。

同步方法体

public synchronized void synMethod(){
    //方法体
}
差于

public  void synMethod(){
    
    synchronized(this){
//一次只能有一个线程进入
    }
}

 private byte[] lock=new byte[1];
public  void synMethod(){
    
    synchronized(lock){
//一次只能有一个线程进入
    }
}

 

.2.显示锁Lock和ReentrantLock

Interface Lock(JDK1.8)

  • All Known Implementing Classes:

    ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock

 

 

1
2
3
4
5
6
7
8
public interface Lock {
   void lock();
   void lockInterruptibly() throws InterruptedException;
   boolean tryLock();
   boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
   void unlock();
   Condition newCondition();
   }

 

    • Modifier and Type Method and Description
      void lock()

      Acquires the lock.

      获取锁

      void lockInterruptibly()

      Acquires the lock unless the current thread is interrupted.

      获取锁除非当前线程已中断

      Condition newCondition()

      Returns a new Condition instance that is bound to this Lock instance.

      返回绑定到此锁实例的新Condition实例

      boolean tryLock()

      Acquires the lock only if it is free at the time of invocation.

      当且仅当调用锁是空闲的情况下才获取锁

      boolean tryLock(long time, TimeUnit unit)

      Acquires the lock if it is free within the given waiting time and the current thread has not been interrupted.

      如果锁在给定的等待时间内空闲且当前线程未被中断,则获取该锁。

      void unlock()

      Releases the lock.

      释放该锁

 具体解说

java多线程系列六之“锁”类多样_第4张图片

java多线程系列六之“锁”类多样_第5张图片 

 使用方法如下

 

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class X{
	private final ReentrantLock lock=new ReentrantLock();

	//...
	public void m(){
		lock.lock();
		try{

		}finally{
			lock.unlock();//释放锁
		}
	}
}

java多线程系列六之“锁”类多样_第6张图片 

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)(注意:有公平锁与非公平锁两种情况)

默认非公平锁  ReentrantLock lock=new ReentrantLock(false);//false是默认的

公平锁   ReentrantLock lock=new ReentrantLock(true);

ReentrantLock 扩展的功能

(1)实现可轮询的锁请求

     在内部锁中,死锁是致命的——唯一的恢复方法是重新启动程序,唯一的预防方法是在构建程序时不要出错。而可轮询的锁获取模式具有更完善的错误恢复机制,可以规避死锁的发生。 

     如果你不能获得所有需要的锁,那么使用可轮询的获取方式使你能够重新拿到控制权,它会释放你已经获得的这些锁,然后再重新尝试。可轮询的锁获取模式,由tryLock()方法实现。此方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值true。如果锁不可用,则此方法将立即返回值false。

(2)实现可定时的锁请求

     当使用内部锁时,一旦开始请求,锁就不能停止了,所以内部锁给实现具有时限的活动带来了风险。为了解决这一问题,可以使用定时锁。当具有时限的活 
动调用了阻塞方法,定时锁能够在时间预算内设定相应的超时。如果活动在期待的时间内没能获得结果,定时锁能使程序提前返回。可定时的锁获取模式,由tryLock(long, TimeUnit)方法实现。 

(3)实现可中断的锁获取请求

    可中断的锁获取操作允许在可取消的活动中使用。lockInterruptibly()方法能够使你获得锁的时候响应中断。

 

请注意一下两种方式的区别

第一种方式:两个方法之间的锁是独立的。代码如下

 

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.concurrent.locks.ReentrantLock;
public class Count{
	private int num=0;
	
	public Count(){}
		public void get(){
			final ReentrantLock lock=new ReentrantLock();
			try{
				lock.lock();//加锁
				System.out.println(Thread.currentThread().getName()+"  get begin");
				Thread.sleep(1000L);
				num=num+1;
				System.out.println(Thread.currentThread().getName()+"  get end"+num);
				lock.unlock();
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		public void put(){
			final ReentrantLock lock=new ReentrantLock();
			try{
				lock.lock();
				System.out.println(Thread.currentThread().getName()+"   put begin");
				Thread.sleep(1000L);
				num=num+1;
				System.out.println(Thread.currentThread().getName()+"   put end"+num);
				lock.unlock();
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}

 

测试Demo

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
	public static void main(String[] args){
		Count ct=new Count();
		for(int i=0;i<2;i++){
			new Thread(){
				@Override
				public void run(){
					ct.get();
				}
			}.start();
		}
			for (int i=0;i<2;i++) {
				new Thread(){
					@Override
					public void run(){
						ct.put();
					}
				}.start();
			}
		
	}
	
}  

 

运行结果

java多线程系列六之“锁”类多样_第7张图片 

第二种方式,两个方法之间使用相同的锁

将Count中的ReentrantLock改成全局变量,如下所示

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.util.concurrent.locks.ReentrantLock;
public class Count{
	private int num=0;
final ReentrantLock lock=new ReentrantLock();
	public Count(){}
		public void get(){
			
			try{
				lock.lock();//加锁
				System.out.println(Thread.currentThread().getName()+"  get begin");
				Thread.sleep(1000L);
				num=num+1;
				System.out.println(Thread.currentThread().getName()+"  get end"+num);
				lock.unlock();
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		public void put(){
			
			try{
				lock.lock();
				System.out.println(Thread.currentThread().getName()+"   put begin");
				Thread.sleep(1000L);
				num=num+1;
				System.out.println(Thread.currentThread().getName()+"   put end"+num);
				lock.unlock();
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}

运行结果。(每次都一样,仔细体会一下)

java多线程系列六之“锁”类多样_第8张图片

 .3.显示锁ReadWriteLock和ReentrantReadWriteLock

java多线程系列六之“锁”类多样_第9张图片

 

1
2
3
4
public interface ReadWriteLock {
	Lock readLock();
	Lock writeLock();
}

 

 java多线程系列六之“锁”类多样_第10张图片

 

java多线程系列六之“锁”类多样_第11张图片 

java多线程系列六之“锁”类多样_第12张图片 

使用读/写锁的方法和步骤

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
//创建一个ReentrantReadWriteLock对象
private ReentrantReadWriteLock rw1=new ReentrantReadWriteLock();
//抽取读锁和写锁
private Lock readLock=rw1.readLock();//得到一个可被多个读操作共用的读锁,但它会排斥所有写操作
private Lock writeLock=rw1.writeLock();//得到一个写锁,它会排斥所有其他的读操作和写操作

//对所有访问者加读锁
public double getTotalBalance(){
	readLock.lock();
	try{...}
	finally{readLock.unlock();}
} 
//对所有修改者加写锁
public void transfer(){
	writeLock.lock();
	try{...}
	finally{
		writeLock.unlock();
	}
}

 

实例体会

第一种情况,先体验一下ReadLock和WriteLock单独使用的情况

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Count{
	private int num=0;
final ReentrantReadWriteLock rw1=new ReentrantReadWriteLock();
	public Count(){}
		public void get(){
			rw1.readLock().lock();//加锁
			try{
				
				System.out.println(Thread.currentThread().getName()+"  read begin");
				Thread.sleep(1000L);
				num=num+1;
				System.out.println(Thread.currentThread().getName()+"  read end"+num);
				
			}catch(InterruptedException e){
				e.printStackTrace();
			}finally{
				rw1.readLock().unlock();
			}
		}
		public void put(){
			rw1.writeLock().lock();
			try{
				
				System.out.println(Thread.currentThread().getName()+"   put begin");
				Thread.sleep(1000L);
				num=num+1;
				System.out.println(Thread.currentThread().getName()+"   put end"+num);
				
			}catch(InterruptedException e){
				e.printStackTrace();
			}finally{
				rw1.writeLock().unlock();
			}
		}
	}

 

测试Demo

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantReadWriteLockDemo {
	public static void main(String[] args){
		Count ct=new Count();
		for(int i=0;i<2;i++){
			new Thread(){
				@Override
				public void run(){
					ct.get();
				}
			}.start();
		}
			for (int i=0;i<2;i++) {
				new Thread(){
					@Override
					public void run(){
						ct.put();
					}
				}.start();
			}
		
	}
	
} 

 

运行结果(可以看的出来,读的时候是并发的,写的时候是有顺序的带阻塞机制的)

java多线程系列六之“锁”类多样_第13张图片

第二种情况,体会一个ReadLock和WriteLock的复杂使用情况,模拟一个有读写数据的场景,仔细体会一下

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private final Map map=new HashMap();//假设这里面存了数据缓存
private final ReentrantReaadWriteLock rwlock=new ReentrantReaadWriteLock();
public Object readWrite(String id){
	Object value=null;
	rwlock.readLock().lock();
	try{
		value=map.get(id);
		if (value==null) {
			rwlock.readLock().unlock();
			rwlock.writeLock().lock();
			try{
				if (value==null) {//预防其他对象在线程交互时提前更改了value
					value="aaa";
				}
			}finally{
                rwlock.writeLock().unlock();
			}
			rwlock.readLock().lock();
		}
	}finally{
		rwlock.readLock().unlock();
	}
	return value;
}

 

java多线程系列六之“锁”类多样_第14张图片 

.4.显示锁StampedLock

Class StampedLock

  • java.lang.Object
    • java.util.concurrent.locks.StampedLock
  • All Implemented Interfaces:Serializable

    • Modifier and Type Method and Description
      Lock asReadLock()

      Returns a plain Lock view of this StampedLock in which the Lock.lock() method is mapped to readLock(), and similarly for other methods.

      返回该StampedLock一个空白的Lock视图,其中Lock.lock()方法被映射到readLock(),其他方法也是如此

      ReadWriteLock asReadWriteLock()

      Returns a ReadWriteLock view of this StampedLock in which the ReadWriteLock.readLock() method is mapped to asReadLock(), and ReadWriteLock.writeLock() to asWriteLock().

      返回该StampedLock的一个ReadWriteLock 视图,其中ReadWriteLock.readLock()被映射到asReadLock()方法,

      ReadWriteLock.writeLock()被映射到 asWriteLock()方法

      Lock asWriteLock()

      Returns a plain Lock view of this StampedLock in which the Lock.lock() method is mapped to writeLock(), and similarly for other methods.

      返回该StampedLock的一个空白Lock视图,其中Lock.lock()方法被映射到writeLock(),其他方法也是如此

      int getReadLockCount()

      Queries the number of read locks held for this lock.

      查询为此锁保留的读锁数。

      boolean isReadLocked()

      Returns true if the lock is currently held non-exclusively.

      如果锁当前以非独占方式持有,则isReadLocked()返回true。

      boolean isWriteLocked()

      Returns true if the lock is currently held exclusively.

      如果锁当前以非独占方式持有,则isWriteLocked()返回true。

      long readLock()

      Non-exclusively acquires the lock, blocking if necessary until available.

      readLock()非独占地获取锁,必要时阻塞,直到可用。

      long readLockInterruptibly()

      Non-exclusively acquires the lock, blocking if necessary until available or the current thread is interrupted.

      readLockInterruptibly()非独占地获取锁,必要时阻塞,直到可用或当前线程中断

      String toString()

      Returns a string identifying this lock, as well as its lock state.

      返回标识此锁及其锁状态的字符串。

      long tryConvertToOptimisticRead(long stamp)

      If the lock state matches the given stamp then, if the stamp represents holding a lock, releases it and returns an observation stamp.

      如果锁状态与给定的标记匹配,接着如果该标记表示持有锁,则释放它并返回一个观察标记。

      long tryConvertToReadLock(long stamp)

      If the lock state matches the given stamp, performs one of the following actions.

      如果锁状态与给定的标记匹配,执行下列操作之一。

      long tryConvertToWriteLock(long stamp)

      If the lock state matches the given stamp, performs one of the following actions.

      如果锁状态与给定的标记匹配,执行下列操作之一。

      long tryOptimisticRead()

      Returns a stamp that can later be validated, or zero if exclusively locked.

      返回可稍后验证的标记,如果以独占方式锁定,则返回零。

      long tryReadLock()

      Non-exclusively acquires the lock if it is immediately available.

      如果锁立即可用,则非独占获取它。

      long tryReadLock(long time, TimeUnit unit)

      Non-exclusively acquires the lock if it is available within the given time and the current thread has not been interrupted.

      如果该锁在给定的时间内是可用的,并且当前线程没有中断,则非独占获取它

      boolean tryUnlockRead()

      Releases one hold of the read lock if it is held, without requiring a stamp value.

      释放持有的一个读锁(如果它被持有),而不需要标记值。

      boolean tryUnlockWrite()

      Releases the write lock if it is held, without requiring a stamp value.

      释放持有的一个写锁(如果它被持有),而不需要标记值。

      long tryWriteLock()

      Exclusively acquires the lock if it is immediately available.

      如果锁立即可用,则独占获取它

      long tryWriteLock(long time, TimeUnit unit)

      Exclusively acquires the lock if it is available within the given time and the current thread has not been interrupted.

      如果该锁在给定的时间内是可用的,并且当前线程没有中断,则独占获取它

      void unlock(long stamp)

      If the lock state matches the given stamp, releases the corresponding mode of the lock.

      如果锁定状态与给定的标记匹配,则释放相应的锁定模式。

      void unlockRead(long stamp)

      If the lock state matches the given stamp, releases the non-exclusive lock.

      如果锁定状态与给定的标记匹配,则释放非独占锁。

      void unlockWrite(long stamp)

      If the lock state matches the given stamp, releases the exclusive lock.

      如果锁定状态与给定的标记匹配,则释放独占锁。

      boolean validate(long stamp)

      Returns true if the lock has not been exclusively acquired since issuance of the given stamp.

      如果自给定的标记以来未独占获取锁,则返回true。

      long writeLock()

      Exclusively acquires the lock, blocking if necessary until available.

      独占地获取该锁,必要时阻塞,直到可用。

      long writeLockInterruptibly()

      Exclusively acquires the lock, blocking if necessary until available or the current thread is interrupted.

      独占地获取该锁,必要时阻塞,直到可用或当前线程中断

java多线程系列六之“锁”类多样_第15张图片

java多线程系列六之“锁”类多样_第16张图片 

看一下StampedLock的部分源码

java多线程系列六之“锁”类多样_第17张图片 java多线程系列六之“锁”类多样_第18张图片

 java多线程系列六之“锁”类多样_第19张图片

java多线程系列六之“锁”类多样_第20张图片 

下面是Java的doc中提供的例子

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class Point {
   private double x, y;
   private final StampedLock sl = new StampedLock();

   void move(double deltaX, double deltaY) { // an exclusively locked method
     long stamp = sl.writeLock();
     try {
       x += deltaX;
       y += deltaY;
     } finally {
       sl.unlockWrite(stamp);
     }
   }
//下面是乐观读锁案例
   double distanceFromOrigin() { // A read-only method
     long stamp = sl.tryOptimisticRead();
     double currentX = x, currentY = y;
     if (!sl.validate(stamp)) {
        stamp = sl.readLock();
        try {
          currentX = x;
          currentY = y;
        } finally {
           sl.unlockRead(stamp);
        }
     }
     return Math.sqrt(currentX * currentX + currentY * currentY);
   }
//下面是悲观读锁案例
   void moveIfAtOrigin(double newX, double newY) { // upgrade
     // Could instead start with optimistic, not read mode
     long stamp = sl.readLock();
     try {
       while (x == 0.0 && y == 0.0) {
         long ws = sl.tryConvertToWriteLock(stamp);//将读锁转为写锁
         if (ws != 0L) {
           stamp = ws;
           x = newX;
           y = newY;
           break;
         }
         else {
           sl.unlockRead(stamp);
           stamp = sl.writeLock();
         }
       }
     } finally {
       sl.unlock(stamp);
     }
   }
 }

 java多线程系列六之“锁”类多样_第21张图片

java多线程系列六之“锁”类多样_第22张图片 

Java关键字volatile修饰变量

java多线程系列六之“锁”类多样_第23张图片 

java多线程系列六之“锁”类多样_第24张图片 

使用volatile变量的第二个语义是禁止指令重排序优化。在单例模式的双检模式中就利用到

 原子操作:atomic

java多线程系列六之“锁”类多样_第25张图片

 java多线程系列六之“锁”类多样_第26张图片

AtomicInteger的主要方法有

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//获取当前的值
public final int get()
//取当前的值,并设置新的值
public final int getAndSet(int newValue)
//获取当前的值,并自增
public final int getAndIncrement()
//获取当前的值,并自减
public final int getAndDecrement()
//获取当前的值,并加上预期的值
public final int getAndAdd(int delta)

 

 使用方法如下

 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import java.util.concurrent.atomic.AtomicInteger;
public class TestC{
	public static void main(String[] args){
		AtomicInteger ai=new AtomicInteger(0);
		System.out.println(ai.get());
		System.out.println(ai.getAndSet(5));
		System.out.println(ai.getAndIncrement());
		System.out.println(ai.getAndDecrement());
		System.out.println(ai.getAndAdd(10));
		System.out.println(ai.get());
	}
}

 

运行结果

java多线程系列六之“锁”类多样_第27张图片 

原子操作atomic的实现原理,是利用CPU的比较并交换(即CAS:Compare and Swap)和非阻塞算法(non-blocking algorithms) 

你可能感兴趣的:(多线程,java,同步锁)