Lock初体验
前言
到了紧张的秋招时间点了,本想自己总结一遍所有知识点.后来看到各种大大小小其他人总结好的排版精美的文档,不禁有点灰心.经历了秋招的捶打才发现,如果仅仅依靠找到的内容,不经过自己的思考,总是会有所欠缺.要坚持写总结,积少成多,总会有进步.
锁
我一直都觉得锁的概念很模糊,总想弄懂,也看过一些文章,但总是不能很好的理解.
当多个线程访问同一个资源(我对资源的通俗理解就是某一个变量/数据/对象 - 个人理解,不对之处还请指正.)的时候,如果同时进行修改,可能就会造成和预期不一样的后果.
package com.icebear.jsutforfun.thread.lockdemo;
public class NoLock {
public static void main(String[] args) {
InnerResource innerResource = new InnerResource();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
innerResource.addNum();
}
}, "threadName").start();
}
System.out.println("最终的结果:" + innerResource.getNum()); // 小于10000
}
}
// 资源类
class InnerResource {
// 访问的资源 num
private int num;
public int getNum() {
return num;
}
public void addNum() {
num++;
}
}
造成这种结果的原因就是JMM(Java内存模型).线程对变量的操作不能直接在主内存中完成,需要将变量的数据拷贝到属于自己的工作空间完成,最后再将结果提交到主内存当中,但是在这个过程中就会造成数据的不一致.
线程同步的方式Lock
掏出经典的买票案例:
package com.icebear.jsutforfun.thread.lockdemo;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo implements Runnable {
private int tickets = 200;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName() + "我不需要争抢!");
try {
lock.lock();
if (tickets-- > 0) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "销售了第" + tickets + "张票");
} else {
break;
}
} finally {
lock.unlock();
try {
// 这里主要是让其他线程参与争抢
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这里抛出一个新问题,线程同步的方式.临界信号事互斥
这里从lock()
到unlock()
中间这段使用锁同步的代码就叫做临界区.
public class Test {
public static void main(String[] args) {
LockDemo lockDemo = new LockDemo();
Thread thread1 = new Thread(lockDemo);
Thread thread2 = new Thread(lockDemo);
thread1.start();
thread2.start();
}
}
这里的thread1
和thread2
传入的是同一个lockDemo
对象,所以在进行同步的时候,使用的锁是同一个lockDemo
对象的锁.
被线程操作的需要进行同步的对象,我们就可以理解为资源类.
假设thread1
先执行到临界区代码,此时thread2
的访问就会被阻塞,只有当thread1
执行完成之后,thread2
才能够对lockDemo
对象中的变量进行修改.