说到并发那么就要说一下Synchronized和ReentrantLock等等方面相关的知识。
针对互斥锁先来介绍一些引起线程安全问题的主要原因是什么?
- 存在共享数据(也称临界资源);
- 存在多条线程共同操作共享数据。
解决上面的问题的方法时:同一个时刻有且只有一个线程在操作共享数据时候其他线程必须等到该线程处理完数据后再对共享数据进行操作。
因此引入了互斥锁,其有以下几个特性:
- 互斥性
同一个时间只有一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。也被称为原子性。
- 可见性
保证在锁释放之前对共享变量所作的修改对于随后获得该锁的另外的线程时可见的(在获得锁的时候获得最新共享变量的值),否则另外一个线程可能是本地缓存的某个副本上继续操作导致不一致。
synchronized锁住的是对象而不是代码。
堆是线程共享的也是经常访问操作的地方,因此给一个对象上合适的锁时解决线程问题的关键。
同时锁又分为两类:
- 获取对象锁
获取对象锁的两种用法:
1、同步代码块
synchronized(this),synchronized(实例),而我们所说的“锁”,也就是小括号内的实例对象。
2、同步非静态方法
synchronized(method),这里的锁指的是当前对象的实例对象。
下面是一个简单的demo
public class SyncThread implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.startsWith("A")) {
async();
} else if (threadName.startsWith("B")) {
syncObjectBlock1();
} else if (threadName.startsWith("C")) {
syncObjectMethod1();
}
}
/**
* 异步方法
*/
private void async() {
try {
System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 方法中有 synchronized(this|object) {} 同步代码块
*/
private void syncObjectBlock1() {
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (this) {
try {
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* synchronized 修饰非静态方法
*/
private synchronized void syncObjectMethod1() {
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
先来看一下A线程执行async()方法的结果:
因为async()方法本身和内部属性没有被synchronized包围可以正常运行。
看一下B线程执行的情况:
B线程执行的方法中,如果使用this锁那么被this锁包着的按照顺序去执行,没有使用this锁的就会异步正常执行。
看看C线程执行的方法,使用的是synchronized去修饰方法:
被synchronized包着的整个方法都是只能一次让一个线程去访问。
- 获取类锁
获取类锁有两种方法:
1、同步代码块(synchronized(类.class)),锁是小括号()中的类对象(class 对象)
2、同步静态方法(synchronized static method) , 锁是当前对象的类对象(class 对象)
下面是一个简单的demo:
public class SyncThread implements Runnable {
@Override
public void run() {
String threadName = Thread.currentThread().getName();
if (threadName.startsWith("D")) {
syncClassBlock1();
} else if (threadName.startsWith("E")) {
syncClassMethod1();
}
}
private void syncClassBlock1() {
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
synchronized (SyncThread.class) {
try {
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private synchronized static void syncClassMethod1() {
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
try {
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果如下:
使用的区别仅仅是只有一个被static修饰,一个没有被static修饰。
不难发现执行的流程和之前是一样的,先来看一下D线程:D开始输出的语句是异步执行,于是线程1和线程2打印出没有被synchronized包着的,之后逐个获取锁按照顺序打印出对应的语句:
E线程这次是锁完全覆盖,获取整体的锁按照顺序执行:
总结一下对象锁和类锁:
- 有线程访问对象的同步代码块时候另外的线程可以访问该对象的非同步代码块;
- 如果锁住的是同一个对象,一个线程在访问对象的同步代码块的时候,另一个访问对象的同步代码块的线程会被阻塞;
- 如果锁住的是同一个对象,一个线程在访问对象的同步方法的时候,另一个访问对象的同步方法的线程会被阻塞;
- 如果锁住的是同一个对象,一个线程在访问对象的同步代码块的时候,另一个访问对象的同步方法的线程会被阻塞,反之亦然;
- 同一个类的不同对象的锁互不干扰;
- 类锁也是一把特殊的对象锁,所以同一个类的不同对象使用类锁是同步的;
- 类锁和对象锁互不干扰。