今天来讲一讲java中的同步锁,本文不对基本概念进行说明并且假定读者已经知道锁的基本用途?
锁的作用:在多线程访问同一数据时提供对数据的保护,防止数据被破坏或者不一致
本文目的:帮助读者清晰的认识java中的同步锁的相关知识,并能在程序中合理的使用
强烈建议自己尝试动手用同步锁实现一个生产者消费者模型再继续往下看
生产者消费者简单模型代码参考:
public class Main{
List list = new ArrayList();
public static void main(String[] args) throws Exception {
Main t = new Main();
Consumer consumer = t.new Consumer();
Producer producer = t.new Producer();
consumer.start();
producer.start();
}
class Consumer extends Thread{
@Override
public void run() {
while (true) {
synchronized (list) {
if (list.size() > 0) {
//如果list中有元素则消费掉
for(int i = 0 ;i < list.size();i++) {
list.remove(i);
System.out.println("消费1个");
}
}else{
//如果list中没有元素则唤醒其他等待线程,当前线程进入等待
list.notifyAll();
list.wait();
}
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
while (true) {
synchronized (list) {
if (list.size() == 0) {
//如果list中没有元素则生产
list.add(1);
System.out.println("生产1个");
}else{
//如果有则唤醒其他线程执行,当前线程进入等待模式
list.notifyAll();
list.wait();
}
}
}
}
}
以上是生产者和消费者的简单模型示例。
整个程序的思路就是启动两个线程,一个是资源的生产者Producer,一个是资源的消费者Consumer,资源的容器是一个list,代码逻辑是如果生产者发现list中没有资源,就新增一个,如果有的话线程则进入等待状态,消费者恰好相反,如果有就消费,没有就等待。
上面的程序是可以按照我们设想的思路进行的,控制台会不断的打印
生产1个,消费1个,生产1个,消费1个………. ,这里涉及到几个知识点要注意
如果能够清晰的理解上面每行代码的作用,说明对同步锁的用法和基本就掌握了,如果还不是特别清楚,下面我们来详细的分析上面的代码和涉及到的知识点
第一:要保证一个线程在操作list的时候其他线程不能对它进行操作,否则会出现数据不一致的情况,所以要对list进行加锁处理,防止其他线程篡改
第二:关于 wait(),notify(),notifyAll()方法,简单的说这几个方法是让当前线程等待和唤醒的方法,是Object类的方法,因为Object是所有类的父类,所以说所有的对象都有这个方法
wait() :让当前线程去等待该对象的锁,那究竟是谁的锁呢?谁调用的就去获取谁的锁,比如说上面调用了 list.wait(), 这就表示让当前线程去等待,要调用wait()方法是有要求的,要求是当前线程必须已经拥有了对象的锁,才能调用wait()方法,这里有的读者可能就有点晕了,既然要拥有锁之后才能调用wait(), 那调用wait是要等待什么呢? 这就和下面的一个方法产生关联了,调用wait(),就是等待另一个线程调用notify()方法。
notify() :这个方法和上面的wait是对应的,是用来唤醒等待该资源的线程,相当于说,哎,兄弟我要把锁释放了,你们拿去用吧!,然后其他某个的线程就获取到了锁去继续干活儿了,还有一个方法叫notifyAll,作用好像是和notify一样,从jdk的说明文档中没有清晰的看出区别,使用上效果好像是一样的。
对象锁、类锁、synchronized同步方法,以及synchronized同步代码块究竟是什么?它们之间又之间是什么关系?
对象锁和synchronzied方法,请看如下代码
// java中每个对象都有一把同步锁,obj1和obj2各有一把一把同步锁
Object obj1 = new Object();
Object obj2 = new Object();
synchronized就是去获取对象的同步锁的意思,请看如下代码
public void test1(){
synchronized(obj1){
System.out.println("执行了....");
}
}
上面的代码是要获取obj1对象的锁,如果obj1对象的锁被其他的线程持有了,那程序就会一直停在synchronzied那里,直到获取到obj1的锁才往下执行
但我们经常看到的同步代码是这样
public class Main{
public synchronized void doInSyn(){
System.out.println("执行咯.......");
}
}
线程在执行doInSyn()方法时首先需要获取当前对象的锁才能执行,其实和下面这种写法的效果是一样
public class Main{
public void doInSyn(){
//代表先去获取this对象的同步锁才能执行
synchronized(this){
System.out.println("执行咯.......");
}
}
}
相信到这里应该已经理解对象锁了,但有时我们会看到如下的写法
public static synchronized doInSync(){
}
这里的synchronzied还是去获取当前对象的同步锁再执行的意思吗?当然,答案是否定的,所以下面我们要讲的是类的锁,虽然名称和对象锁不一样但其实原理是一样的
Java中静态方法是属于类的,所以静态方法的锁也是类的锁
Java中静态方法是属于类的,下面我们通过代码来解释
public class ClassLockTest extends Thread {
public static void main(String[] args) {
ClassLockTest t1 = new ClassLockTest();
t1.setName("T_One");
ClassLockTest t2 = new ClassLockTest();
t2.setName("T_Two");
t1.start();
t2.start();
}
@Override
public void run() {
try {
print(this.getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static synchronized void print(String threadName){
int count = 5;
while(count-- > 0){
Thread.sleep(100);
System.out.println(threadName + " "+count);
}
}
}
结果:
T_One 4
T_One 3
T_One 2
T_One 1
T_One 0
T_Two 4
T_Two 3
T_Two 2
T_Two 1
T_Two 0
我们可以看到虽然同时启动了两个线程去打印,但顺序完全没有乱,就是因为静态同步方法 (static synchronized修饰)会先去获取对象的锁,然后再执行,如果没有获取到则会一直等待,直到其他线程执行结束释放锁
上面的方法等同于这种写法
public static void print(String threadName) {
synchronized(ClassLockTest.class){
int count = 5;
while(count-- > 0){
Thread.sleep(100);
System.out.println(threadName + " "+count);
}
}
}