synchronized关键字
1.为什么要用synchronized?
在我们平时开发过程中,经常会听到线程安全,线程不安全的字眼。
线程不安全都是在多线程的环境下发生的,比如多个线程同时访问一个共享变量,这就会产生脏读的现象。
线程安全就是获得实例变量的值是经过同步处理的,不会出现脏读的现象。
2.synchronized同步方法:
2.1方法内部的变量线程安全
输出结果:
2.2变量线程不安全
输出结果:
上面是由于,两个线程访问一个共同访问一个对象的实例变量,从而导致了线程不安全的情况。
解决方法就是:在方法上加上synchronized关键字。
输出结果:
加上synchronized,同步访问,所以一定是输出完a在输出b,或者是输出完b在输出a。
2.3.多个对象多个锁
上面demo,演示两个线程访问同一个类的两个不同实例的相同名称的同步方法,从打印结果发现是异步执行?
解析:关键字synchronized取的锁都是对象锁,哪个线程先执行带synchronized关键字方法,哪个线程就持有该方法所属对象锁lock,那么其他线程只能呈等待状态。前提是多个线程访问的是同一个对象。,上面创建两个类的实例对象,所以jvm会创建多个锁。
2.4.synchronized方法与锁对象
输出结果:
将程序稍作修改:
通过上面的示例可以得出结论:调用synchronized的方法,一定是排队进行的,另外要牢牢记住共享两个字,只有共享资源的读写才需要同步化,如果不是共享资源,根本没有同步的必要。
假设我在一个共享资源类加一个普通方法,程序又会怎么运行呢?
我们可以看出虽然线程a持有对象锁,但是并不影响b线程调用非synchronized方法。
总结下结论:
1.A线程持有myObject对象锁,但是并不影响其他线程访问myObject对象中的非synchronized的方法。
2.A线程先获取对象锁,如果其他线程此时想要调用synchronized的方法,只能等待A线程释放锁,也就是满足同步的原则。
2.5.脏读
上面已经使用了多个线程调用同一个方法时,为了避免数据交叉的情况,使用synchronized关键字来进行同步。
虽然在赋值时进行了同步了,但是在取值的时候还是会出现一些意外,比如脏读,发生脏读是指在读取实例变量的时候,此值已经被其他线程更改过了。
输出结果:
那么怎么解决脏读的现象呢?
其实只要保证set线程和get线程是同一个锁对象即可,加上synchronized关键字。这样就能保证,get线程只能等待set线程释放完锁,才能
去访问同步方法。如下所示:
2.6.synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象的锁的时候,再次请求此对象锁时可以再次得到该对象锁。这也证明在一个synchronized内部调用本类的其他synchronized方法时,是永远可时可以得到锁的。
输出结果:
看输出结果,上面示例主要是想说明2点:
1.线程A占有了对象锁,此时执行service1()方法,但是又要调用service2(),然后在调用servcie3(),因为service2(),service3()方法都是同步方法,这时候就可以重复使用这个锁对象,这就叫可重入,可以重复使用。
2在主线程休息0.2s后线程B尝试去调用service3(),但是线程A此时还在休眠中,所以并不会释放锁。所以线程b一定要等线程a可重入锁使用完毕,才能访问同步方法servcie3();所以打印顺序时如上面所示。
上面的示例,存在子父类继承关系的时候,子类完全可以通过可重入锁调用父类中的同步方法。
2.7 出现异常,锁自动释放
输出结果: