java高并发实战编程(一)

总览:什么是锁:锁就是一种资源,用来进行共享资源的保护操作(也可以认为锁是一条封锁线,一个对象获得了可以通过,否则就只能在隔离带后面等待,除非轮到自己获取到权限)。

一.synchronized功能拓展:重入锁 ReentrantLock

    1.性能和synchronized 相差不大代码,代码:

        

package com.fortune.util;

import java.util.concurrent.locks.ReentrantLock;

//重入锁代替synchronized 两者消耗差不多两千万次循环
public class ThreadUtils implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i=0;
    @Override
    public void run() {
        for (int j=0;j<10000000;j++){
            lock.lock();
            try {
                i++;
            }finally {
                lock.unlock();
            }
           /*synchronized (this){
               i++;
           }*/
        }
    }


    public static void main(String[] args) throws InterruptedException {
        Long st = System.currentTimeMillis();
        ThreadUtils tt = new ThreadUtils();
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
        System.out.println("消耗时间:"+(System.currentTimeMillis()-st));
    }

    总结:ReentrantLock需要手动加锁以及释放锁(显示操作),因此灵活度高,但是要在退出临界区要释放锁,否则其它线程也进入不了。之所以叫 重入锁,是因为可以反复进入,但是仅限于一个线程,如上线程可以改为:

lock.lock();
lock.lock();
try {
    i++;
}finally {
    lock.unlock();
    lock.unlock();
}

注意:锁定了几次就要释放几次,获取以及释放锁的消耗时间相比于第一次会大很多。

2.ReentrantLock 可以中断响应:对于synchronized,如果一个线程在等待锁,要么获得锁继续执行,要么保持等待,不能中途退出。而使用重入锁则可以被中断。也就是在等待锁的过程中,程序可以根据需要取消对锁的请求。

3.限时锁:在一定时间内尝试获取锁失败之后,即返回(不进行任何操作),方法Reentrant().tryLock(),如果不带参数,当有线程获取到锁后就直接返回 true,否则立即返回false,不会进行等待,不会产生死锁。代码:

public class ThreadUtils implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();

    public void run() {
        try {
            //在5s内尝试获取锁
            if (lock.tryLock(5, TimeUnit.SECONDS)){
                //这个thread应该是当前执行的线程
                System.out.println("我获取到锁了");
                Thread.sleep(6000);
                lock.unlock();  
            }else {
                System.out.println("get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
    
    public static void main(String[] args) throws InterruptedException {
       ThreadUtils threadUtils = new ThreadUtils();
       Thread t1 = new Thread(threadUtils);
       Thread t2 = new Thread(threadUtils);
       t1.start();t2.start();
    }

}

4.公平锁:大多数情况下琐是不公平的。当线程a请求了锁1,线程b也请求了锁1,当锁1可用时,到底是先分配a还是b。系统只会从等待队列中随机选取一个,不能保证其公平性。而公平锁就是为了保证先到先得,按照申请的时间顺序进行锁的分配。不会产生饥饿。总会获取到资源。而synchronized则是非公平锁,利用Public Reentrant(boolean fair);进行设置。

     根据系统调度:一个线程会倾向于再次获取已经持有的锁,也就是说一个线程第一次获取了锁之后,下一次获取锁的概率就相对来说大一点。这种方式很高效,但是是不公平的,而公平锁需要一个有序队列进行记录,非常消耗系统资源。

5.重入锁的好搭档:Condition条件

     condition对象通过调用方法实现和wait()以及notify()方法作用相同。但是 wait()和notify()都是和synchronized 关键字合作使用。 而Condition是与重入锁相关联的。通过Lock接口的Condition.newCondition()方法生成一个与当前重入锁绑定的condition实例。利用Condition对象,可以让线程进行等待或者得到通知等。

    方法:

      await() 方法会使当前线程等待,同时释放锁,当其他线程中使用signal()或者signalAll()方法时,线程会重新获得锁,并继续执行,或者线程被中断,也能跳出等待和Object.wait()方法相似

     awaitUninterruptibly()方法与 await()方法基本相同,但是不会在等待过程中响应中断。

     signal()方法用于唤醒一个在等待中的线程。相对signalAll()会唤醒所有在等待的线程。和Object.notify()方法类似。

  代码:

public class ThreadUtils implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();

    public void run() {
        lock.lock();
        try {
            condition.await();
            System.out.println("Thread is going on");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
       ThreadUtils threadUtils = new ThreadUtils();
       Thread t1 = new Thread(threadUtils);
       t1.start();
       Thread.sleep(7000);
       //通知线程t1继续执行(加锁是为了防止共享资源冲突,因为线程唤醒),因为最开始的锁已经释放过了,此时共享资源未获取锁。
        lock.lock();
        condition.signal();
       //唤醒一个线程,此时需要将共享资源的锁进行释放,否则唤醒了也获取不到锁,一直保持等待状态。
        lock.unlock();
    }

}

和Object().wait()和notify()方法一样,当线程使用Condition.await()时,要求线程持有相关的重入锁,(没获取锁进行此操作会报错)在Condition.await()调用后,这个线程会释放这把锁。同理在Condition.signal()方法调用时,也要求线程先获得相关的锁。在signal()方法调用后,系统会从当前Condition对象的等待队列中,唤醒一个线程。一旦线程被唤醒,会重新尝试获得与之绑定的重入锁,成功获取继续执行,因此在signal()方法调用之后,一般需要释放相关的锁。谦让给被唤醒的线程,否则唤醒了线程但也无法获得锁,因此无法真正继续执行。

猜你喜欢

转载自blog.csdn.net/qq_35152911/article/details/85156665
今日推荐