我对于锁的初次接触和简单理解
定义一个商店Shop类
public class Shop {
public void watch() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
Thread.sleep(2000);
}
public void buy() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ":" + "开始买商品!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "买完了");
Thread.sleep(2000);
}
}
这个类中有两个方法,每个方法都可以输出两次当前线程名和对应语句。为了方便区分,在这两个输出语句之间加上2秒的间隔。
public class About_LockTest {
public static void main(String[] args) {
//实例化一个Shop对象 sp
Shop sp = new Shop();
//匿名内部类使得t0和t1都执行sp中的watch方法
Thread t0 = new Thread(() -> {
try {
sp.watch();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t1 = new Thread(() -> {
try {
sp.watch();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t0.start();
t1.start();
}
}
运行该程序,发现t0和t1同时输出相应语句,这表明两个线程在同时运行。如果,对watch方法加上锁,那么再运行该程序会有什么结果呢?
public synchronized void watch() throws InterruptedException {//在public后面加上synchronized
System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
Thread.sleep(2000);
}
此时,通过输出线程名可以发现,只有一个线程在运行,而另一个线程没有运行,也就是处于阻塞状态。直接在方法名前面加上synchronized关键词,其实等价于以下语句
public void watch() throws InterruptedException {
synchronized (this) {//将整个方法体包裹在锁内,其中用this,本类对象作为钥匙
System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
Thread.sleep(2000);
}
}
线程在执行这个方法的时候,被锁住的部分必须要钥匙才能进入,每个钥匙只能同时被一个线程持有。在这里钥匙是本类对象,而在上面都是利用sp对象调用方法,所以钥匙就是sp。这个持有sp的状态,会一直持续到线程在走出synchronized包裹的范围前,此时没拿到钥匙sp的线程只能在锁外排队,处于阻塞状态。等到一个线程走出synchro-nized的作用域之后,就会释放手中的钥匙sp,此时钥匙处于自由状态,而另一个线程会得到这把钥匙,于是可以进入这个锁住的区域,可以执行里面的代码。
为了验证以上观点,我对Thread t1进行改写:
1、锁只作用于自己的作用域
Thread t1 = new Thread(() -> {
try {
sp.buy();//添加一个buy方法在watch方法前面
sp.watch;
} catch (InterruptedException e) {
e.printStackTrace();
}
});
运行程序后,发现t0在执行watch方法的同时,t1也在执行buy方法,当t0执行完watch且t1执行完buy之后,t1拿到t0的锁,继续执行watch方法。所以说,此处这把锁只是锁住了watch方法,对于buy方法而言,并无任何影响
2、同一把钥匙只能同时被同一个线程持有(忽略释放之后持有之前的自由状态)
将t0的watch方法删去,只留有buy方法:
Thread t1 = new Thread(() -> {
try {
sp.buy();//此处t1只有buy方法了
} catch (InterruptedException e) {
e.printStackTrace();
}
});
将Shop类中的buy也加上synchronized锁,这把锁的钥匙也是this本类对象,在目前认仍是sp
public synchronized void buy() throws InterruptedException {//buy方法加锁
System.out.println(Thread.currentThread().getName() + ":" + "开始买商品!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "买完了");
Thread.sleep(2000);
}
运行程序后,发现t0执行watch方法时,线程t1没有任何动作,而等到t0结束watch方法后,才开始执行buy。由于之前已经证明锁只能作用于自己的作用域,所以排除了watch的锁影响了buy(相反亦然),这就说明此处钥匙只能同时被一个线程持有,只要等钥匙释放并拿到钥匙后,另一个线程才会执行。
3、只要持有钥匙就可以打开对应锁
(在2的基础上)在Shop类中实例化一个Object对象obj,这里将watch方法锁的钥匙改为obj:
Object obj = new Object();//新生成一个obj
public void watch() throws InterruptedException {
synchronized (obj) {//将obj作为钥匙
System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
Thread.sleep(2000);
}
}
运行程序发现,t0和t1分别同时运行watch和buy方法。由1和2可以知道,不会是二者同时持有钥匙,并且因为不同锁之间相互影响而导致线程同时运行。这里是因为,watch方法使用了obj作为钥匙,t0持有的是obj;buy使用了本类对象sp作为钥匙。这两个钥匙并不相同,所以t0和t1都能直接拿到各自的钥匙(没有其他线程竞争),打开对应的锁执行方法。
4、调用不同对象的多线程的同步
新创建一个Shop对象shp,让t0只调用shp的buy方法,t1只调用原来sp的方法,将3中两个锁的钥匙都改为this
public static void main(String[] args) {
Shop sp = new Shop();
Shop shp=new Shop();//再实例化一个shp
Thread t0 = new Thread(() -> {
try {
shp.buy();//调用shp中的buy方法
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t1 = new Thread(() -> {
try {
sp.buy();//调用sp中的buy方法
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t0.start();
t1.start();
}
/**两个方法都以this作为钥匙*/
public void watch() throws InterruptedException {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
Thread.sleep(2000);
}
}
public synchronized void buy() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ":" + "开始买商品!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "买完了");
Thread.sleep(2000);
}
执行程序后发现t0和t1同时各自执行buy方法,因为他们的钥匙shp、sp都直接被持有,无需等待。如果在这种情况下同步t0和t1,可以在类中创建一个静态对象作为钥匙:
static Object obj = new Object();//静态对象
//都以静态对象为钥匙
public void watch() throws InterruptedException {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
Thread.sleep(2000);
}
}
public void buy() throws InterruptedException {
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + ":" + "开始买商品!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":" + "买完了");
Thread.sleep(2000);
}
}
静态对象是类所有对象共享的,所以不管有多少个对象,在这个类都只有一个静态对象可用,这个钥匙就唯一了,所以执行程序后一个线程先执行buy,之后另一个线程才执行buy。
总结
锁的作用范围是有限的(可自定义),不同锁对应的钥匙可以相同,相应的这把钥匙完全可以打开这些锁。同一把钥匙同时只能被同一个线程持有,并且只有这个持有钥匙的线程可以执行锁内代码,没有钥匙的线程就处于阻塞状态,等到持有钥匙的线程走出锁后,会释放钥匙,这时就有机会得到钥匙执行代码,没有得到就继续堵塞,等待下一次机会。