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参数

下面举例加以说明

计数器

 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.线程安全

看一个错误例子。将上面的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,线程不安全

为什么会出现这种情况呢。因为它锁定的对象不一样,所以不建议用参数作为锁的对象,那样子,你的同步锁只会对这个方法有用,而失去了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)

 

 

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 TypeMethod and Description
      voidlock()

      Acquires the lock.

      获取锁

      voidlockInterruptibly()

      Acquires the lock unless the current thread is interrupted.

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

      ConditionnewCondition()

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

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

      booleantryLock()

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

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

      booleantryLock(long time, TimeUnit unit)

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

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

      voidunlock()

      Releases the lock.

      释放该锁

 具体解说

 

 使用方法如下

 

 

 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();//释放锁
		}
	}
}

 

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();
			}
		
	}
	
}  

 

运行结果

 

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

将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();
			}
		}
	}

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

 .3.显示锁ReadWriteLock和ReentrantReadWriteLock

 

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

 

 

 

 

 

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

 

 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();
			}
		
	}
	
} 

 

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

第二种情况,体会一个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<String,Object> map=new HashMap<String,Object>();//假设这里面存了数据缓存
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;
}

 

 

.4.显示锁StampedLock

Class StampedLock

    • Modifier and TypeMethod and Description
      LockasReadLock()

      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(),其他方法也是如此

      ReadWriteLockasReadWriteLock()

      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()方法

      LockasWriteLock()

      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(),其他方法也是如此

      intgetReadLockCount()

      Queries the number of read locks held for this lock.

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

      booleanisReadLocked()

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

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

      booleanisWriteLocked()

      Returns true if the lock is currently held exclusively.

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

      longreadLock()

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

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

      longreadLockInterruptibly()

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

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

      StringtoString()

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

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

      longtryConvertToOptimisticRead(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.

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

      longtryConvertToReadLock(long stamp)

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

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

      longtryConvertToWriteLock(long stamp)

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

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

      longtryOptimisticRead()

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

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

      longtryReadLock()

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

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

      longtryReadLock(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.

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

      booleantryUnlockRead()

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

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

      booleantryUnlockWrite()

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

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

      longtryWriteLock()

      Exclusively acquires the lock if it is immediately available.

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

      longtryWriteLock(long time, TimeUnit unit)

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

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

      voidunlock(long stamp)

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

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

      voidunlockRead(long stamp)

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

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

      voidunlockWrite(long stamp)

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

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

      booleanvalidate(long stamp)

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

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

      longwriteLock()

      Exclusively acquires the lock, blocking if necessary until available.

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

      longwriteLockInterruptibly()

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

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

 

看一下StampedLock的部分源码

 

 

 

下面是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关键字volatile修饰变量

 

 

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

 原子操作:atomic

 

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());
	}
}

 

运行结果

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值