JAVA多线程学习2之synchronized
实现原理:
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。java中每一个对象都可以作为锁,这是synchronized实现同步的基础。
安全问题:
多线程引发的安全问题,由于多个线程共享了数据,且一个线程在操作(多为写)数据的过程中,可能同时对某个数据进行操作,从而导致数据出错,由此我们想到了一个解决思路,将操作共享数据的代码作为一个整体,同意时刻只让同一个线程去访问它,当这个线程执行完这段代码之后,才允许其他代码访问这个代码。这就是线程同步的思想,而synchronized就是用来实现线程同步的。
代码:
synchronized(expression){
// 需要同步的代码块
}
注:上面的expression必须是一个引用类型的变量,这里我们可以理解为java的任何对象,
如:上图的expression可以下面的obj来替代
Private Object obj=new Object();
线程同步的机制和线程锁:(java中任意对象都可以作为监听器(monitor))
1.一个线程执行到synchronized代码块,会先检查obj对象,如果为空,抛出NullPointerExpression 异常;
2.如果obj不为空,线程尝试给监听器上锁,如果监听器已经被锁,则线程被阻塞;
3.如果监听器没有被锁,则线程给监听器上锁,并且持有该锁,然后执行代码块;
4.代码块正常运行结束或者非正常结束,监听器都将自动解锁。
同步方法:
synchronized修饰方法。
同步代码块和同步方法的区别:
两者的区别主要在于同步锁上面,同步方法只能用this来作为同步锁,如果一个类中需要多个锁,为了避免锁的冲突,同步方法就不能满足,只能使用同步代码块。或者多个类中使用同一个锁,也只能用同步代码块,不能用同步方法。
总结:
Java多线程面试常见问题:
1.java中wait和sleep方法有何不同?
最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。
其它不同有:
- sleep是Thread类的静态方法,wait是Object方法。
- wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
- sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
2.编写Java代码,解决生产者——消费者问题。
生产者——消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。
使用问题4中阻塞队列实现代码来解决。但此不是唯一解决方案。
解决生产者/消费者问题的方法可分为两类:
- 采用某种机制保护生产者和消费者之间的同步;
- 在生产者和消费者之间建立一个管道。
第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。因此建议使用第一种方式来实现。
同步的核心问题在于:如何保证同一资源被多个线程并发访问时的完整性?
常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。Java语言在多线程编程上实现了完全对象化,提供了对同步机制的良好支持。
在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。管道方法不建议使用,阻塞队列方法在问题4已有描述,现只提供前两种实现方法。
3.现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。
join底层是wait方法,所以它是会释放对象锁的,而sleep在同步的方法中是不释放对象锁的,只有同步方法执行完毕,其他线程才可以执行。
4.在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁。
5.