前言
在编程中,很多人都会尝试使用多线程的方式去编程,但是却很难保证自己写出来的多线程程序的正确性,在多线程中如果涉及到对共享资源的并发读写,这时就会产生资源的争夺。而在资源争夺中,第一想到的就是使用锁 ,对共享资源进行数据保护。java中提供了2种基本也是最常用的锁,synchronized、Lock!但是这2种锁有什么特点?分别使用的场景?在使用过程中应该注意哪些?各自有哪些不足呢?
本文将带着上面的问题并结合源码进行仔细分析,让你对锁有一个深入的了解。
synchronized 详解
一、概念
synchronized 是java中的关键字,利用锁的机制来实现同步,来达到对共享资源保护。
锁的机制有2种特质:
1、互斥性:在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制。互斥性我们往往称作原子性
2、可见性:确保在锁被释放之前,对共享变量所做的修改,对其余的线程是可见的(也就是说在获得锁的时候应获得最新的共享变量值),否则对于另外一个线程在操作共享变量是自己在本地缓存的副本上,这样就会引发数据的不一致。
二、对象锁和类锁
在使用Synchronized之前,先了解什么是对象锁和类锁
1、对象锁
在java中,每个对象都会有一个monitor对象,这个对象其实就是java对象锁,通常也被称作为"内置锁"或"对象锁",类的对象有多个,所有对象锁也有多个,相互之间互不干涉。
2、类锁
在java中,针对每一个类都有一个帧,可以称作为"类锁",类锁实际也是通过对象锁实现的,即类的Class对象锁,每一个类有且仅有一个Class对象(JVM加载类的时候产生),所有每一个类只有一个类锁。
三、修饰方法
1、修饰实例方法:作用于当前实例加锁,在进入同步代码方法时先获取当前实例锁
2、修饰静态方法:作用于当前类对象锁,在进入同步代码方法时先获取当前对象锁(类.class)
修饰实例方法:
顾名思义就是修饰类中的实例方法,并且默认是当前对象作为锁的对象,而一个对象只有一把锁,所以同一时刻只能有一个线程执行被同步的方法,等到线程执行完方法后,其他线程才能继续执行被同步的方法。正确实例代码如下:
public class SynchronizedTest implements Runnable {
public static int count = 0;
@Override
public void run() {
for (int i = 1; i <= 1000000; i++) {
add();
}
}
public synchronized void add() {
++count;
}
public static void main(String[] args) throws Exception {
SynchronizedTest test = new SynchronizedTest();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count); //2000000
}
}
错误代码:对同一共享资源使用不同实例锁
public class SynchronizedTest implements Runnable {
private Object object;
public SynchronizedTest(Object object) {
this.object = object;
}
public static int count = 0;
@Override
public void run() {
synchronized (object) {
for (int i = 1; i <= 1000000; i++) {
add();
}
}
}
public static void add() {
++count;
}
public static void main(String[] args) throws Exception {
Object object1 = new Object();
Object object2 = new Object();
SynchronizedTest firstLock = new SynchronizedTest(object1);
SynchronizedTest secondLock = new SynchronizedTest(object2);
Thread t1 = new Thread(firstLock);
Thread t2 = new Thread(secondLock);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);//小于 20000000
}
}
运行程序后,会发现结果永远小于2000000,说明synchronized没有起到同步的作用了,说明修饰实例方法只能作用实例对象,不能作用到类对象
修饰静态方法:
public class SynchronizedTest implements Runnable {
public static int count = 0;
@Override
public void run() {
for (int i = 1; i <= 100000; i++) {
add();
}
}
public static synchronized void add() {
++count;
}
public static void main(String[] args) throws Exception {
SynchronizedTest firstLock = new SynchronizedTest();
SynchronizedTest secondLock = new SynchronizedTest();
Thread t1 = new Thread(firstLock);
Thread t2 = new Thread(secondLock);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);=2000000
}
}
作用与静态方法的时候,不管实例化多少个实例对象,结果用于等2000000,说明锁对象是当前类.class,有且仅有一把锁,最终结果和实际结果一致!
修饰代码块:正确案例
ppublic class SynchronizedTest implements Runnable {
private Object object;
public SynchronizedTest(Object object) {
this.object = object;
}
public static int count = 0;
@Override
public void run() {
synchronized (object) {
for (int i = 1; i <= 1000000; i++) {
add();
}
}
}
public static void add() {
++count;
}
public static void main(String[] args) throws Exception {
Object object = new Object();
SynchronizedTest firstLock = n