1、为什么要加入锁机制?
首先看一下单例设计模式中懒汉式(详情请点击博客《设计模式之--单例模式》)的一段代码:
<span style="font-size:14px;">public class Student {
private Student() {
}
//声明对象的引用,但是不创建对象
private static Student s = null;
//调用公共方法时才创建对象
public static Student getStudent() {
// 如果t是null的时候,创建对象
if (s == null) {
s = new Student();
}
//否则,返回s
return s;
}
}</span>
这段代码如果放在多线程中是不安全的,为什么呢?
假设这个程序中有两条线程1和2,那么当两条线程都执行到了if (s == null)这一步,此时1号线程抢到了CPU的执行权,1号线程就通过了if(s==null)的验证,但是当1号刚要执行s = new Student()之前,2号线程又抢到了CPU执行权,因为这时候1号还没有创建对象,所以2号线程也能通过if(s==null)的验证,来到s = new Student()这一步。之后1和2就会各自创建一个对象,那么,这个单例模式就算是创建失败了。这叫做线程安全问题。
2、线程安全问题如何产生的呢?
是线程的随机性和延迟性,线程可以随便抢夺CPU资源,谁也不知道下一刻是谁抢到资源,这导致了线程访问共享数据出现了问题。
3、如何解决线程安全问题,就要用到多线程的锁机制。
锁机制是将可能出问题的代码用锁对象锁起来,一般不会是所有的代码都有问题,所以,我们只需要找到那些可能出问题的代码锁起来,将锁起来来的代码看做一个整体,只有这个整体完毕,别人才能继续访问,这个被锁起来的整体就叫做同步代码块。这样,使得同一时间只能有一个线程来访问这一个同步代码块,当线程进入同步代码块时就会向外界传递一个消息:这块代码已经被锁了,等我出来之后你再进去。这类似于数据库中的“事务”概念。这样,就保证了每次只有一条线程执行代码块中的内容。
锁的格式:
<span style="font-size:14px;"> synchronized(锁对象)
{
需要被锁的代码;
}</span>
以上面单例模式代码为例加锁:
<span style="font-size:14px;">public class Student {
private Student() {
}
private static Student s = null;
//在方法之上加锁
public synchronized static Student getStudent() {
if (s == null) {
s = new Student();
}
return s;
}
} </span>
小结:怎么确定是否应该被锁,如何判断是否有线程安全问题?
(1)看有没有共享数据(这里的共享数据是s)。
(2)看对共享数据的操作是不是多条语句(if 判断和s的赋值)。
(3)看是不是在多线程程序中。
如果代码满足这三个条件,那么就说明应该加锁了。