一、为何会线程不安全
1、方法内的变量为线程安全
- “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了。
- 方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量是私有的特性造成的。
2、实例变量非线程安全
- 如果多个线程共同访问1个对象中的实例变量,则有可能出现“非线程安全”问题。
- 两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现“非线程安全”问题。
- 两个线程访问同一个对象中的同步方法时一定是线程安全的。
二、synchronized关键字与锁
1、synchronized方法与锁对象
- synchronized获取的是对象锁,调用用关键字synchronized声明的方法一定是排队运行的。另外需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要。
- A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
- A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。
2、多个对象多个锁
- 关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。
- 但如果多个线程访问多个对象,则JVM会创建多个锁。同步的单词为synchronized,异步的单词为asynchronized。
3、脏读
- 当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确地讲,是获得了对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法。
- 当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁后才可以调用。这时A线程已经执行了一个完整的任务,不存在脏读的基本环境。
4、synchronized锁重入
- 关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
- “可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
- 可重入锁也支持在父子类继承的环境中。当存在父子类继承关系时,子类是完全可以通过“可重入锁”调用父类的同步方法的。
5、出现异常,锁自动释放
- 线程a出现异常并释放锁,线程b进入方法正常打印,就是出现异常的锁被自动释放了。
6、同步不具有继承性
- 同步不可以继承。
三、synchronized同步代码块
1、synchronized同步代码块的使用
- 当两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
2、用同步代码块解决同步方法的弊端
- 当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该object对象中的非synchronized(this)同步代码块。不在synchronized块中就是异步执行,在synchronized块中就是同步执行。
3、synchronized代码块间的同步性
- 在使用同步synchronized(this)代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。
4、同步synchronized(this)代码块是锁定当前对象的
5、将任意对象作为对象监视器
- 多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果就是按顺序执行,也就是同步的,阻塞的。
6、细化3个结论
- 当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。
- 当其他线程执行x对象中synchronized同步方法时呈同步效果。
- 当其他线程执行x对象方法里面的synchronized(this)代码块时也呈现同步效果。
- 但需要注意:如果其他线程调用不加synchronized关键字的方法时,还是异步调用。
四、静态同步synchronized方法与synchronized(class)代码块
关键字synchronized还可以应用在static静态方法上,如果这样写,那是对当前的*.java文件对应的Class类进行持锁
synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上是给对象上锁。一个是对象锁,另外一个是Class锁,而Class锁可以对类的所有对象实例起作用。
同步synchronized(class)代码块的作用其实和synchronized static方法的作用一样。
五、数据类型String的常量池特性
在JVM中具有String常量池缓存的功能,将synchronized(string)同步块与String联合使用时,要注意常量池以带来的一些例外。
有两个String类型的变量,值都是AA,A、B两个线程持有两个变量的锁,但是因为都是常量池里的AA 所以其实是相同的锁,如果A先获取锁,就会造成线程B不能执行。这就是String常量池所带来的问题。因此在大多数的情况下,同步synchronized代码块都不使用String作为锁对象,而改用其他,比如new Object()实例化一个Object对象,但它并不放入缓存中。
六、同步synchronized方法无限等待与解决
这时就可以使用同步块来解决这样的问题。
七、多线程的死锁
Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成。在多线程技术中,“死锁”是必须避免的,因为这会造成线程的“假死”。
八、锁对象的改变
在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,则这些线程之间就是同步的;如果分别获得锁对象,这些线程之间就是异步的。
还需要提示一下,只要对象不变,既使对象的属性被改变,运行的结果还是同步。
关注@陈大白精准面试 公众号
〖特别声明〗:本文内容仅供参考,不做权威认证,如若验证其真实性,请咨询相关权威专业人士。如有侵犯您的原创版权或者图片、等版权权利请告知 wzz#tom.com,我们将尽快删除相关内容。