java中的锁

2019-09-23 09:15:44来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

java中的锁

引言

在java单线程中,并不会出现资源抢夺的现象,但是在多线程并发中,会出现资源抢夺现象。为了避免这种情况需要上锁

分类

可重入锁,又名递归锁

指的是同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,也即是说,线程可以进入任何一个它已经拥有的锁所同步着的代码块。

使用synchronized

class Phone{

    public void sendSMS() {
        System.out.println(Thread.currentThread().getName()+"\t invoked sendSMS()");
        sendEmail();
    }

    private void sendEmail() {
        System.out.println(Thread.currentThread().getName()+"\t ###########invoked sendEmail()");
    }
}
public class ReentrantLockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            phone.sendSMS();
        },"t1").start();

        new Thread(()->{
            phone.sendSMS();
        },"t2").start();
    }
}

结果

t1   invoked sendSMS()  //t1线程在外层方法获取锁的时候
t1   ###########invoked sendEmail()  //t1在线程进入内层方法会自动获取锁
t2   invoked sendSMS()
t2   ###########invoked sendEmail()

使用ReentrantLock

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

class Phone implements Runnable {


    Lock lock = new ReentrantLock();

    public void sendSMS() {
        System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");
        sendEmail();
    }

    private void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\t ###########invoked sendEmail()");
    }

    @Override
    public void run() {
        get();
    }

    public void get() {
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t invoked get()");
            set();
        }catch (Exception e){

        }finally {
            lock.unlock();
        }

    }

    private void set() {
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + "\t invoked set()");
        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }
}

public class ReentrantLockDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendSMS();
        }, "t1").start();

        new Thread(() -> {
            phone.sendSMS();
        }, "t2").start();


        //暂停一会线程
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println();
        
        Thread t3 = new Thread(phone,"t3");
        Thread t4 = new Thread(phone,"t4");
        t3.start();
        t4.start();

    }
}

结果

t1   invoked sendSMS()
t1   ###########invoked sendEmail()
t2   invoked sendSMS()
t2   ###########invoked sendEmail()




t3   invoked get()
t3   invoked set()
t4   invoked get()
t4   invoked set()

自旋锁

CAS循环比较并交换

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class SpinLockDemo {

    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t come in...");
        while (!atomicReference.compareAndSet(null, thread)) {

        }

    }

    public void myUnLock() {

        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t invoked myUnLock()");

    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            spinLockDemo.myLock();
            //暂停一会线程
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            spinLockDemo.myUnLock();

        }, "AA").start();


        //暂停一会线程
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        new Thread(() -> {
            spinLockDemo.myLock();

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            spinLockDemo.myUnLock();

        }, "BB").start();

    }
}

结果

AA   come in...
BB   come in...
AA   invoked myUnLock()
BB   invoked myUnLock()

读写锁

读时共享资源数据,写时独占资源数据

多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行,但是如果有一个线程想去写共享资源,就不应该再有其它线程可以对该资源进行读或写。

读-读能共存

读-写不能共存

写-写不能共存

没有使用读写锁前

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

class MyCache{
    private volatile Map<String,Object> map=new HashMap<>();


    public void put(String key, String value) {
        System.out.println(Thread.currentThread().getName()+"\t 正在写入:"+key);
        //暂停一会线程
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"\t 写入完成:");

    }

    public void get(String key) {
        System.out.println(Thread.currentThread().getName()+"\t 正在读取:");
        //暂停一会线程
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object result = map.get(key);
        System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);
    }
}
public class ReadWriteDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        //创建5个线程,写入资源数据
        for(int i=1;i<=5;i++){
            final  int tempInt = i;
            new Thread(()->{
                myCache.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }


        //创建5个线程,读取资源数据
        for(int i=1;i<=5;i++){
            final  int tempInt = i;
            new Thread(()->{
                myCache.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }
}

结果

写时并不满足原子性和独占性,整个过程必须是一个完整的统一体,中间不允许被分割,被打断

5    正在写入:5
1    正在写入:1
3    正在写入:3
2    正在读取:
4    正在写入:4
2    正在写入:2
1    正在读取:
5    正在读取:
3    正在读取:
4    正在读取:
2    写入完成:
5    读取完成:5
5    写入完成:
4    读取完成:null
2    读取完成:null
3    写入完成:
1    读取完成:null
1    写入完成:
3    读取完成:3
4    写入完成:

使用读写锁后

使用ReentrantReadWriteLock

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();

    //读写锁
    private ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();


    public void put(String key, String value) {

        rwlock.writeLock().lock();
        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
            //暂停一会线程
            try {
                TimeUnit.MICROSECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成:");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwlock.writeLock().unlock();
        }


    }

    public void get(String key) {

        rwlock.readLock().lock();
        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
            //暂停一会线程
            try {
                TimeUnit.MICROSECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwlock.readLock().unlock();
        }
    }
}

public class ReadWriteDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();

        //创建5个线程,写入资源数据
        for (int i = 1; i <= 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt + "");
            }, String.valueOf(i)).start();
        }


        //创建5个线程,读取资源数据
        for (int i = 1; i <= 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }
    }
}

结果

4    正在写入:4
4    写入完成:
2    正在写入:2
2    写入完成:
1    正在写入:1
1    写入完成:
3    正在写入:3
3    写入完成:
5    正在写入:5
5    写入完成:
1    正在读取:
4    正在读取:
2    正在读取:
3    正在读取:
5    正在读取:
3    读取完成:3
1    读取完成:1
5    读取完成:5
2    读取完成:2
4    读取完成:4

参考

一道面试题比较synchronized和读写锁 - where - ITeye博客


原文链接:https://www.cnblogs.com/lisingshen/p/11569310.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:java循环队列实现代码

下一篇:SSH框架之Spring第一篇