我们使用多线程是解决 单线程的排队等待时间过长,但多线程随之而来了一个不速之客"数据不一致",共享资源导致的数据混乱--脏读,幻读,重复读 以及随之出现的各种我们经常听到经典问题,如:ABA问题
1.临界资源是什么?
设置一个变量 a = 0 , 创建2个线程 thread1 ,thread2, 线程做的事情是一样的,将变量a增加5万
这里的临界资源就是a (临界资源 : 被多个线程共享的资源)
补充:2019/02/21 :我在使用锁的时候,碰到一个问题,int/long...基础类型变量不能被锁,以前的理解是基础类型不是引用类型,没有继承Object超类,里面没有一些线程操作方法。 NOW 我觉得还是只看到了一个方面,还有共享这个点没涉及到,从JMM角度看,栈的私有的,堆是共享的,对象存储是在堆中,而基本类型的变量是存储在栈中的。
有人会问:那锁对象的同时,基本类型变量怎么就锁住了呢 ?问得好,你很聪明。举个例子 -- 漂亮小姐姐支付宝里面的钱(基本类型变量),放在她的账号里面,你能用吗?不能,那你要是追到她了(获取"对象"),那你的她的不都是她的了么,你还能用吗?
2. 临界资源会引起什么问题?
还是上面那个例子: 讲道理2个线程执行下来最终结果应该是10万,但是执行多次结果依旧不是预期的10万
...
要是出现在生产上,出现这个问题可能导致公司非常大的损失,我们怎么避免出现这种差错? NEXT
3. 解决临界资源问题的方法?
1.不使用多线程---这显然不现实,在能够用多线程提高性能的地方,弃而不用会被扣工资!!
2.多线程但线程之间不使用共享资源,那这就不存在临界资源这个问题了
3.使用线程安全的类型:AtomicInteger / AtomicReference...CurrentHashMap
4.保证同一时间临界资源只能被单个线程占用==》(1) synchronize同步锁 (2)ReentrantLock ...
synchronize==》
ReetrantLock , 这里我看到一道面试题说使用2个int类型简单实现读写锁的功能 《读写互斥,写写互斥,读读不互斥》==>
随手实现一个读写锁,不是非常严谨,但是基本点都有
/**
* @author shengwuyou 用简单的2个int 类型实现一个读写锁
* @version 创建时间:2019/2/20 17:44
*/
public class SelfReadWriterLock {
//read 和 write 是分离的,同一时间只能由一种,所以读是1 写是-1 空闲时0
private volatile int workType = 0;
//workNum 表示锁的个数
private volatile int workNum = 0;
public void tryReadLock() throws InterruptedException {
while (workType == -1){
System.out.println("获取读锁失败");
Thread.sleep(1000);
//throw new RuntimeException("try get read Lock failed,now is writing");
}synchronized (this){
workType++;
workNum++;
}
}
public void tryWriteLock() throws InterruptedException {
while (workType != 0){
System.out.println("获取写锁失败");
Thread.sleep(1000);
//throw new RuntimeException("try get read Lock failed,now is writing");
}
synchronized (this){
workType++;
workNum++;
}
}
public void unReadLock(){
synchronized (this){
if (workNum >0) {
workNum--;
}
if (workNum == 0){
workType = 0;
}
}
}
public void unWriteLock(){
synchronized (this){
if (workNum <0) {
workNum--;
}
workType = 0;
}
}
}
用手写的读写锁去锁上面的临界资源例子:
这2中方式都能实现我们想要的目的。
4. 关于锁的使用中出现的问题: 死锁 / 活锁
出现死锁的常见例子:
- A线程已经占用a资源还需要b资源,B线程已经占用b资源还需要a资源
- 线程执行完成后才释放资源,无法外部释放线程获取的资源。
/**
* @author shengwuyou 死锁的基本实现
* @version 创建时间:2019/2/20 15:58
*/
public class ThreadSiSuoTest {
/**
* 这里使用Integer,需要注意 Integer变量在 [-128,127] 之间可以通过 synchronized(integer) 获取锁
* 一旦超出需要设置成synchronized(integer.toString.intern())
*/
private Integer a = 10;
private Integer b = 20;
class Run1 implements Runnable{
@Override
public void run() {
synchronized (a){
try {
System.out.println(Thread.currentThread().getName() + ": 拿到了 a 的锁了");
Thread.sleep(2000);
synchronized (b) {
System.out.println(Thread.currentThread().getName() + ": 拿到了 b 的锁了");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Run2 implements Runnable{
@Override
public void run() {
synchronized (b){
try {
System.out.println(Thread.currentThread().getName() + ": 拿到了 b 的锁了");
Thread.sleep(2000);
synchronized (a) {
System.out.println(Thread.currentThread().getName() + ": 拿到了 a 的锁了");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void sisuo(){
Thread t1 = new Thread(new Run1());
Thread t2 = new Thread(new Run2());
t1.start();
t2.start();
}
public static void main(String[] args) {
ThreadSiSuoTest siSuoThreadTest = new ThreadSiSuoTest();
siSuoThreadTest.sisuo();
}
}
活锁 --- 例子: 最常见的就是使用 CAS机制的情况下,当期望值 一直都和内存值不一致时,会不断的重试,形成一个活锁
死锁与活锁的区别:死锁中的线程是处于block(阻塞)状态,已经走不下去了 ,活锁中的线程是一直处于run 的状态
补充:
2019/02/27:
1.死锁检测: 在jdk1.7 之后已经给我们提供检测命令(比如有jstack)