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状态。
public class App1 extends Thread {
private Object lock;
public App1(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
System.out.println(Thread.currentThread().getName()
+ " : start to wait.");
lock.wait();
System.out.println(Thread.currentThread().getName()
+ " : wait ends, execute again.");
}
} catch (Exception e) {}
}
}
public class App2 extends Thread {
private Object lock;
public App2(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName()
+ " : Start notify.");
lock.notify();
System.out.println(Thread.currentThread().getName()
+ " : notify ends, start to execute again.");
}
}
public static void main(String[] args) {
try {
Object lock = new Object();
App1 app1 = new App1(lock);
app1.start();
Thread.sleep(5000);
App2 app2 = new App2(lock);
app2.start();
} catch (Exception e) {}
}
}
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还支持任意对象的锁,这样同一个类的不同方法可以添加不同的对象锁。
public class App1 {
private Object lock1 = new Object();
private static Object lock2 = new Object();
synchronized public void funcA() {
//this object lock
}
public void funcB() {
synchronized(this) {
//this object lock
}
//run something without lock
}
public void funcC(List<String> list) {
synchronized(list) {
//list object lock
}
}
public void funcD() {
synchronized(lock1) {
//lock1 object lock
}
}
public void funcE() {
synchronized(lock2) {
//lock2 static object lock
}
}
public void funcF() {
synchronized(App1.class) {
//App1 class lock
}
}
synchronized public static void funcG() {
//App1 class lock
}
}
使用ReentraintLock和ReentraintReadWriteLock实现线程的同步
java的lock机制基于Abstract Queued Synchronizer (AQS)的实现,AQS定义了多线程访问共享资源的同步器框架,常见的如ReentraintLock/Semaphore/CountDownLatch等都依赖于AQS的实现;
AQS通过维护一个FIFO队列,并且通过一个由volatile修饰的int状态值来实现锁的获取。FIFO队列中每一个Node表示一个排队线程,其保存着线程的引用和状态,然后通过三个方法分别对获取或者设置状态。
private volatile int state;
static final class Node {
int waitStatus;
Node prev;
Node next;
Node nextWaiter;
Thread thread;
}
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState; protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg)
protected int tryAcquireShared(int arg)
protected boolean tryReleaseShared(int arg)
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模式的节点对应的操作都执行完毕之后才会获取到锁,这就是读写锁的模式。
public class App1 extends Thread {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public App1() {
super();
}
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " : start to wait.");
condition.await();//condition.signal();
System.out.println(Thread.currentThread().getName()
+ " : wait ends, execute again.");
} catch (Exception e) {
} finally {
lock.unlock();
}
}
}
Java多线程的同步方式和锁机制的更多相关文章
- Java多线程之同步集合和并发集合
Java多线程之同步集合和并发集合 不管是同步集合还是并发集合他们都支持线程安全,他们之间主要的区别体现在性能和可扩展性,还有他们如何实现的线程安全. 同步集合类 Hashtable Vector 同 ...
- Java多线程的同步控制记录
Java多线程的同步控制记录 一.重入锁 重入锁完全可以代替 synchronized 关键字.在JDK 1.5 早期版本,重入锁的性能优于 synchronized.JDK 1.6 开始,对于 sy ...
- Java多线程编程(同步、死锁、生产消费者问题)
Java多线程编程(同步.死锁.生产消费): 关于线程同步以及死锁问题: 线程同步概念:是指若干个线程对象并行进行资源的访问时实现的资源处理保护操作: 线程死锁概念:是指两个线程都在等待对方先完成,造 ...
- JAVA多线程-实现同步
一.什么是线程安全问题 当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题.但是做读操作是不会发生数据冲突问题. 二.如何解决线程安全问题 1)如何 ...
- Java多线程的同步机制(synchronized)
一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个 ...
- Java多线程面试题:线程锁+线程池+线程同步等
1.并发编程三要素? 1)原子性 原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行. 2)可见性 可见性指多个线程操作一个共享变量时,其中一个线程对变量 ...
- java——多线程的实现方式、三种办法解决线程赛跑、多线程数据同步(synchronized)、死锁
多线程的实现方式:demo1.demo2 demo1:继承Thread类,重写run()方法 package thread_test; public class ThreadDemo1 extends ...
- java多线程系类:JUC锁:01之框架
本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--"JUC锁"01之 框架02. Java多线程系列--"JUC锁&q ...
- Java多线程总结(二)锁、线程池
掌握Java中的多线程,必须掌握Java中的各种锁,以及了解Java中线程池的运用.关于Java多线程基础总结可以参考我的这篇博文Java多线程总结(一)多线程基础 转载请注明出处——http://w ...
随机推荐
- 与adb相关的问题,比如掉线问题、Android Studio 提示Session 'app':Error Installing APK、找不到设备
这一篇帖子 会写的比较简单 不过相信大家也可能遇到这些问题 为了怕自己忘记 记录下来 顺便也和大家一起分享 描述:在一些机型上安装软件 提示卸载原先的软件 但是又安装不上新软件 DELETE ...
- bzoj 2460: [BeiJing2011]元素【线性基+贪心】
先按魔力值从大到小排序,然后从大到小插入线性基中,如果插入成功就加上这个魔力值 因为线性基里是没有异或和为0的集合的,所以正确性显然,然后最优性,考虑放进去一个原来没选的,这样为了可行性就要删掉一个, ...
- ssh密钥的分发之一:ssh-copy-id
ssh密钥的分发 我们在使用客户端账号对主机记性管理的时候,可以分为以下两种情况: .第一种情况,直接使用root账号: 优点:使用root账号密钥分发简单,指令执行简单 缺点:不安全 .第二种情况, ...
- 线段树(单点更新)/树状数组 HDOJ 1166 敌兵布阵
题目传送门 /* 线段树基本功能:区间值的和,修改某个值 */ #include <cstdio> #include <cstring> #define lson l, m, ...
- Reference for shell scripting
${var} 和 $var的区别 http://stackoverflow.com/questions/8748831/when-do-we-need-curly-braces-in-variable ...
- Lambda表达式的一些常用形式
1.调用一个方法 prod=>EvaluteProduct(prod); 2.lambad表达式来表示一个多参数的委托,则必须把参数封装在括号内.语句如下: (prod,count)=>p ...
- scrollTop、offsetTop、clientTop
1.offsetTop: obj.offsetTop 指 obj 相对于版面或由 offsetParent 属性指定的父坐标的计算上侧位置. 2.clientTop: 这个返回的是元素周围边框的厚度, ...
- Redis为什么这么快
Redis为什么这么快 1.完全基于内存,绝大部分请求是纯粹的内存操作,非常快速.数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1): 2.数据结构简单, ...
- php中的define()函数
<?php define("PI",3.1415926); //定义常量 $r=12;//定义圆半径 echo "半径为12的单位的圆的面积".PI*($ ...
- 程序员必须知道FTP命令
程序员必须知道FTP命令 文件传输软件的使用格式为:FTP<FTP地址>,若连 接成功,系统将提示用户输入 ...