在实际开发中经常会用到多线程协作来处理问题,锁是处理线程安全不可缺少的机制。在JAVA中可以通过至少三种方式来实现线程锁。
1. synchronized修饰符,这种锁机制是虚拟机实现的一种锁。
2. Lock接口的实现类,这种是JAVA程序实现的锁机制。
3. CAS 通过调用底层本地方法CompareAndSet 来实现。
余下内容将结合具体的例子来看看这三种锁机制的不通,以及介绍锁实现原理。
锁机制实现介绍
在实际业务中,我们为了提高cpu的使用效率,为了提高程序执行效率,引入了多线程,而对于一些共享资源,多线程操作往往会造成线程安全问题,这时候我们往往需要一种机制可以保证多线程访问这些共享资源的时候可以先后访问。锁解决的就是这个问题。我们在访问这些资源时候需要先拿到锁,当访问介绍的时候需要释放锁,拿到锁后,其它线程就阻塞等待,一次保重共享资源多线程访问的安全。
synchronized修饰符
在虚拟机中有方法栈,对象通过堆形式存储,所有对象可以被多线程共享,synchronized是JAVA虚拟机提供的一种锁机制实现,分方法锁,对象锁,类锁,由于是虚拟机底层实现的锁机制,所以通过synchronized实现的锁机制要比程序自己实现的锁机制更加高效和方便使用。
方法锁:
1. 未加线程
public class SynchronizedMtdTest {
public static void main(String[] args) {
System.out.println("主线程开始~");
StringBuffer stringBuffer =new StringBuffer();
new Thread(new Runnable() {
@Override
public void run() {
SynchronizedMtdTest.mtd("pid="+ Thread.currentThread().getId()+",这是一个线程调用方法执行~",stringBuffer);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
SynchronizedMtdTest.mtd("pid="+ Thread.currentThread().getId()+",这是一个线程调用方法执行~",stringBuffer);
}
}).start();
System.out.println("主线程结束~");
}
public static synchronized void mtd(String text,StringBuffer stringBuffer){
try {
stringBuffer.append(text);
Thread.sleep(5000);
stringBuffer.append("\n");
System.out.println(stringBuffer);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
主线程开始~
主线程结束~
pid=10,这是一个线程调用方法执行~pid=11,这是一个线程调用方法执行~
pid=10,这是一个线程调用方法执行~pid=11,这是一个线程调用方法执行~
2. 加锁
public static synchronized void mtd(String text,StringBuffer stringBuffer){
try {
stringBuffer.append(text);
Thread.sleep(5000);
stringBuffer.append("\n");
System.out.println(stringBuffer);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果:
主线程开始~
主线程结束~
pid=10,这是一个线程调用方法执行~
pid=10,这是一个线程调用方法执行~
pid=11,这是一个线程调用方法执行~
可见在没有加锁的情况下结果是乱的,而且每次执行的循序可能不一样,而加锁后从执行结果可以看出对对象的操作是互斥操作,保证了线程的安全。
对象锁
对于方法锁保证了多线程在调用方法时候互斥,而实际中我们方法中不仅有对共享对象操作还有局部对象的操作,未了提高程序的执行效率,JAVA提供了对象锁和类锁实现。
public static void mtd(String text, StringBuffer stringBuffer) {
System.out.println("方法调用开始~");
synchronized (stringBuffer) {
stringBuffer.append(text);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringBuffer.append("\n");
System.out.println(stringBuffer);
}
System.out.println("方法调用结束~"); }
结果:
方法调用开始~
方法调用开始~
主线程结束~
pid=10,这是一个线程调用方法执行~
方法调用结束~
pid=10,这是一个线程调用方法执行~
pid=11,这是一个线程调用方法执行~
方法调用结束~
可以看到被加锁的对象在多线程访问的时候,互斥访问。
假定猜想
对象锁是保证锁内代码执行互斥?还是只保证对加锁对象做的互斥。做如下调整
public static void mtd(String text, StringBuffer stringBuffer) {
System.out.println("方法调用开始~");
synchronized (stringBuffer) {
System.out.println("-----------1--------");
stringBuffer.append(text);
System.out.println("-----------2--------");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stringBuffer.append("\n");
System.out.println(stringBuffer);
}
System.out.println("方法调用结束~");
}
结果:
主线程开始~
方法调用开始~
-----------1--------
-----------2--------
主线程结束~
方法调用开始~
pid=10,这是一个线程调用方法执行~
方法调用结束~
-----------1--------
-----------2--------
pid=10,这是一个线程调用方法执行~
pid=11,这是一个线程调用方法执行~
方法调用结束~
类锁
public static void appendStr(String text) {
synchronized (Object.class) {
stringBuffer.append(text);
try {
System.out.println("-------------");
System.out.println(stringBuffer);
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void printStr() {
synchronized (Object.class) {
System.out.println("-------------");
System.out.println(stringBuffer);
}
}
}
可见:
对于对象锁来说,可以理解为在内存有个记录锁机制的地方,所有可以共享这个空间的线程,都可以获取锁和释放锁,而且相同线程可以重复获取锁,但是其它线程在未获得锁的时候需要阻塞等待。而类锁,原理相同,不过可能存放区域不同。