Java的锁机制的一些学习

废话不多说,现在边学边写,学到啥写啥,可能很乱

1.synchronized

这个关键字就不多说了,Java中一说到锁就会想到这个关键字,关于这个关键字的学习可以自己找别的文章看。

2.ReentrantLock

java.util.concurrent.lock 中的Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。ReentrantLock 类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,但是添加了类似锁投票定时锁等候可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)

    这是引用别人博客中的一段解释,就这样看文字可能也不好理解,直接撸代码吧!

package com.coderzhangch.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author zhangqi
 * @date 2018年4月12日
 */

public class MyReentrantLock {

    /**
     * java.util.concurrent.lock 中的Lock框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类, 而不是作为语言的特性来实现。这就为Lock
     * 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了Lock ,它拥有与synchronized 相同的并发性和内存语义,
     * 但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。 (换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
     * 
     * @param args
     */
    private Lock lock = new ReentrantLock();

    // 定义一个输出的方法
    public void output() {
        
        /**
                                      用synchronized锁定一个代码块通常是这样的
                                      
              synchronized (this) {
                for (int i = 0; i < text.length(); i++) {
                    System.out.println(text.charAt(i));
                }
          }
         */
       //用ReentrantLock锁定代码块
        // 1.拿到锁
        //lock.lock();
        try {
            //2.要锁定的代码块放在try中
            for (int i = 0; i < 100; i++) {
                number--;
                System.out.println(Thread.currentThread().getName()+":"+number);
            }
        } finally {
            //3.由于lock必须手动释放锁,为了保证锁能够释放,在finally中释放
            //lock.unlock();//释放
        }

    }
    private int number = 10000;    
    
    public static void main(String[] args) {
        final MyReentrantLock myReentrantLock = new MyReentrantLock();
        //搞10个线程跑
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    myReentrantLock.output();
                }
            }).start();
        }
       
        
    }
}

上面这些代码是ReentrantLock和synchronized锁定一个代码块的操作\

先看看没有锁的时候的打印(部分):


有脏数据,并且抢着执行

再看看,放开注释:

       // 1.拿到锁
        lock.lock();
        try {
            //2.要锁定的代码块放在try中
            for (int i = 0; i < 100; i++) {
                number--;
                System.out.println(Thread.currentThread().getName()+":"+number);
            }
        } finally {
            //3.由于lock必须手动释放锁,为了保证锁能够释放,在finally中释放
            lock.unlock();//释放
        }

    }

输出(篇幅问题,只截取部分):

Thread-1:9903
Thread-1:9902
Thread-1:9901
Thread-1:9900
Thread-7:9899
Thread-7:9898
Thread-7:9897
Thread-7:9896
Thread-7:9803
Thread-7:9802
Thread-7:9801
Thread-7:9800
Thread-3:9799
Thread-3:9798
Thread-3:9797
Thread-3:9796
Thread-3:9795
Thread-3:9703
Thread-3:9702
Thread-3:9701
Thread-3:9700
Thread-4:9699
Thread-4:9698
Thread-4:9697
Thread-4:9696
Thread-4:9695
Thread-4:9694

可以看到,使用了lock锁以后,每个线程都是执行完100次循环才放开锁,其它线程才能得到锁,并且也不存在脏数据

3.ReadWriteLock

上例中展示的是和synchronized相同的功能,那Lock的优势在哪里?

先来看看这种场景,对于共享数据的读写,如果用synchronized做的话是这样的效果:

package com.coderzhangch.lock;

import java.util.Random;

/** 
* @author zhangqi 
* @date 2018年4月12日
*/

public class MyReadWriteLock {

    //共享数据
    private int data ;
    
    public synchronized void set(int data) {    
        System.out.println(Thread.currentThread().getName() + "准备写入数据");    
        try {    
            Thread.sleep(20);    
        } catch (InterruptedException e) {    
            e.printStackTrace();    
        }    
        this.data = data;    
        System.out.println(Thread.currentThread().getName() + "写入" + this.data);    
    }       
    public synchronized  void get() {    
        System.out.println(Thread.currentThread().getName() + "准备读取数据");    
        try {    
            Thread.sleep(20);    
        } catch (InterruptedException e) {    
            e.printStackTrace();    
        }    
        System.out.println(Thread.currentThread().getName() + "读取" + this.data);    
    }    
    
    public static void main(String[] args) {    
//      final Data data = new Data();    
        final MyReadWriteLock data = new MyReadWriteLock();    
//      final RwLockData data = new RwLockData();    
        
      //写入  
      for (int i = 0; i < 3; i++) {    
          Thread t = new Thread(new Runnable() {    
              @Override  
      public void run() {    
                  for (int j = 0; j < 5; j++) {    
                      data.set(new Random().nextInt(30));    
                  }    
              }    
          });  
          t.setName("Thread-W" + i);  
          t.start();  
      }    
      //读取  
      for (int i = 0; i < 3; i++) {    
          Thread t = new Thread(new Runnable() {    
              @Override  
      public void run() {    
                  for (int j = 0; j < 5; j++) {    
                      data.get();    
                  }    
              }    
          });    
          t.setName("Thread-R" + i);  
          t.start();  
      }    
  }  
}

保证每个线程对于共享数据的可见性,避免读写到脏数据

输出:

Thread-W0准备写入数据
Thread-W0写入28
Thread-W0准备写入数据
Thread-W0写入15
Thread-R2准备读取数据
Thread-R2读取15
Thread-R2准备读取数据
Thread-R2读取15
Thread-R2准备读取数据
Thread-R2读取15
Thread-R2准备读取数据
Thread-R2读取15
Thread-R2准备读取数据
Thread-R2读取15
Thread-R1准备读取数据
Thread-R1读取15
Thread-R1准备读取数据
Thread-R1读取15
Thread-R1准备读取数据
Thread-R1读取15
Thread-R1准备读取数据
Thread-R1读取15
Thread-R0准备读取数据
Thread-R0读取15
Thread-R0准备读取数据
Thread-R0读取15
Thread-R0准备读取数据
Thread-R0读取15
Thread-R0准备读取数据
Thread-R0读取15
Thread-R0准备读取数据
Thread-R0读取15
Thread-W2准备写入数据
Thread-W2写入7
Thread-W2准备写入数据
Thread-W2写入29
Thread-W2准备写入数据
Thread-W2写入6
Thread-W1准备写入数据
Thread-W1写入16
Thread-W1准备写入数据
Thread-W1写入14
Thread-W1准备写入数据
Thread-W1写入11
Thread-W2准备写入数据
Thread-W2写入7
Thread-W2准备写入数据
Thread-W2写入17
Thread-R1准备读取数据
Thread-R1读取17
Thread-W0准备写入数据
Thread-W0写入6
Thread-W1准备写入数据
Thread-W1写入10
Thread-W1准备写入数据
Thread-W1写入18
Thread-W0准备写入数据
Thread-W0写入0
Thread-W0准备写入数据
Thread-W0写入8

可以看到,并没有读到脏数据,因为都加了锁,但是对于读取可以看到,读取与读取之间也是互斥的,这样就不合理了,读取之间应该是不互斥的。

像这种场景我们就应该用读写锁ReadWriteLock:

package com.coderzhangch.lock;

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author zhangqi
 * @date 2018年4月12日
 */

public class MyReadWriteLock {

    private int data;// 共享数据
    // 创建一个读写锁
    private ReadWriteLock rwl = new ReentrantReadWriteLock();

    public void set(int data) {
        rwl.writeLock().lock();// 取到写锁
        try {
            System.out.println(Thread.currentThread().getName() + "准备写入数据");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.data = data;
            System.out.println(Thread.currentThread().getName() + "写入" + this.data);
        } finally {
            rwl.writeLock().unlock();// 释放写锁
        }
    }

    public void get() {
        rwl.readLock().lock();// 取到读锁
        try {
            System.out.println(Thread.currentThread().getName() + "准备读取数据");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "读取" + this.data);
        } finally {
            rwl.readLock().unlock();// 释放读锁
        }
    }

    public static void main(String[] args) {
        
        final MyReadWriteLock data = new MyReadWriteLock();

        // 写入
        for (int i = 0; i < 3; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 5; j++) {
                        data.set(new Random().nextInt(30));
                    }
                }
            });
            t.setName("Thread-W" + i);
            t.start();
        }
        // 读取
        for (int i = 0; i < 3; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 5; j++) {
                        data.get();
                    }
                }
            });
            t.setName("Thread-R" + i);
            t.start();
        }
    }
}

输出:

Thread-W0准备写入数据
Thread-W0写入18
Thread-W0准备写入数据
Thread-W0写入24
Thread-W0准备写入数据
Thread-W0写入22
Thread-W0准备写入数据
Thread-W0写入0
Thread-W0准备写入数据
Thread-W0写入28
Thread-W1准备写入数据
Thread-W1写入16
Thread-W1准备写入数据
Thread-W1写入2
Thread-W1准备写入数据
Thread-W1写入0
Thread-W1准备写入数据
Thread-W1写入6
Thread-W1准备写入数据
Thread-W1写入11
Thread-W2准备写入数据
Thread-W2写入9
Thread-W2准备写入数据
Thread-W2写入6
Thread-W2准备写入数据
Thread-W2写入11
Thread-R0准备读取数据
Thread-R1准备读取数据
Thread-R2准备读取数据
Thread-R0读取11
Thread-R2读取11
Thread-R1读取11
Thread-W2准备写入数据
Thread-W2写入1
Thread-W2准备写入数据
Thread-W2写入11
Thread-R0准备读取数据
Thread-R2准备读取数据
Thread-R1准备读取数据
Thread-R1读取11
Thread-R1准备读取数据
Thread-R0读取11
Thread-R0准备读取数据
Thread-R2读取11
Thread-R2准备读取数据
Thread-R2读取11
Thread-R0读取11
Thread-R0准备读取数据
Thread-R1读取11
Thread-R1准备读取数据
Thread-R2准备读取数据
Thread-R0读取11
Thread-R1读取11
Thread-R1准备读取数据
Thread-R2读取11
Thread-R0准备读取数据
Thread-R2准备读取数据
Thread-R2读取11
Thread-R1读取11
Thread-R0读取11

可以看到,读写还是互斥,写之间也互斥,但是读与读之间并不互斥。这就是读写锁之于synchronized的优势。


4.线程间通信Condition

Condition可以替代传统的线程间通信,await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。

——为什么方法名不直接叫wait()/notify()/nofityAll()?因为Object的这几个方法是final的,不可重写!

传统线程的通信方式,Condition都可以实现。

注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。

Condition的强大之处在于它可以为多个线程间建立不同的Condition

看JDK文档中的一个例子:假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition 实例来做到这一点。

——其实就是java.util.concurrent.ArrayBlockingQueue的功能


上面一段是引用别人博客的一段

Object中的wait()/notify()/nofityAll()方法是为synchronized而准备的,只能绑定在这个关键字上,而Condition是可以绑定在Lock上也可以绑定synchronized关键字。

下面就来实现一个阻塞队列来体会一下Condition

package com.coderzhangch.lock;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author zhangqi
 * @date 2018年4月12日
 */

public class MyBlockingQueueWithCondition {
    // 创建Lock对象
    private Lock lock = new ReentrantLock();
    // 获取一个操作写线程的Condition,相当于我调用writeLock.signal()方法唤醒的肯定是在做写操作的线程
    private Condition writeLock = lock.newCondition();
    // 获取一个操作读线程的Condition,相当于我调用readLock.signal()方法唤醒的肯定是在做读操作的线程
    private Condition readLock = lock.newCondition();
    private Object[] queue = new Object[100];// 创建一个缓存队列
    private int writeIndex;// 创建一个变量记录写索引
    private int readIndex;// 创建一个变量记录读索引
    private int count;// 创建一个变量记录缓存队列中的数量

    public int getWriteIndex() {
        return writeIndex;
    }

    public int getReadIndex() {
        return readIndex;
    }

    public int getCount() {
        return count;
    }

    /**
     * 写入队列的方法
     * 
     * @throws InterruptedException
     */
    public void put(Object object) throws InterruptedException {
        // 先锁住
        lock.lock();
        try {
            // 线程一进来,先判断队列是不是满了
            while (count == queue.length) {
                // 如果满了,就让写线程等待
                writeLock.await();
            }
            // 走到这就证明队列没有满,那么就把东西写到对应的索引上去,因为我们用writeIndex来记录写的索引了,所以很方便
            queue[writeIndex] = object;
            System.out.println("当前执行写操作");
            System.out.println("当前队列中读的索引是:" + readIndex);
            System.out.println("当前队列中写的索引是:" + writeIndex);
            System.out.println("当前队列中元素的数量:" + count);
            if (++writeIndex == queue.length) {
                writeIndex = 0;
            }
            count++;
            // 唤醒读线程
            readLock.signal();
        } finally {
            // 释放锁
            lock.unlock();
        }

    }

    /**
     * 从队列中拿数据的方法
     * 
     * @throws InterruptedException
     */
    public Object take() throws InterruptedException {
        // 先锁住
        lock.lock();
        try {
            while (count == 0) {
                // 如果队列空了,就让读线程等待
                readLock.await();
            }
            // 如果到这儿了就证明队列中有数据
            Object object = queue[readIndex];
            System.out.println("当前执行读操作");
            System.out.println("当前队列中读的索引是:" + readIndex);
            System.out.println("当前队列中写的索引是:" + writeIndex);
            System.out.println("当前队列中元素的数量:" + count);
            if (++readIndex == queue.length) {
                readIndex = 0;
            }
            count--;
            // 唤醒写线程
            writeLock.signal();
            return object;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        final ExecutorService writeThreadPool = Executors.newFixedThreadPool(2);
        final ExecutorService readThreadPool = Executors.newFixedThreadPool(5);
        final MyBlockingQueueWithCondition blockingQueueWithCondition = new MyBlockingQueueWithCondition();
        final Random random = new Random();
        while (true) {
            writeThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 不断的朝队列中写随机数
                        int nextInt = random.nextInt(30);
                        blockingQueueWithCondition.put(nextInt);
                        System.out.println("写入的数据:" + nextInt);
                        System.out.println();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            readThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 不断的读取队列中的数据
                        Thread.sleep(20);
                        Object take = blockingQueueWithCondition.take();
                        System.out.println("读到的数据:" + take);
                        System.out.println();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

这段代码输出的是一直到队列满了读线程才会去读,这个我搞不懂,感觉代码也没有问题。今天太晚了,等回头有时间再回来研究研究这个吧。

这个博客是为了学习别人的一篇博客而写的,不算自己写的。附上原博主的链接:https://blog.csdn.net/vking_wang/article/details/9952063



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值