两个线程执行a++;
得到不我们想要的结果,比正确结果少。
原因a++不是原子操作,a++包含三个操作。
对像锁:
包括方法锁(默认锁对象为this当前实例对象) 和同步代码块锁(自己指定锁对象)。
类锁:
指synchronize修饰静态的方法或指定锁为Class对象。
public class demo implements Runnable{
Object lock1 = new Object();
Object lock2 = new Object();
@Override
public void run() {
synchronized(lock1) {
System.out.println("我是lock1。我叫"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
synchronized(lock2) {
System.out.println("我是lock2。我叫"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"运行结束");
}
}
static demo demoinstance = new demo();
public static void main(String[] args) {
Thread t1 =new Thread(demoinstance);
Thread t2 =new Thread(demoinstance);
t1.start();
t2.start();
while(t1.isAlive()||t2.isAlive()) {
}
System.out.println("finished");
}
}
1.两个线程同时访问一个对象的同步方法。
相互等待,只能有一个持有。
2.两个线程访问两个对象的同步方法。
同时并行的进行,不会相互干扰,因为锁对象不是同一个。
3.两个线程访问的是synchronized的静态方法。
对应的锁对象是同一把,故要相互等待,只能一个持有。
4.同时访问同步方法与非同步方法。
同时并行的进行,不会相互干扰,synchronized只影响被他修饰的方法,非同步方法不受影响。
5.同时访问一个类的不同的普通同步方法。
对于同一个实例,及时没有指定锁对象是哪一个,但是默认都是this对象,故相互等待。
6.同时访问静态synchronized和非静态synchronized方法。
同时并行的进行,不会相互干扰,因为锁的对象不一样,静态synchronized的锁对象是*.class,非静态synchronized方法的锁对象是this。
http://www.cnblogs.com/owenma/p/8609348.html
7.方法抛出异常后,会释放锁。
3点核心思想
- 一把锁只能被一个线程获取,没有拿到锁的线程必须等待;
- 每个实例都对应有自己的一把锁,不同实例之间互不影响;例外: 锁对象是*.class以及synchronized修饰的的是static方法的时候,所有对象公用一把锁;
- 无论是方法正常运行完毕或者方法抛出异常,都会释放锁。
性质:可重入
指同一线程的外层函数获得锁之后,内程函数可以直接再次获取。
好处: 避免重锁,提升封装性。
粒度: 线程而非调用。
情况一:同一个方法是可重入的。
/*描述: 可重入粒度测试:递归调用本方法*/
public class SynchronizedDemo {
int a = 0 ;
public static void main(String[] args) {
SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
synchronizedDemo.method1();
}
private void method1() {
System.out.println("这是method1,a= "+a);
if(a==0) {
a++;
method1();
}
}
}
情况二:可重入不要求是同一个方法。
/*描述: 可重入粒度测试:调用本类其他方法*/
public class SynchronizedDemo2 {
public static void main(String[] args) {
SynchronizedDemo2 synchronizedDemo = new SynchronizedDemo2();
synchronizedDemo.method1();
}
private synchronized void method1() {
System.out.println("我是method1");
method2();
}
private synchronized void method2() {
System.out.println("我是method2");
}
}
情况三:可重入不要求是同一个类中的。
class SynchronizedDemoSuperClass3{
public synchronized void doSometing() {
System.out.println("我是父类方法");
}
}
public class SynchronizedDemo3 extends SynchronizedDemoSuperClass3{
public synchronized void doSomething() {
System.out.println("我是子类方法");
super.doSometing();
}
public static void main(String[] args) {
SynchronizedDemo3 synchronizedDemo = new SynchronizedDemo3();
synchronizedDemo.doSomething();
}
}
加锁和释放锁的原理:
/*方法一和方法二是等价的*/
public class SynchronizedDemo2 {
Lock domelock = new ReentrantLock();
public static void main(String[] args) {
SynchronizedDemo2 synchronizedDemo = new SynchronizedDemo2();
synchronizedDemo.method1();
synchronizedDemo.method2();
}
private synchronized void method1() {
System.out.println("我是synchronized形式的锁");
}
private void method2() {
domelock.lock();
try {
System.out.println("我是lock形式的锁");
}finally {
domelock.unlock();
}
}
}
public class strive {
private Object object = new Object();
public void insert(Thread thread) {
synchronized (object) {
}
}
}
注意:一个monitorenter,两个monitorexit
可重入原理:加锁次数计数器
- JVM负责跟踪对象被加锁的次数
- 线程第一次给对象加锁的时候,计数变为1.每当这个相同的线程在此对象上再次获得锁时,计数会递增。
- 每当任务离开时,计数递减,当计数为0的时候,锁被完全释放。
可见性原理:
需要了解java内存模型(JMM):通过主内存这个共享变量存储地址,保证线程之间通讯。(线程都是由本地内存的,各个线程想要通信,必须通过主内存)
synchronized 的缺陷:
- 效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在试图获得锁的线程。
- 不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的。
- 无法知道是否成功获取到锁。
lock的方法
Lock.lock();
Lock.unlock();
Lock.trylock();//返回是boolean
Lock.trylock(long timeout,TimeUnit unit);//可以设置超时时间
常见面试问题:
1.使用注意点:
- 锁对象不能为空
- 作用域不宜过大
- 避免死锁
2.如何选择Lock和synchronized关键字?
- 建议都不使用,可以使用java.util.concurrent包中的Automic类,countDown等类
- 优先使用现成工具,如果没有就优先使用synchronized关键字,好处是写尽量少的代码就能实现功能。如果需要灵活的加解锁机制,则使用Lock接口。
3.多线程访问同步方法的各种具体情况。
思考题
- 多个线程等待同一个synchronized锁的时候,JMV如何选择下一个获取锁的是哪个线程?(调度算法)
- Synchronized使得同时只有一个线程可以执行,性能较差,有什么办法可以提升性能?
- 我想更灵活地控制锁的获取和释放,怎么办?(自己去实现一个锁)
- 什么是锁的升级、降级?什么是JVM里的偏斜锁,轻量级锁,重量级锁。
总结
一句话介绍synchronized
JVM会自动通过使用monitor来加锁和解锁,保证了同时只有一个线程可以执行指定代码,从而保证了线程安全,同时具有可重入和不可中断的性质。