【JavaSE】线程锁详解

JDK1.5提供的Lock锁体系

synchronized是性能低效的,提供隐式的上锁解锁,而Lock接口 中提供能够由开发者实现自定义上锁解锁的显式锁,性能更高一些。

Lock接口的实现子类:
可重入锁ReentrantLock
可重入读写锁ReentrantReadWriteLock.ReadLock
Condition

实例化Lock对象

Lock ticketLock = new ReentrantLock() ; 

上锁void lock()

try{
lock.lock()
//以下代码只有一个线程可以运行
...
}
finally{
lock.unlock();//不论是否有异常,都会释放锁
}

解锁void unlock();

void lockInterrupt()throws InputedException();//响应中断锁
boolean tryLock(); // 非阻塞式响应中断能立即返回,获取锁返回true反之为false
boolean tryLock(long time,TimeUnit unit);// 超时获取锁,在超时内或未中断的情况下能获取锁
Condition newCondition(); // 获取与lock绑定的等待通知组件,

public class LockTest {
    public static void main(String[] args) {
        Runnable runnable = new LockRunn();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();

    }
}

class LockRunn implements Runnable {
    private int tick = 10;
    private Lock lock = new ReentrantLock();


    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();//加锁,一般将锁加在try块之前,解锁放在finally中
            try {
                if (this.tick > 0) {
                    System.out.println(Thread.currentThread().getName() + "剩余票数" + --this.tick);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }
}


到了JDK1.6,发生了变化,对synchronize加入了很多优化措施,有自适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK1.6上synchronize的性能并不比Lock差

ReentrantLock中所有方法实际上都是调用了器静态内部类Sync中的方法,而Sync继承了AbstractQueuedSynchronizer(AQS–简称同步器)。

ReentrantLock(独占式重入锁)详解

重入:表示能够对共享资源重复加锁,即当前线程再次获取锁时不会被阻塞。
重入的实现:如果该同步状态不为0,表示此同步状态已被线程获取,再判断持有同步状态的线程是否当前线程,如果是:同步状态+1并返回true,表示持有线程重入同步块。

释放过程:当且仅当同步状态减为0并且线程为当前线程时表示锁被正确释放。否则调用setState()将减1后的状态设置回去。
若非持有锁线程调用tryRelease()方法会抛出异常IllegalMonitorStateException

公平锁与非公平锁:

公平锁:锁的获取顺序符合时间顺序,即等待时间最长的线程最先获取锁.
要使用公平锁,调用ReentrantLock有参构造传入true,获取内置的公平锁.

公平锁的实现:利用tryAcquire()获取同步状态。首先判断当前队列是否有元素,若有,入队尾排队。
非公平锁的实现:首先会利用自旋尝试竞争锁,若失败调用tryAcquire(),tryAcquire()方法中又利用自旋争取资源。

非公平锁效率高于公平锁,因此默认选择非公平锁.
公平锁保证了获取到锁的线程一定是等待时间最长的线程,保证了请求资源时间上的绝对顺序,需要频繁的进行上下文切换,性能开销较大.
非公平锁保证系统有更大的吞吐量(效率较高),但会造成线程饥饿现象(有的线程可能永远无法获取非公平锁)

内建锁默认是独占锁,Lock锁拥有共享锁,响应中断和超时机制。

ReentrantReadWriteLock详解

读写锁:允许同一时刻被多个读线程访问,但在写线程访问时,所有的读线程与其他的写线程均会阻塞.
写线程能够获取到锁的前提条件:没有任何读、写线程拿到锁.
注:读锁不等于无锁,因为在写线程拿到锁后,读线程就不能进行。

写锁WriteLock(内部类)

写锁获取(模板方法-tryAcquire()):是独占锁
同步状态的低十六位是表示写锁,高十六位表示读锁。
写锁获取逻辑:当读锁已被读线程获取或写锁已被其他线程获取,则写线程失败;否则,当前同步状态没有被任何读写线程获取,当前线程获取写锁成功并且支持重入。

写锁释放逻辑同独占式锁的释放(release)逻辑

读锁–共享式锁(tryAcquireShared()),写锁是互斥的。
注:缓存的实现就是应用读写锁

锁降级:写锁可以降级为读锁,反过来读锁不能升级为写锁。

Condition接口的await()与signal机制

常用等待抽象方法:

void await()---同Object.wait(),直到被中断或唤醒

void awaitUninterrupt()---不响应中断,直到被唤醒。

boolean await(long time, TimeUnit unit)throws InterruptedException---同Object.wait(long timeout),多了自定义时间单位,中断、超时、被唤醒
 
boolean awaitUntil(Date deadline) throws InterruptedException  支持设置截至时间

常用唤醒抽象方法:

void signal():唤醒一个等待在Condition上的线程,将该线程由等待队列转移到同步队列中。
void signalAll():将所有等待队列在condition上的线程全部转移到同步队列中。
与内建锁wait()、notify()的区别

1.Object提供的wait()与notify()方法是与对象监视器monitor配合完成线程等待与通知机制,属于JVM底层实现。而Condition与Lock配合完成的等待通知机制属于Java语言级别,具有更高的控制与扩展性。
2. Condition独有特性:1.支持不响应中断,而Object不支持。2.支持多个等待队列,而Object只有一个。3.支持截止时间设置,而Object不支持

condition的等待队列

等待队列与同步队列共享了Node节点类,等待队列是一个单向的带由头尾节点的队列。
condition应用场景:实现有界队列。

Condition实现生产消费者模型

public class ConditionPC {
    public static void main(String[] args) {
        GoodsC goodsC = new GoodsC(20);
        Produce produce = new Produce(goodsC);
        Consumers consumers = new Consumers(goodsC);
        List<Thread> list = new ArrayList<>();
        //启动10个消费者线程
        for(int i=0;i<10;i++){
            Thread thread=new Thread(consumers,"消费者"+i);
            list.add(thread);
        }
        //启动5个消费者
        for(int i=0;i<5;i++){
            Thread thread=new Thread(produce,"生产者"+i);
            list.add(thread);
        }
        //一键启动
        for(Thread th:list){
            th.start();
        }
    }
}
class GoodsC{
    private String name;
    private int count;
    private int maxCount;
    private Lock lock=new ReentrantLock();
    //消费者等待队列
    private Condition consumerCondition=lock.newCondition();
    //生产者等待队列
    private  Condition producerConditon=lock.newCondition();
    //设置商品名称
    public void setGoods(String name){
        lock.lock();
        try {
            //当商品达到数量最大值,生产者线程进入生产者等待队列
            while (count == maxCount) {
                try {
                    System.out.println(Thread.currentThread().getName()+"还有很多商品,歇会~");
                    producerConditon.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name;
            count++;
            System.out.println(Thread.currentThread().getName()+"生产"+toString());
            //唤醒处于消费队列的线程
            consumerCondition.signalAll();
        }finally {
            lock.unlock();
        }
    }
public void getGoods(){
        try{
            lock.lock();
            while (count==0){
                System.out.println(Thread.currentThread().getName()+"还没有商品");
                try {
                    consumerCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            count--;
            System.out.println(Thread.currentThread().getName()+"消费"+toString());
            //唤醒所有生产者线程
            producerConditon.signalAll();
        }finally {

        }
}
    @Override
    public String toString() {
        return "GoodsC{" +
                "name='" + name + '\'' +
                ", count=" + count +
                '}';
    }

    public GoodsC(int maxCount) {
        this.maxCount = maxCount;
    }
}
class Produce implements Runnable {
    private GoodsC goodsC;

    public Produce(GoodsC goodsC) {
        this.goodsC = goodsC;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (true) {
            this.goodsC.setGoods("纪梵希限量");

        }
    }
}
class Consumers implements Runnable{
    private GoodsC goodsC;

    public Consumers(GoodsC goodsC) {
        this.goodsC = goodsC;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        while (true){
        this.goodsC.getGoods();
        }

    }
}

ThreadLocal

用于提供线程的局部变量,在多线程环境可以保证各个线程里的变量独立于其他线程,即ThreadLock可以为每个线程创建一个单独的副本,相当于线程的private static 类型变量,与同步机制恰好相反,因为同步机制是保证多线程环境下数据的一致性,而ThreadLocal是保证多线程环境下数据的独立性。

public class Test {
    //多线程共享
    private static String staticValue;
   //多线程独立
    private static ThreadLocal<String> threadLocal =new ThreadLocal<>();

//jdk1.8提供的初始化方法
    //private static ThreadLocal<String> threadLocal1 = ThreadLocal.withInitial(() -> "HELLO");
  
    public static void main(String[] args) throws InterruptedException {
        //main是主线程指向
        //在主线程中修改staticValue
        staticValue="主线程中的值";
        threadLocal.set("主线程修改后的值");
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                staticValue="子线程中的值";
                threadLocal.set("子线程修改后的值");
                System.out.println(threadLocal.get());
            }
        },"子线程");
        thread.start();
        thread.join();

        System.out.println(staticValue);//主线程中的值
        System.out.println(threadLocal.get());//子线程修改后的值
    }
}

ThreadLcoal实现方法:
1.set(T value)方法
set(T value) 方法中,首先获取当前线程,然后在获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不 为 null,则将 value 保存到 ThreadLocalMap 中,并用当前 ThreadLocal 作为 key;否则创建一个 ThreadLocalMap 并给到当前线程,然后保存 value。
ThreadLocalMap 相当于一个 HashMap,是真正保存值的地方。
2. get()方法
在 get() 方法中也会获取到当前线程的 ThreadLocalMap,如果 ThreadLocalMap 不为 null,则把获取 key 为当前 ThreadLocal 的值;否则调用 setInitialValue() 方法返回初始值,并保存到新创建的 ThreadLocalMap 中。

ThreadLocalMap详解
在 set,get,initialValue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 ThreadLocalMap, 如果 ThreadLocalMap 为 null,则会创建一个 ThreadLocalMap,并给到当前线程.
在使用 ThreadLocal 类型变量进行相关操作时,都会通过当前线程获取到 ThreadLocalMap 来完成操作。每个线 程的 ThreadLocalMap 是属于线程自己的,ThreadLocalMap 中维护的值也是属于线程自己的。这就保证了 ThreadLocal 类型的变量在每个线程中是独立的,在多线程环境下不会相互影响。

ThreadLocalMap 采用立即初始化的方式,当使用到set,get,initialValue 和 remove方法时,都会尝试获取ThreadLocalMap,如果ThreadLocalMap为null,就会通过ThreadLocalMap的构造方法初始化创建一个ThreadLocalMap对象,并将第一次需要保存的键值对存储到一个数组中,完成初始化工作。
ThreadLocalMap内部维护了一个哈希表,是一个Entry对象数组,用来存储数据,其初始化容量是16,Entry对象用于保存一个键值对,其中key以弱引用的方式保存。
当利用set方法将数据保存到Entry数组中时,先会通过key值即当前ThreadLocal的hashCode计算其在哈希表中存放的位置,然后判断要存放的索引位置是否已经存在Entry对象,若要存放的位置已存在Entry对象,就会进一步判断要存放的key值与已存在的key是否相等,若相等则将直接覆盖value值,若要存放位置的key值为null,说明它已经被回收了,则该entry已经无效,则会清除无效的Entry操作,然后用要保存的键值对替换该位置上的entry。若要保存的位置上没有Entry对象,那么就会将当前的键值对作为一个新的entry对象保存到该位置,然后调整table容量,并且清除无效对象,再根据需要进行扩容。

在 ThreadLocalMap 的 set(),get() 和 remove() 方法中,都有清除无效 Entry 的操作,这样做是为了降低内存泄 漏发生的可能。
Entry 中的 key 使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。
假设 Entry 的 key 没有使用弱引用的方式,而是使用了强引用:由于 ThreadLocalMap 的生命周期和当前线程一 样长,那么当引用 ThreadLocal 的对象被回收后,由于 ThreadLocalMap 还持有 ThreadLocal 和对应 value 的强 引用,ThreadLocal 和对应的 value 是不会被回收的,这就导致了内存泄漏。所以 Entry 以弱引用的方式避免了 ThreadLocal 没有被回收而导致的内存泄漏,但是此时 value 仍然是无法回收的,依然会导致内存泄漏。 ThreadLocalMap 已经考虑到这种情况,并且有一些防护措施:在调用 ThreadLocal 的 get(),set() 和 remove() 的时候都会清除当前线程 ThreadLocalMap 中所有 key 为 null 的 value。这样可以降低内存泄漏发生 的概率。所以我们在使用 ThreadLocal 的时候,每次用完 ThreadLocal 都调用 remove() 方法,清除数据,防止 内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值