高并发--卷4--Lock的使用

在前面的几卷,主要介绍了使用synchronized关键字实现线程的同步,这一讲,我们介绍一种非java虚拟机自身提供的关键字来实现线程同步,而是用ReentranLock类来实现,它在jdk1.5中被提出,用来解决synchronized解决不了的一些问题。比如使用synchronized关键字和wait和notifyAll的时候,notifyAll唤醒的是所有于synchronized锁对应的线程,这就使得不能定向唤醒线程。

异步的情况


public class T{
    public static void main(String[] args) {
        Server server = new Server();
        new Thread(new MyThread(server),"A").start();
        new Thread(new MyThread(server),"B").start();

    }
}

class MyThread implements Runnable{
    private Server server;
    public MyThread(Server server) {
        this.server = server;
    }
    @Override
    public void run() {
        try {
            server.go();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


class Server{   
    public void go() throws InterruptedException{
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(500);
        System.out.println(Thread.currentThread().getName());
    }   
}

输出结果:
B
A
B
A

使用ReentrantLock

值得注意的是,在try_catch中unlock()方法一般写在finally中。

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


public class T{
    public static void main(String[] args) {
        Server server = new Server();
        new Thread(new MyThread(server),"A").start();
        new Thread(new MyThread(server),"B").start();

    }
}

class MyThread implements Runnable{
    private Server server;
    public MyThread(Server server) {
        this.server = server;
    }
    @Override
    public void run() {
        server.go();
    }
}


class Server{   
    Lock lock = new ReentrantLock();
    public void go() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock();
        }

    }   
}

输出结果:
A
A
B
B

可以发现,使用了Lock.lock()和lock.unlock(),也能达到同步的效果。

Condition实现等待/通知

下面就用一个列子,来说明如何使用Condition来操作一个栈,这里使用一个线程入栈,当栈满了,另外一个线程出栈,当栈空了,另外一个线程又接着入栈,以此循环。

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


public class T{
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Stack<String> stack = new Stack<String>(10);
        Thread t1 = new Thread(new PopThread(lock,stack,condition));
        Thread t2 = new Thread(new PushThread(lock,stack,condition));
        t1.start();
        t2.start();
    }
}

//入栈线程
class PopThread implements Runnable{
    private Lock lock;
    private Stack<String> stack;
    private Condition condition;
    public PopThread(Lock lock,Stack<String> stack,Condition condition) {
        this.lock = lock;
        this.stack = stack;
        this.condition = condition;
    }
    @Override
    public void run() {
        lock.lock();
        while(true){
            if(!stack.isEmpty()){
                stack.pop();
            }else{
                try {
                    System.out.println("empty");
                    condition.signalAll();
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

//出栈线程
class PushThread implements Runnable{
    private Lock lock;
    private Stack<String> stack;
    private Condition condition;
    public PushThread(Lock lock,Stack<String> stack,Condition condition) {
        this.lock = lock;
        this.stack = stack;
        this.condition = condition;
    }
    @Override
    public void run() {
        lock.lock();
        while(true){
            if(!stack.isFull()){
                stack.push("123");
            }else{
                try {
                    System.out.println("full");
                    condition.signalAll();
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class StackOverflowException extends Exception{
    public StackOverflowException(){
        super();
    }
    public StackOverflowException(String message){
        super(message);
    }    
}

//该栈本身不提供同步功能
class Stack<T>{
    private int size;
    private Object data[];
    private int top;

    public Stack(int size){
        this.size = size;
        data = new Object[size];
        this.top = -1;
    }

    public void push(T e){
        if(top == size -1)
            try {
                throw new StackOverflowException("栈向上溢出");
            } catch (StackOverflowException e1) {
                e1.printStackTrace();
            }
        else data[++top] = e;
    }

    public T pop(){
        T t = null;
        if(top == -1)
            try {
                throw new StackOverflowException("栈向下溢出");
            } catch (StackOverflowException e) {
                e.printStackTrace();
            }
        return (T)data[top--];
    }

    public boolean isEmpty(){
        if(top == -1)return true;
        else return false;
    }

    public boolean isFull(){
        if(top == size - 1)return true;
        else return false;
    }
}

输出结果:
empty
full
empty
full
empty
full
empty
full

结果分析:
先输出empty的原因是,出栈线程先抢到Lock锁,由于栈空,所以打印empty,并且进入await()状态,由于await()有释放锁的特性,所以,入栈线程抢到锁,当入栈达到栈满的时候,就输出full,并且唤醒出栈线程,而自身进入await()状态。依次循环。
以上完全可以用synchronized、notifyAll、await()实现。细心的读者可能发现,使用Condition更加灵活,我们完全可以在一个lock下使用多个Condition来实现,只唤醒特定的线程,而如果使用notifyAll、await组合,会唤醒所有同步线程(这个时候通常会用一个循环来检验,直到不满足循环的条件跳出循环)。

唤醒部分线程

如果要唤醒对应的线程,那么就需要让对应的线程调用condition.await()方法,然后再调用该condition.signalAll()方法。

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

public class T{
    public static void main(String[] args) throws InterruptedException {
        final Server server = new Server();
        new Thread(new Runnable(){
            @Override
            public void run() {
                server.await1();
            }           
        }).start();
        new Thread(new Runnable() {

            @Override
            public void run() {
                server.await2();
            }
        }).start();
        Thread.sleep(500);
        //只唤醒线程1
        server.signalAll1();
    }
}


class Server{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    public void await1(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "--await");
            condition1.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }

    public void await2(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "--await");
            condition2.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }

    public void signalAll1(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "--signalAll1");
            condition1.signalAll();
        }finally{
            lock.unlock();
        }
    }

    public void signalAll2(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "--signalAll2");
            condition2.signalAll();
        }finally{
            lock.unlock();
        }
    }
}

输出结果:
Thread-0–await
Thread-1–await
main–signalAll1

  • 发现只唤醒了第一个线程

多对多实现交替打印

这里可以使用一个Condition然后通过变量控制实现交替打印,比如交替打印1,2

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

public class T{
    public static void main(String[] args) throws InterruptedException {
        Server server = new Server();
        Thread t1[] = new Thread[10];
        Thread t2[] = new Thread[10];
        for (int i = 0; i < t1.length; i++) {
            t1[i] = new Thread(new Thread1(server));
            t2[i] = new Thread(new Thread2(server));
        }
        for (int i = 0; i < t2.length; i++) {
            t1[i].start();
            t2[i].start();
        }
    }
}

class Thread1 implements Runnable{
    private Server server;
    public Thread1(Server server) {
        this.server = server;
    }
    @Override
    public void run() {
        server.print1();
    }
}

class Thread2 implements Runnable{
    private Server server;
    public Thread2(Server server) {
        this.server = server;
    }
    @Override
    public void run() {
        server.print2();
    }
}

//true打印1 false 打印2
class Server{
    private boolean flag = false;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void print1(){
        try{
            lock.lock();
            while(!flag){
                condition.await();          
            }
            System.out.println("1");
            flag = false;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
    public void print2(){
        try{
            lock.lock();
            while(flag){
                condition.await();
            }
            System.out.println("2");
            flag = true;
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }
}

输出结果:
2
1
2
1
2
1
2
1
2
1
2
1
2
1
2
1
2
1
2
1

  • 这里用一个flag变量实现的控制当前线程的等待,但是这种会在一定程度上损耗性能,原因是,sigalAll()会唤醒同类,使得同类线程任然在不满足条件的情况下去争抢锁,所以在这种情况下,建议使用两个Condition。

多对多实现交替打印的另一种方式

与上面实现的区别在于,这里直接唤醒的非同类线程,而上面的写法在唤醒线程的时候,会唤醒同类线程,导致同类线程在不满足条件的情况下也争抢锁。由此可见,这样写效率更高。

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

public class T{
    public static void main(String[] args) throws InterruptedException {
        Server server = new Server();
        Thread t1[] = new Thread[10];
        Thread t2[] = new Thread[10];
        for (int i = 0; i < t1.length; i++) {
            t1[i] = new Thread(new Thread1(server));
            t2[i] = new Thread(new Thread2(server));
        }
        for (int i = 0; i < t2.length; i++) {
            t1[i].start();
            t2[i].start();
        }
    }
}

class Thread1 implements Runnable{
    private Server server;
    public Thread1(Server server) {
        this.server = server;
    }
    @Override
    public void run() {
        server.print1();
    }
}

class Thread2 implements Runnable{
    private Server server;
    public Thread2(Server server) {
        this.server = server;
    }
    @Override
    public void run() {
        server.print2();
    }
}

//true打印1 false 打印2
class Server{
    private boolean flag = false;
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    public void print1(){
        try{
            lock.lock();
            if(!flag){
                System.out.println("同类线程await");
                condition1.await();         
            }
            System.out.println("1");
            flag = false;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock();
        }           

    }
    public void print2(){
        try{
            lock.lock();
            if(flag){
                System.out.println("同类线程await");
                condition2.await();
            }
            System.out.println("2");
            flag = true;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            lock.unlock();
        }           
    }
}

输出结果:
2
1
2
1
2
1
2
1
2
1
同类线程await
2
1
同类线程await
2
同类线程await
1
2
1
1
2
1
2

  • 同类线程只有在刚开始的start()的时候会有争抢锁的可能性,以后都会按次序的进行wait()和signal

公平锁和非公平锁

所谓的公平锁和非公平锁其实就是锁的获取顺序是否基本和线程的启动顺序一样,如果锁的获取顺序和线程的启动顺序基本一致,那么就是公平锁,否则就是非公平锁,通过在ReentrantLock(boolean)构造时设置一个boolean类型的参数来设置公平锁和非公平锁。

非公平锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T{
    public static void main(String[] args) throws InterruptedException {
        final Server server = new Server();
        Runnable t = new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " run");  
                server.go();
            }
        };

        Thread thread[] = new Thread[10];
        for (int i = 0; i < 10; i++) {
            thread[i] = new Thread(t);
        }

        for (int i = 0; i < thread.length; i++) {
            thread[i].start();
        }
    }
}


class Server{
    private Lock lock = new ReentrantLock(false);
    public void go(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " lock");
        }finally{
            lock.unlock();
        }
    }
}

输出结果:
Thread-1 run
Thread-3 run
Thread-1 lock
Thread-2 run
Thread-0 run
Thread-3 lock
Thread-7 run
Thread-7 lock
Thread-4 run
Thread-9 run
Thread-8 run
Thread-6 run
Thread-5 run
Thread-4 lock
Thread-2 lock
Thread-0 lock
Thread-9 lock
Thread-8 lock
Thread-6 lock
Thread-5 lock

  • 由此可见,线程的start顺序和lock顺序基本无关。当ReentrantLock(boolean)参数不传的时候,默认为false
公平锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class T{
    public static void main(String[] args) throws InterruptedException {
        final Server server = new Server();
        Runnable t = new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " run");  
                server.go();
            }
        };

        Thread thread[] = new Thread[10];
        for (int i = 0; i < 10; i++) {
            thread[i] = new Thread(t);
        }

        for (int i = 0; i < thread.length; i++) {
            thread[i].start();
        }
    }
}


class Server{
    private Lock lock = new ReentrantLock(true);
    public void go(){
        try{
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " lock");
        }finally{
            lock.unlock();
        }
    }
}

输出结果:
Thread-1 run
Thread-4 run
Thread-7 run
Thread-6 run
Thread-0 run
Thread-3 run
Thread-2 run
Thread-5 run
Thread-1 lock
Thread-9 run
Thread-8 run
Thread-4 lock
Thread-7 lock
Thread-6 lock
Thread-0 lock
Thread-3 lock
Thread-2 lock
Thread-5 lock
Thread-9 lock
Thread-8 lock

  • 由输出结果可以看出,线程的start()顺序和lock()顺序基本一致。当然你也注意到了ReentrantLock()参数传的是true。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值