volatile
- volatile自身特性
java中使用volatile关键字来保证可见性。
可见性:当对一个变量修改后,会立即更新到主存中,其它线程使用该变量时,会读取主存中的最新值。
相对于没有volatile修饰的共享变量,其它线程读取该变量的值有可能是缓存中的值。下面是一个用于理解的例子:
//共享变量
boolean stopFlag=false;
//线程1
new Thread(
new Runnable() {
public void run() {
while(!stopFlag){//操作2
System.out.println("do something...");
}
}
}
).start();
//线程2
new Thread(
new Runnable() {
public void run() {
stopFlag=true;//操作1
}
}
).start();
如果没有volatile ,操作1会将stopFlag保存到工作内存,不确定何时刷新到主存区。此时线程1读取将stopFlag=false读入到自己的工作内存,会继续执行循环。而使用volatile修饰后,stopFlag修改时会立即更新到主存,此时线程1工作内存中的stopFlag失效,必需从主内存中重新读取。
- volatile对内存可见性影响
一个简单的例子:
static int a=0;//普通共享变量
static boolean flag = false;
public static void main(String[] args) {
//线程1
new Thread(
new Runnable() {
public void run() {
if(flag){//操作1
int x=100/a;//操作2
}
}
}
).start();;
//线程2
new Thread(
new Runnable() {
public void run() {
a = 10;//操作3
flag = true;//操作4
}
}
).start();
}
代码执行时,编译器和处理器有可能对代码执行顺序进行重排列。如果代码按照 操作4-操作1-操作2这样的顺序执行,程序会抛出异常。解决方法是用volatile修饰flag变量,用volatile修饰后,编译器和处理器不会对代码的运行顺序重排列。实现原理简单理解:使用volatile修饰后,会对flag加屏障,保证操作3完成并写入主内存后才执行操作4。
synchronized
看过大牛们写的synchronized底层实现的文章,可惜自己基础知识不过关,看不懂。暂且总结一下synchronized使用规则,待以后再学习底层实现:
- 使用synchronized产生的对象锁。例如synchronized修饰对象方法或属性,修饰代码块synchronized(Object object)(object为对象参数)。
- 使用synchronized产生的类锁。例如synchronized来修饰静态方法或属性,修饰代码块 synchronized(Class class)。
//定义一个类,包含一个synchronized修饰的静态方法、一个synchronized修饰的对象方法、一个没有synchronized修饰的对象方法
class SynchronsizedBlock {
//修饰静态方法
public static synchronized void staticMethod() {
for(int i = 0;i<5;i++){
System.out.println(Thread.currentThread().getName()+": 线程在执行 staticMethod 第"+i+"次");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//修饰对象方法
public synchronized void objectMethod(){
for(int i = 0;i<5;i++){
System.out.println(Thread.currentThread().getName()+": 线程在执行 objectMethod 第"+i+"次");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//普通方法用于对比
public void referenceMethod(){
for(int i = 0;i<10;i++){
System.out.println(Thread.currentThread().getName()+": 线程在执行 referenceMethod 第"+i+"次");
}
}
}
写一个测试类,产生两个线程,调用上面的方法:
public class ClientTest{
public static void main(String[] args) {
final SynchronsizedBlock synchronsizedBlock = new SynchronsizedBlock();
//线程1获取synchronsizedBlock对象的锁
new Thread(new Runnable() {
public void run() {
synchronized(synchronsizedBlock){
synchronsizedBlock.objectMethod();
}
}
},"线程1").start();
//线程1获取锁后,让线程2访问synchronsizedBlock对象的静态方法
new Thread(new Runnable() {
public void run() {
SynchronsizedBlock.staticMethod();
}
},"线程2").start();
}
}
这个结果很好理解,线程1执行synchronized(synchronsizedBlock) 获取synchronsizedBlock该对象的对象级别的锁,而staticMethod()方法属于类级别的方法,所以线程2可以访问。如果让线程1去调用referenceMethod()方法,线程2去调用objectMethod()方法的结果:
简单理解上面代码中synchronized(synchronsizedBlock) 获取对象锁,获取对象锁后,其它线程不能访问该对象中使用synchronsized修饰的对象方法。synchronized(this){}道理相同,获取当前对象的对象锁。
最后经典的Double-Check例子:
//懒汉模式double check
class SingletonFactory{
private static SingletonTest singletonTest;
public SingletonTest getInstance(){
//firstcheck
if(singletonTest==null){
synchronized (this) {
//secondcheck
if(singletonTest==null){
return new SingletonTest();
}
}
}
return singletonTest;
}
}
class SingletonTest{
}
- Lock
Lock是个接口,实现类ReentrantLock,主要的方法有:lock(),lockInterruptibly(), tryLock(), tryLock(long paramLong, TimeUnit paramTimeUnit), unlock(),newCondition()
lock():获取锁
lockInterruptibly():获取所,和lock()相比线程等待时,可被中断
tryLock():如果得到锁返回true,没有得到返回false
tryLock(long paramLong, TimeUnit paramTimeUnit):线程等待一段事件后重新获取锁
unlock():释放锁
newCondition():绑定条件
用法举例:
static Lock lock = new ReentrantLock();
//使用tryLock()获取锁
public void lockMethod(){
try{
if(lock.tryLock()){
for(int i= 0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"--执行lockMethod()"+i+"次");
Thread.sleep(500L);
}
lock.unlock();//释放锁
}else{
System.out.println(Thread.currentThread().getName()+"获取锁失败");
}
}catch(Exception e){
e.printStackTrace();
}
}
//使用lock获取锁
public void lockMethod2(){
try {
lock.lock();
for(int i= 0;i<5;i++){
System.out.println(Thread.currentThread().getName()+"--执行LocktescLock2第"+i+"次");
Thread.sleep(500L);
}
}
catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();//释放锁
}
}
tryLock()比synchronized使用更加灵活,比如一个抢单的方法,只要保证有一个线程进入抢单即可,其它线程可直接返回。
使用lock中Condition实现 生产者-消费者问题:
产品类:
public class Product {
private int id;
public Product(int id){
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public String toString() {
return "Product [id=" + id + "]";
}
仓库类:
public class Warehouse {
private int MaxProduct = 10;//仓库最大容量
List<Product> products = new ArrayList<Product>();
public List<Product> getProducts() {
return products;
}
public void setProducts(List<Product> products) {
this.products = products;
}
public int getMaxProduct() {
return MaxProduct;
}
public void setMaxProduct(int maxProduct) {
MaxProduct = maxProduct;
}
}
生产者类:(负责生产产品)
public class Producer implements Runnable{
Lock lock = null;
Condition notFull = null;
Condition notEmpty = null;
Warehouse warehouse = null;
public Producer(Lock lock,Condition notFull,Condition notEmpty){
this.lock = lock;
this.notFull = notFull;
this.notEmpty = notEmpty;
}
//生产产品的方法
public void produce(){
while(true){
try{
lock.lock();
//仓库已满
while(warehouse.products.size()==warehouse.getMaxProduct()){
System.out.println("仓库已满...");
notFull.await();
}
System.out.println("仓库未满,生产产品...");
//仓库未满,生产产品
Product product = new Product((int)(Math.random()*100));
warehouse.products.add(product);
notEmpty.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
System.out.println("生产者释放资源");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void run() {
produce();
}
public Warehouse getWarehouse() {
return warehouse;
}
public void setWarehouse(Warehouse warehouse) {
this.warehouse = warehouse;
}
}
消费者类:(负责消费产品)
public class Consumer implements Runnable{
Lock lock = null;
Condition notFull = null;
Condition notEmpty = null;
Warehouse warehouse = null;
public Consumer(Lock lock,Condition notFull,Condition notEmpty){
this.lock = lock;
this.notFull = notFull;
this.notEmpty = notEmpty;
}
public void consume(){
while(true){
try{
lock.lock();
//仓库中库存为0
while(warehouse.products.size()==0){
System.out.println("没有库存......");
notEmpty.await();
}
//有库存
System.out.println("有库存,消费产品......"+warehouse.products.get(0).toString());
warehouse.products.remove(0);
notFull.signal();
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
System.out.println("消费者释放资源");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Override
public void run() {
consume();
}
public Warehouse getWarehouse() {
return warehouse;
}
public void setWarehouse(Warehouse warehouse) {
this.warehouse = warehouse;
}
}
测试方法:
public static void main(String[] args) {
Lock lock = new ReentrantLock();//产生锁
Condition notEmpty = lock.newCondition();//绑定条件
Condition notFull = lock.newCondition();
Warehouse wareHouse = new Warehouse();//仓库
Producer producer = new Producer(lock, notFull, notEmpty);
producer.setWarehouse(wareHouse);
Consumer consumer = new Consumer(lock, notFull, notEmpty);
consumer.setWarehouse(wareHouse);
Thread thread1 = new Thread(producer);
Thread thread2 = new Thread(consumer);
thread1.start();
thread2.start();
}
执行结果:
仓库未满,生产产品…
生产者释放资源
仓库已满…
有库存,消费产品……Product [id=40]
消费者释放资源
仓库未满,生产产品…
生产者释放资源
仓库已满…
有库存,消费产品……Product [id=20]
消费者释放资源
仓库未满,生产产品…
生产者释放资源
仓库已满…
有库存,消费产品……Product [id=53]
消费者释放资源
仓库未满,生产产品…
生产者释放资源
ReadWriteLock
ReadWriteLock读写锁接口,实现类有ReentrantReadWriteLock。接口:
public abstract interface ReadWriteLock {
public abstract Lock readLock();//获取读锁
public abstract Lock writeLock();//获取写锁
}
ReadWriteLock使用规则:
1)线程可以同时获取读锁,如果有线程获取读锁,其它线程不能获取写锁
2)只有一个线程可获得写锁,其它线程不能获取读锁和写锁。