Synchronize、volatile、lock相关
Synchronize修饰方法:
1,方法内变量不存在线程不安全问题,因为在内存模型中,每一个线程对应一个栈,而里方法里面的变量会在每个栈里有一个,不存在互相干扰的问题。
2,实例变量非线程安全。其实就是,当不同的线程对同一块内存上的对象进行操作时,因为cpu的随机执行,可能会出现a线程执行到一半被b线程抢占了cpu,导致b拿到的不是最新的状态,或者b线程的操作又被后来的a操作覆盖。
因此,当多个线程访问同一个对象时,需要加上synchronize关键字来修饰该对象,表示当某线程进入synchronize修饰的方法时,其他线程必须等待该线程执行完成,才可以继续执行同样被synchronize修饰的方法。
需要注意,必须是synchronize修饰的同一个对象,才会发生某线程等待的情况。如果a线程占有的objectA锁而b线程占有的是objecyB锁,他们直接会是异步执行。是否锁的同一个对象可以通过:
1, 是否synchronize()中是同一个对象来判断。
2, 当修饰方法时,this是不是同一个东西来判断。
Synchronize是可重入锁:
public class SynchronizedRelock {
synchronized public void service1(){
System.out.println("service1");
service2();
}
synchronized public void service2(){
System.out.println("service2");
service3();
}
synchronized public void service3(){
System.out.println("service3");
}
public static void main(String[] args) {
new TestThread(new SynchronizedRelock()).start();
}
}
class TestThread extends Thread{
private SynchronizedRelock synchronizedRelock;
public TestThread(SynchronizedRelock synchronizedRelock){
this.synchronizedRelock = synchronizedRelock;
}
@Override
public void run(){
synchronizedRelock.service1();
}
}
可以看到,尽管该线程在service1()中已经锁住了this对象,即new synchronizeRelock()实例,但是依然是可以访问service2()和service3()。即可重入锁是可以*访问任何自己已经取得锁的地方。
一个例子:
class FatherClass{
synchronized public void fatherService(){
System.out.println("father menthod");
}
}
class SonClass extends FatherClass{
synchronized public void sonService(){
System.out.println("son methof");
fatherService();
}
}
public class SynchronizedRelock {
public static void main(String[] args) {
new TestThread(new SonClass()).start();
}
}
class TestThread extends Thread{
private SonClass sonClass;
public TestThread(SonClass sonClass){
this.sonClass = sonClass;
}
@Override
public void run(){
sonClass.sonService();
}
}
可以看到,子类在锁定一个对象锁时,通过对象调用父类方法也是可以的,因为父类的方法锁定的也是该子类对象。但是要注意,父类和子类均需要用synchronize来修饰,synchronize关键字并不能继承。
Synchronize修饰代码块:
Synchronize修饰方法有一个弊端,就是当方法体内除了锁对象的改变,还有许多大量的非所对象的操作,由于synchronize修饰了整个方法,会导致效率低下,这个时候可以考虑使用synchronize修饰代码块来代替。
比如:
synchronize public void test(){
Thread.sleep(10000);
This.change();
}
当有两个线程同时访问该方法时,会导致他们同步执行,也就是说两个线程分别sleep 10000毫秒。
改为:
public void test(){
Thread.sleep(10000);
synchronized(this){
this.change();
}
}
这样,就可以保证两个线程异步sleep,效率提升了一倍。
脏读问题:
public class DirtyR{
private int i;
synchronized public void add(){
System.out.println(i++);
}
synchronized public void sub(){
System.out.println(i--);
}
public static void main(String[] args) {
DirtyR dirtyR = new DirtyR();
new AddThread(dirtyR).start();
new SubThread(dirtyR).start();
}
}
class AddThread extends Thread{
DirtyR dirtyR;
public AddThread(DirtyR dirtyR){
this.dirtyR = dirtyR;
}
@Override
public void run(){
for (int i = 0; i < 100; i++) {
dirtyR.add();
}
}
}
class SubThread extends Thread{
DirtyR dirtyr;
public SubThread(DirtyR dirtyR){
this.dirtyr = dirtyR;
}
@Override
public void run(){
for (int i = 0; i < 100; i++) {
dirtyr.sub();
}
}
}
运行该代码,虽然没有出现脏读的现象,但可以虽然用了synchronize来修饰,但是并不是按照加到100再减到1的顺序执行,这就为脏读提供了环境。
静态同步synchronized方法和synchronized(class)代码块
要分清楚静态方法锁的是类本身,即在内存中代表该类的一个class对象。即使使用实例来调用静态方法,也是会锁住类本身。即当a实例和b实例同时调用被synchronized修饰的静态方式时,也是被同步执行的。
public class DirtyR{
private static int i;
synchronized public static void add(){
System.out.println(i++);
}
public static void main(String[] args) {
DirtyR dirtyR1 = new DirtyR();
DirtyR dirtyR2 = new DirtyR();
new AddThread(dirtyR1).start();
new AddThread(dirtyR2).start();
}
}
class AddThread extends Thread{
DirtyR dirtyR;
public AddThread(DirtyR dirtyR){
this.dirtyR = dirtyR;
}
@Override
public void run(){
for (int i = 0; i < 100; i++) {
dirtyR.add();
}
}
}
class SubThread extends Thread{
DirtyR dirtyr;
public SubThread(DirtyR dirtyR){
this.dirtyr = dirtyR;
}
@Override
public void run(){
for (int i = 0; i < 100; i++) {
dirtyr.add();
}
}
}
可以看到,最终的输出也是同步的。
用String作为锁时的常量池问题。
public class DirtyR{
private int i;
String lock1 = "123";
String lock2 = "123";
public void add1(){
synchronized (lock1){
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread()+" "+i++);
}
}
}
public void add2() throws InterruptedException {
synchronized (lock2){
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread()+" "+i+++" "+j);
}
}
}
public static void main(String[] args) {
DirtyR dirtyR1 = new DirtyR();
new AddThread(dirtyR1).start();
new SubThread(dirtyR1).start();
}
}
class AddThread extends Thread{
DirtyR dirtyR;
public AddThread(DirtyR dirtyR){
this.dirtyR = dirtyR;
}
@Override
public void run(){
dirtyR.add1();
}
}
class SubThread extends Thread{
DirtyR dirtyr;
public SubThread(DirtyR dirtyR){
this.dirtyr = dirtyR;
}
@Override
public void run(){
try {
dirtyr.add2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
可以看到,虽然两个方法锁的对象时lock1和lock2,但由于常量池的存在,lock1和lock2实际上是同一个对象,随意最终还是以同步的方法在执行。可以用Object对象来作为
死锁,即双方都在等在对方释放锁后自己释放锁。
当锁对象发生改变时:
public class DirtyR{
private int i;
String s = "lock";
public void add1() throws InterruptedException {
synchronized(s){
s="newlock";
Thread.sleep(50);
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread().getName());
}
}
}
public void add2(){
synchronized(s){
System.out.println(s);
for (int j = 0; j < 100; j++) {
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) throws InterruptedException {
DirtyR dirtyR1 = new DirtyR();
new AddThread(dirtyR1).start();
Thread.sleep(50);
new SubThread(dirtyR1).start();
}
}
class AddThread extends Thread{
private DirtyR dirtyR;
public AddThread(DirtyR dirtyR){
this.dirtyR = dirtyR;
}
@Override
public void run(){
try {
dirtyR.add1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class SubThread extends Thread{
private DirtyR dirtyr;
public SubThread(DirtyR dirtyR){
this.dirtyr = dirtyR;
}
@Override
public void run(){
dirtyr.add2();
}
}
可以看到当lock对象发生改变时,便会异步执行。
volatile关键字 (等有时间专门写volatile的东西)
volatile的主要作用是,保证线程的对某变量的读取总是从公共内存中读取的,增加了实例变量在多个线程间的可见性。
和synchronized比较,volatile更多的是增加变量在线程间的可见性,销量会高过synchronized,但是synchronized可以解决线程间的同步问题,也保证原子性。
原子类也可以保证原子性,却不可以保证代码间的执行顺序,因此synchronized还是最保险的措施。