Java基础之线程安全

java多线程内存模型,主内存+工作内存。线程是将主内存的变量拷贝到工作内存中,在工作内存中对变量进行改动,再写回到主内存中,这个过程包含lock、read、load、use、assign、store、write、unlock等几个过程。jvm规定的这8个线程操作都是原子性的操作,但全过程并不是原子性的。jvm对这些操作做了限制,比如一个线程获取共享变量的lock后,其它线程要获取该变量的lock就必须等待前一个线程unlock操作,这保证了多线程操作共享变量时,共享变量的一致性。

但是jvm的这8种操作只是针对共享变量,而我们通常遇到的是要保证一块代码执行的原子性,jvm提供了monitorenter及monitorexit机制,它们就类似于变量的lock及unlock,对应代码就是synchronized及java.util.cucurrent.locks包里的Lock类。

Java对于多线程安全问题提供了多种解决方式。

1.volatile 关键字:

volatile修饰变量,他用于保证共享变量的可见性。volatile是jvm提供的最轻量级线程安全机制。它是用于修饰共享变量的

它的关键在于,每次写操作都先于读操作进行,即保证每次变量值的更改,对于之后其它线程的读取是可见的。

volatile还有一个特点就是禁止指令重排。它的实现机制是,代码编译后,volatile变量出现的指令会被加上一个内存屏障,内存屏障的作用是保证下面两点:

a).volatile变量的代码行前面的代码,在指令重排时永远在volatile变量前;

b).volatile变量的代码行后面的代码,在指令重排时永远在volatile变量之后。

volatile的应用场景比较少,下面举个例子,展示volatile的一种应用场景。

共享变量

boolean f = false;

线程1

do something.........

f=true;

do something.........

线程2

do something.........

while(!f){

}

do something.........

如上,如果f不使用volatile修饰,则线程2就可能一直运行下去,陷入死循环,因为线程1修改f变量后,线程2并没有变。

这时候volatile关键字就保证了f修改对所有线程可见,线程2就不会陷入死循环。

上面情况是体现volatile保证可见性的特点。对于volatile放在指令重排的特征,一般情况如下:

共享变量

boolean f=false;

线程1

do something.........

initSomeProp();//语句1

f=true;//语句2

do domething.........

线程2

do something.........

while(!f){

}

loadSomeProp();

上面例子是防止线程1的指令重排,导致线程2在loadSomeProp()时出错。这是由于语句2有可能在执行时被jvm重新排到语句1之前进行,这导致线程1还没有执行initSomeProp()方法,线程2就执行了loadSomeProp()方法,这是无法达到预期目标的。使用volatile就能够防止语句2被重新排到语句1之前执行。

2.synchronized关键字:

java程序猿处理多线程问题时,关于线程安全,使用最多的就死synchronized关键字了。

synchronized关键字主要是用于保证一段代码执行的安全性,它对一块代码加锁,不需要手动释放锁。多线程执行synchronized代码块时,当一个线程获取锁,其它线程就只能等待锁释放后才能获取锁并执行代码块。

synchronized能够保证多线程执行该代码块时线程间保持互斥性。因为synchronized代码块在执行前或获取lock,此时会从主内存中重新读取所需的变量值,在代码执行完成后会释放锁,将修改的变量值全部写回主内存,所以synchronized也能保证变量的可见性。

synchronized可以用于修饰class、方法、方法中的一块代码。

用于修饰class时,表示该类的所有public方法均是线程安全的。其实锁的对象是该类的对象本身。这种做法会使该类的所有public方法在多线程执行时均是互斥的,也会是不同方法之间的执行也是互斥的(即线程1执行方法1时,线程2想执行方法2也是不行的,因为线程2要获取同一个对象的锁,而这个锁正被线程1使用)。

用于修饰方法时,表示该方法是线程安全的。同样的,如果该类中有多个方法都被synchronized关键字修饰了,则这些方法之间的执行也是线程互斥的,因为修饰方法时,锁的对象也是该类的对象本身。

用于修饰方法中的代码块时,需要一个参数,该参数即作为锁的对象,可以是this对象,也可以是该类中定义的成员变量。用成员变量做锁对象时,一般不会选择复杂的对象,一般都会定义一个空的byte数组对象。

3.java.util.concurrent.locks包:

这是jdk提供的工具包,里面有许多Lock类,主要有ReentrantLock、ReentrantReadWritLock。提供了丰富的方法,以lock方法获取锁,以unlock方法释放锁。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值