非线程安全问题:
多个线程对同一个对象中的实例变量进行并发访问时发生,产生脏读,读取的数据可能是被更改过的。
当多个线程同时访问同一个业务对象中没有同步的方法,可能出现非线程安全问题。
解决办法:
使用synchronized声明,保证方法只能由获得锁的线程执行。
在用synchronized声明的方法中,线程进入方法时就获得锁,方法执行完就释放锁。
(1)对于方法内部的私有变量,不存在非线程安全问题。
(2)多个线程同时访问同一个对象中的同步方法一定是线程安全的。
同步synchronized在字节码指令中的原理:
1、synchronized关键字声明的同步方法:
使用flag标记ACC_SYN-CHRONIZED,当调用方法时,调用指令会检查方法的ACC_SYNCHRONIZED访问标志。如果设置,执行线程先持有同步锁,然后再执行方法,最后在方法完成时释放锁。
2、synchronized声明代码块:
使用monitorenter和monitorexit字节码指令进行同步处理。
补充:
同步:按顺序执行业务。
异步:执行A业务同时执行B业务。
注意:
(1)如果线程和业务之间属于一对一的关系,每个线程执行自己所属业务的同步方法,不存在争抢关系,不会出现非线程安全问题。只有多个线程执行相同的业务对象的同步方法时,线程 和业务对象属于多对一的关系,为了避免出现非线程安全问题,使用锁。
(2)调用synchronized声明的方法是排队进行运行的。
(3)如果A线程先持有Object对象的lock锁,B线程如果调用object对象中的synchronized方法,需要等待。
(4)java中,锁就是对象,对象可以映射成锁,哪个线程拿到锁,哪个线程就可以执行这个对象中的synchronized方法
(5)使用synchronized声明非静态方法,那么锁就是调用方法的对象。
(6)出现异常,锁自动释放
(7)通常情况下,一旦持有锁后就不再对锁对象进行更改。
synchronized锁重入:
当一个线程得到一个对象锁时,再次请求此对象锁时可以得到对象锁的。
(1)在synchronized方法/块的内部调用本类的其他synchronized方法/块时,是可以得到锁的。如果得到锁,在没有释放之前可以再次得到锁
(2)锁重入支持继承。当存在父子类继承关系时,子类是完全可以通过锁重入调用父类的同步方法。
synchronized同步语句块:
用synchronized声明方法在某些情况下是有弊端的 。
例如A线程调用同步方法执行一个长时间的任务,那么B线程等待时间就长。
可以 用synchronized语句块来解决。
synchronized代码块是将任意对象作为锁。
当两个并发线程访问同一个对象Object中的synchronzied代码块时,只有获得锁的线程才能执行代码块。
(1)不在synchronized代码块中是异步执行。
(2)synchronized(this)用当前对象作为锁。
(3)同步代码块放在非同步的syn方法中进行声明,并不能保证调用方法的线程执行同步,
方法的调用时无序的。
(4)如果用syn对对象x加锁,那么其他线程执行x对象的同步方法呈现同步效果。
使用class对象作为锁:
Class类用于描述类的基本信息,在内存中设计为单例。
静态同步synchronized方法/synchronized(class)同步块:
(1)synchronized声明静态方法,锁使用当前静态方法所在类对应的Class类的单例对象。
(2)synchronzied(class)使用Class类作为锁。