.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
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实例 |
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. 释放该锁 |
具体解说
使用方法如下
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 |
.4.显示锁StampedLock
All Implemented Interfaces:Serializable
Modifier and Type | Method and Description |
---|---|
Lock |
asReadLock() Returns a plain 返回该StampedLock一个空白的Lock视图,其中Lock.lock()方法被映射到readLock(),其他方法也是如此 |
ReadWriteLock |
asReadWriteLock() Returns a 返回该StampedLock的一个ReadWriteLock 视图,其中
|
Lock |
asWriteLock() Returns a plain 返回该StampedLock的一个空白Lock视图,其中 |
int |
getReadLockCount() Queries the number of read locks held for this lock. 查询为此锁保留的读锁数。 |
boolean |
isReadLocked() Returns 如果锁当前以非独占方式持有,则isReadLocked()返回true。 |
boolean |
isWriteLocked() Returns 如果锁当前以非独占方式持有,则 |
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.
|
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. 独占地获取该锁,必要时阻塞,直到可用或当前线程中断 |
看一下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)