java多线程 同步互斥锁_Java多线程的同步方式和锁机制

Object.wait(miliSec)/notify()/notifyAll()

线程调用wait()之后可以由notify()唤醒,如果指定了miliSec的话也可超时后自动唤醒。wait方法的调用会让当前线程放弃已经获取的object锁标志位,比如在同步代码块synchronized中调用wait(),则表示当前线程被唤醒之后需要重新获取同步代码块的锁。另外wait/notify由于要操作对象的锁标志位,因此必须在synchronized代码块中调用,否则会抛出运行时异常IllegalMonitorStateException。

wait/notify机制出现之前,生产/消费实现模型的同步一般通过while(true)轮询实现,弊端是极大耗用CPU资源做无用的轮询。在调用wait方法之前,线程需要获取当前实例对象的锁,执行wait方法返回之后,线程释放掉对象锁并进入block状态;其他线程在调用notify方法之前,也需要获取当前实例对象的锁,执行notify方法时,如果有多个线程处理block状态则从中按某规则选择一个唤醒,notify方法调用之后不会立即释放锁,要等线程的同步方法执行完毕之后才释放对象锁,因此一次notify调用只会唤醒一个线程,其他block的线程依旧处理block状态。

1 public class App1 extendsThread {2 privateObject lock;3 publicApp1(Object lock) {4 super();5 this.lock =lock;6 }7 @Override8 public voidrun() {9 try{10 synchronized(lock) {11 System.out.println(Thread.currentThread().getName()12 + " : start to wait.");13 lock.wait();14 System.out.println(Thread.currentThread().getName()15 + " : wait ends, execute again.");16 }17 } catch(Exception e) {}18 }19 }20 public class App2 extendsThread {21 privateObject lock;22 publicApp2(Object lock) {23 super();24 this.lock =lock;25 }26 @Override27 public voidrun() {28 synchronized(lock) {29 System.out.println(Thread.currentThread().getName()30 + " : Start notify.");31 lock.notify();32 System.out.println(Thread.currentThread().getName()33 + " : notify ends, start to execute again.");34 }35 }36 public static voidmain(String[] args) {37 try{38 Object lock = newObject();39 App1 app1 = newApp1(lock);40 app1.start();41 Thread.sleep(5000);42 App2 app2 = newApp2(lock);43 app2.start();44 } catch(Exception e) {}45 }46 }47

Thread.sleep(miliSec)

线程释放CPU使用权,并进入休眠状态一段时间miliSec,不会放弃线程的锁标志位,比如如果在同步代码块synchronized中调用sleep(),表示线程将一直持有当前同步代码块的锁,其他线程将一直等待。

Thread.suspend()/resume()

两个方法配套使用,suspend进入的状态必须有resume调用恢复。跟sleep()方法类似,suspend方法也不会放弃线程已经获取的object锁标志位。这对方法已经不推荐使用,因为容易造成线程自己将自己suspend起来。

Thread.yield()

表示当前线程已经获得了充分的CPU执行时间,释放CPU使用权,并重新进入队列等待执行,yield()调用不会阻塞当前线程,也不会放弃当前线程已经获取的锁标志位 ,因此yield方法仅能让跟当前线程具有同样优先级的线程有限执行。

Thread.join(miliSec)

表示当前线程需要等到join方法的调用线程执行完毕之后才能继续执行,或者是等待join方法的调用线程执行一段时间之后当前线程才能执行,内部由wait方法实现,所以线程等待开始的时候就会释放持有的对象锁。

使用synchronized关键字修饰代码块或者方法

表示这块代码为互斥区或者临界区。有两种类型的锁可以通过synchronized加到代码块或者方法上,一种是实例Object锁,一种是class锁。对于同一个ClassLoader下加载的类而言,一个类只有一把class锁,所有这个类的实例都共享一把锁;同一个类可以实现多个实例对象,也就存在多把Object锁。

synchronized是语言自带的内置独享锁(非公平锁,不管race thread排队的时间先后,通过编排字节码实现,锁为对象或者类的头标记位),而Java语言的ReentraintReadWriteLock机制是基于Abstract Queued Synchronizer的一种实现(公平/非公平锁,state加CLH队列实现),主要的实现类是ReentrantLock;Java的数据主要会在CPU、Register、Cache、Heap和Thread stack之间进行复制操作,而前面四个都是在Java Threads之间共享,因此Java的锁机制主要用于解决Racing Threads的数据一致性;

另外通过synchronized添加的锁具有可重入性,也就是只要一个线程已经获取了锁,这样只要共享同一把锁的其他synchronized修饰的代码块或者方法都可以进入,换句话说其他线程访问对其他synchronized修饰的代码块或者方法也需要等待锁的释放,因此synchronized还支持任意对象的锁,这样同一个类的不同方法可以添加不同的对象锁。

1 public classApp1 {2 private Object lock1 = newObject();3 private static Object lock2 = newObject();4

5 synchronized public voidfuncA() {6 //this object lock

7 }8 public voidfuncB() {9 synchronized(this) {10 //this object lock

11 }12 //run something without lock

13 }14 public void funcC(Listlist) {15 synchronized(list) {16 //list object lock

17 }18 }19 public voidfuncD() {20 synchronized(lock1) {21 //lock1 object lock

22 }23 }24 public voidfuncE() {25 synchronized(lock2) {26 //lock2 static object lock

27 }28 }29 public voidfuncF() {30 synchronized(App1.class) {31 //App1 class lock

32 }33 }34 synchronized public static voidfuncG() {35 //App1 class lock

36 }37 }

使用ReentraintLock和ReentraintReadWriteLock实现线程的同步

java的lock机制基于Abstract Queued Synchronizer (AQS)的实现,AQS定义了多线程访问共享资源的同步器框架,常见的如ReentraintLock/Semaphore/CountDownLatch等都依赖于AQS的实现;

AQS通过维护一个FIFO队列,并且通过一个由volatile修饰的int状态值来实现锁的获取。FIFO队列中每一个Node表示一个排队线程,其保存着线程的引用和状态,然后通过三个方法分别对获取或者设置状态。

1 private volatile intstate;2 static final classNode {3 intwaitStatus;4 Node prev;5 Node next;6 Node nextWaiter;7 Thread thread;8 }9 protected final intgetState() {10 returnstate;11 }12 protected final void setState(intnewState) {13 state =newState;14

15 protected final boolean compareAndSetState(int expect, intupdate) {16 //See below for intrinsics setup to support this

17 return unsafe.compareAndSwapInt(this, stateOffset, expect, update);18 }19 protected boolean tryAcquire(intarg)20 protected boolean tryRelease(intarg)21 protected int tryAcquireShared(intarg)22 protected boolean tryReleaseShared(intarg)23 protected boolean isHeldExclusively()

通过对getState,setState和compareAndSetState的封装,AQS的继承类需要试下如下几个方法,前面两个表示获取和释放独占锁(如ReentraintLock),后面两个表示获取和释放共享锁(如Semaphore和CountDownLatch)。ReentrantLock初始化状态state=0,线程A访问同步代码的时候使用ReentrantLock.lock(),内部会调用tryAcquire尝试获取独占锁,状态变成state+1;其他线程调用ReentrantLock.lock()的时候就会失败,直到线程A调用unlock(内部为tryRelease)将状态编程state=0;如果线程A在持有独占锁的同时访问其他同步代码块,这时候state的值就会累加,需要调用unlock(内部为tryRelease)减少state的值。ReentrantLock也提供了类似wait/notify的方法,await/signal,同样的线程在调用这两个方法之前需要获得对象锁监视,也就是执行lock.lock()方法。

ReentrantLock是纯粹的独占锁,为了提升效率引入了ReentrantReadWriteLock.readLock/writeLock,读读共享,读写互斥,写写互斥。CLH队列中的节点模式分为shared和exclusive两种,当一个线程修改了state状态则表示成功获取了锁,如果线程的模式是shared则会执行一个传递读锁的过程,策略是从CLH队列的头到尾依次传递读锁,直到遇到一个模式为exclusive的写锁模式的节点,这个exclusive模式的节点需要等之前所有shared模式的节点对应的操作都执行完毕之后才会获取到锁,这就是读写锁的模式。

1 public class App1 extendsThread {2 private Lock lock = newReentrantLock();3 private Condition condition =lock.newCondition();4 publicApp1() {5 super();6 }7 @Override8 public voidrun() {9 try{10 lock.lock();11 System.out.println(Thread.currentThread().getName() + " : start to wait.");12 condition.await();//condition.signal();

13 System.out.println(Thread.currentThread().getName()14 + " : wait ends, execute again.");15 } catch(Exception e) {16

17 } finally{18 lock.unlock();19 }20 }21 }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值