synchronized锁
数据脏读原因
在多个线程对同一个对象实例变量进行并发访问的时候,很容易引起数据脏读的问题,来看一个简单的并发脏读的实例:
1 | public class HasSelfPrivateNum { |
接着实现两个线程对HasSelfPrivateNum的方法进行访问:
1 | public class ThreadA extends Thread { |
1 | public class ThreadB extends Thread { |
然后新建两个线程来进行调用:
1 | public class Run2_private01 { |
其结果输出如下:
1 | a set over |
结果a和b都输出为200,原因在于num这个字段是一个全局的,在numRef中是共用的,在多个线程对这个字段赋值之后,会引起数据的混乱。那么如何解决并发引起的数据混乱问题呢?加上synchronized关键字就可以了,稍微修改下代码:
1 | public class HasSelfPrivateNum { |
其他初始化线程的代码不变,执行后结果如下:
1 | a set over |
锁重入,支持继承
所谓重入,就是线程得到一个对象锁时,再次请求该对象的锁可以再次进入该对象方法。
1 | public class Main { |
通过继承自Main类,来实现重入锁
1 | public class Sub extends Main { |
可以看到operateIinSub方法不仅可以调用operateIinMain方法,也可以调用实例的operateIinSub1方法,这就是锁可重入。
锁异常
在多线程遇到锁异常的时候会自动释放其所有的锁:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Service {
synchronized public void testMethod() {
if (Thread.currentThread().getName().equals("a")) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + " run beginTime=" + System.currentTimeMillis());
int i = 1;
while (i == 1) {
if (("" + Math.random()).substring(0, 8).equals("0.123456")) {
System.out.println("ThreadName=" + Thread.currentThread().getName() + " run exceptionTime=" + System.currentTimeMillis());
Integer.parseInt("a");
}
}
} else {
System.out.println("Thread b run Time=" + System.currentTimeMillis());
}
}
}
需要定义两个线程类:
1 | public class ThreadA extends Thread { |
1 | public class ThreadB extends Thread { |
主线程去调用这两个线程使其中一个抛出异常:
1 | public class Run6_exception { |
输出结果如下所示:
1 | ThreadName=a run beginTime=1517811391726 |
可以看到,抛出异常的同时线程B中的代码也开始执行了。
锁方法块
有时直接锁方法会造成执行一个同步时间长的任务,那么另外一个线程必须等待较长的时间。可以通过锁代码块来提高运行效率:
- 通过synchronized(this)同步代码块。
- synchronized(非this对象)来同步代码实现。同一个时刻只能有一个线程能执行代码块中的代码。
总结
上面是线程中锁的初步运用,在一些高并发框架里面运用的非常多,遇到多个线程数据同步的问题优先考虑加锁。