Java并发编程一Lock和ReentrantLock初使用

Java并发编程一Lock和ReentrantLock初使用

首先我们来演示一下两个线程不断的打印字符串。

测试代码:

package lock.reentrantlock;

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

public class LockDemo {

    public static void main(String[] args) {
        new LockDemo().init();
    }

    private void init() {
        final Outputer outputer = new Outputer();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("kaven");
                }

            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("wyy");
                }

            }
        }).start();
    }

    static class Outputer {

        Lock lock = new ReentrantLock();

        //字符串打印方法,一个个字符的打印
        public void output(String name) {

            int len = name.length();
            lock.lock();
            try {
                for (int i = 0; i < len; i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println("");
            } finally {
                lock.unlock();
            }
        }
    }
}

输出:

kaven
kaven
wyy
kaven
wyy
wyy
kaven
kaven
wyy

我只粘贴了一部分输出,很明显这些输出是正常的,毕竟我们使用了ReentrantLock类(Lock接口的实现类,接下来都会以ReentrantLock类进行代码实现)在容易出现线程安全的地方上了锁,如果不上锁会怎么样?

测试代码:

package lock.reentrantlock;

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

public class LockDemo {

    public static void main(String[] args) {
        new LockDemo().init();
    }

    private void init() {
        final Outputer outputer = new Outputer();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("kaven");
                }

            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    outputer.output("wyy");
                }

            }
        }).start();
    }

    static class Outputer {

        Lock lock = new ReentrantLock();

        //字符串打印方法,一个个字符的打印
        public void output(String name) {

            int len = name.length();
//            lock.lock();
            try {
                for (int i = 0; i < len; i++) {
                    System.out.print(name.charAt(i));
                }
                System.out.println("");
            } finally {
//                lock.unlock();
            }
        }
    }
}

输出:

wkaven
yy
kaven
wyy
wyy
kaven
kaven

很明显输出出现了问题,是因为在使用共享资源的地方没有上锁,导致了线程安全问题的缘故。

我们来看看Lock接口有哪些方法。

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;

public interface Lock {

    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
  • lock():调用该方法,线程将会去尝试获取锁,当成功获取锁后,从该方法返回,如果锁被其他线程占用,则当前线程会处于休眠状态,直到获取锁为止。
  • lockInterruptibly():尝试去获取锁并且可被中断,和lock()方法的不同之处在于该方法会响应中断,即在锁的获取过程中可以中断当前线程。
  • tryLock():尝试非阻塞的获取锁,调用该方法会立即返回,如果能够获取锁则返回true,否则返回false
  • tryLock(long time, TimeUnit unit):尝试获取锁并且有超时时间,当前线程在3种情况下会返回,当前线程在超时时间内获取了锁当前线程在超时时间内被中断超时时间结束,返回false
  • unlock():释放锁。

这里不会介绍newCondition()

lock()、unlock()

Lock不会像synchronized一样,出现异常的时候会自动释放锁,所以最佳实践是在finally中释放锁,以便保证发生异常的时候锁一定会被释放。

如下代码:

package lock.lock;

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

public class MustUnlock {

    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try{
            //获取本锁保护的资源
            System.out.println(Thread.currentThread().getName()+"开始执行任务");
        }finally {
            lock.unlock();
        }
    }
}

lockInterruptibly()

测试代码:

package lock.lock;

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

public class LockInterruptibly implements Runnable {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        LockInterruptibly lockInterruptibly = new LockInterruptibly();
        Thread thread0 = new Thread(lockInterruptibly);
        Thread thread1 = new Thread(lockInterruptibly);
        thread0.start();
        thread1.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread1.interrupt();
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "尝试获取锁");
        try {
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName() + "获取到了锁");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "睡眠期间被中断了");
            } finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放了锁");
            }
        } catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName() + "获得锁期间被中断了");
        }
    }
}

输出:

Thread-0尝试获取锁
Thread-1尝试获取锁
Thread-0获取到了锁
Thread-1获得锁期间被中断了
Thread-0释放了锁

从输出也可以看出Thread-1在尝试获取锁期间被中断了,这也是synchronized所不具备的。

tryLock()、tryLock(long time, TimeUnit unit)

测试代码:

package lock.lock;

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

/**
 * 描述: 用tryLock来避免死锁
 */
public class TryLock implements Runnable {

    Lock lock = new ReentrantLock();

    public static void main(String[] args) {

        TryLock tryLock = new TryLock();
        new Thread(tryLock).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (tryLock.lock.tryLock()){
            System.out.println("第一次尝试获取锁成功");
            tryLock.lock.unlock();
        }
        else{
            System.out.println("第一次尝试获取锁失败");
        }
        try {
            if (tryLock.lock.tryLock(5 , TimeUnit.SECONDS)){
                System.out.println("第二次尝试获取锁成功");
                tryLock.lock.unlock();
            }
            else{
                System.out.println("第二次尝试获取锁失败");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        lock.lock();
        System.out.println(Thread.currentThread().getName()+" 获取了锁");
        try {
            Thread.sleep(5000);
        }catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getName()+" 释放了锁");
        }
    }
}

输出:

Thread-0 获取了锁
第一次尝试获取锁失败
Thread-0 释放了锁
第二次尝试获取锁成功

结果已经很明显了,tryLock()会非阻塞的尝试获取锁。tryLock(long time, TimeUnit unit)也会尝试获取锁并且有超时时间,有3种情况下会返回:

  1. 当前线程在超时时间内获取了锁。
  2. 当前线程在超时时间内被中断。
  3. 超时时间结束,返回false

这也是synchronized所不具备的。

ReentrantLock类还有些其他的方法和属性。

getHoldCount()

测试代码:

package lock.reentrantlock;

import java.util.concurrent.locks.ReentrantLock;

public class GetHoldCount {
    private  static ReentrantLock lock =  new ReentrantLock();

    public static void main(String[] args) {
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
    }
}

输出:

0
1
2
3
2
1
0

getHoldCount()方法会得到当前线程持有这个锁的次数,Queries the number of holds on this lock by the current thread

源码如下:

    /**
     * Queries the number of holds on this lock by the current thread.
     */
    public int getHoldCount() {
        return sync.getHoldCount();
    }

ReentrantLock是一种可重入锁,我们来实现一下。

测试代码:

package lock.reentrantlock;

import java.util.concurrent.locks.ReentrantLock;

public class RecursionDemo {

    private static ReentrantLock lock = new ReentrantLock();

    private static void accessResource() {
        lock.lock();
        try {
            System.out.println("已经对资源进行了处理");
            if (lock.getHoldCount()<5) {
                System.out.println(lock.getHoldCount());
                accessResource();
                System.out.println(lock.getHoldCount());
            }
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) {
        accessResource();
    }
}

输出:

已经对资源进行了处理
1
已经对资源进行了处理
2
已经对资源进行了处理
3
已经对资源进行了处理
4
已经对资源进行了处理
4
3
2
1

输出很明显展现了ReentrantLock是一种可重入锁。

fair属性

设置fair属性为true,则表明为公平锁,设置为false,则表明为非公平锁。

测试代码:

package lock.reentrantlock;

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

public class FairLock {

    public static void main(String[] args) {

        PrintQueue printQueue = new PrintQueue();
        Thread thread[] = new Thread[5];
        for (int i = 0; i < 5; i++) {
            thread[i] = new Thread(new Job(printQueue));
        }
        for (int i = 0; i < 5; i++) {
            thread[i].start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Job implements Runnable {

    PrintQueue printQueue;

    public Job(PrintQueue printQueue) {
        this.printQueue = printQueue;
    }

    @Override
    public void run() {
        printQueue.printJob(new Object());
    }
}

class PrintQueue {

    private Lock queueLock = new ReentrantLock(true);

    public void printJob(Object document) {

        System.out.println(Thread.currentThread().getName() + "等待打印");
        queueLock.lock();
        try {
            int duration = new Random().nextInt(3) + 1;
            System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration+"秒");
            Thread.sleep(duration * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
            System.out.println(Thread.currentThread().getName() + "打印完毕");
        }
    }
}

输出:

Thread-0等待打印
Thread-0正在打印,需要3秒
Thread-1等待打印
Thread-2等待打印
Thread-3等待打印
Thread-4等待打印
Thread-0打印完毕
Thread-1正在打印,需要2秒
Thread-1打印完毕
Thread-2正在打印,需要2秒
Thread-2打印完毕
Thread-3正在打印,需要1秒
Thread-3打印完毕
Thread-4正在打印,需要2秒
Thread-4打印完毕

从输出可以看出线程获取锁的顺序是公平的,即先来先得,如果大家觉得数据量太小说明不了问题,可以自己尝试一下用大数据量进行测试。

我们再来测试一下非公平锁的情况。

测试代码:

package lock.reentrantlock;

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

public class FairLock {

    public static void main(String[] args) {

        PrintQueue printQueue = new PrintQueue();
        Thread thread[] = new Thread[5];
        for (int i = 0; i < 5; i++) {
            thread[i] = new Thread(new Job(printQueue));
        }
        for (int i = 0; i < 5; i++) {
            thread[i].start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Job implements Runnable {

    PrintQueue printQueue;

    public Job(PrintQueue printQueue) {
        this.printQueue = printQueue;
    }

    @Override
    public void run() {
        printQueue.printJob(new Object());
    }
}

class PrintQueue {

    private Lock queueLock = new ReentrantLock(false);

    public void printJob(Object document) {

        System.out.println(Thread.currentThread().getName() + "等待打印");
        queueLock.lock();
        try {
            int duration = new Random().nextInt(3) + 1;
            System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration+"秒");
            Thread.sleep(duration * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
            System.out.println(Thread.currentThread().getName() + "打印完毕");
        }
        
        queueLock.lock();
        try {
            int duration = new Random().nextInt(3) + 1;
            System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration+"秒");
            Thread.sleep(duration * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            queueLock.unlock();
            System.out.println(Thread.currentThread().getName() + "打印完毕");
        }
    }
}

输出:

Thread-0等待打印
Thread-0正在打印,需要3秒
Thread-1等待打印
Thread-2等待打印
Thread-3等待打印
Thread-4等待打印
Thread-0打印完毕
Thread-0正在打印,需要3秒
Thread-0打印完毕
Thread-1正在打印,需要3秒
Thread-1打印完毕
Thread-1正在打印,需要2秒
Thread-1打印完毕
Thread-2正在打印,需要2秒
Thread-2打印完毕
Thread-3正在打印,需要1秒
Thread-3打印完毕
Thread-3正在打印,需要2秒
Thread-3打印完毕
Thread-4正在打印,需要2秒
Thread-4打印完毕
Thread-2正在打印,需要1秒
Thread-2打印完毕
Thread-4正在打印,需要2秒
Thread-4打印完毕

有没有看出问题来,如果是公平锁,Thread-0打印完毕后应该是Thread-1正在打印,需要3秒,而这里是Thread-0正在打印,需要3秒,很明显是不公平锁。

Thread-0等待打印
Thread-0正在打印,需要3秒
Thread-1等待打印
Thread-2等待打印
Thread-3等待打印
Thread-4等待打印
Thread-0打印完毕
Thread-0正在打印,需要3秒
Thread-0打印完毕

上面的代码改成公平锁输出如下:

Thread-0等待打印
Thread-0正在打印,需要1秒
Thread-1等待打印
Thread-2等待打印
Thread-3等待打印
Thread-4等待打印
Thread-0打印完毕
Thread-1正在打印,需要3秒
Thread-1打印完毕
Thread-2正在打印,需要3秒
Thread-2打印完毕
Thread-3正在打印,需要3秒
Thread-3打印完毕
Thread-4正在打印,需要2秒
Thread-4打印完毕
Thread-0正在打印,需要3秒
Thread-0打印完毕
Thread-1正在打印,需要1秒
Thread-1打印完毕
Thread-2正在打印,需要1秒
Thread-2打印完毕
Thread-3正在打印,需要1秒
Thread-3打印完毕
Thread-4正在打印,需要3秒
Thread-4打印完毕

这才是公平锁的输出情况。

发布了309 篇原创文章 · 获赞 448 · 访问量 71万+

猜你喜欢

转载自blog.csdn.net/qq_37960603/article/details/104363167