多线程之Lock显示锁

简介: 多线程之Lock显示锁

ThreadLocal的使用

除了控制资源访问外,还可以通过增加资源来保证线程安全,ThreadLocal主要解决为每个线程绑定自己的值,相当于增加了一个副本,在进行数据库访问的时候,多个线程需要自己独立的 static 成员变量吗,那就需要使用ThreadLocal。

什么是Lock显示锁

锁分为内部锁和外部锁,内部锁是通过synchronized实现的,显示锁lock是在JDK5中新增的,最常见的实现类是 ReentrantLock、ReadLock、WriteLock,可以起到 “锁” 的作用,由ReentrantLock实现类,ReentrantLock锁被称为可重入锁,它的功能比synchronized多。

Lock接口有5个方法1个条件,

public void lock() { }
public void lockInterruptibly() throws InterruptedException { }
public boolean tryLock() { }
public boolean tryLock(long timeout, TimeUnit unit){  }
public void unlock() { }
public Condition newCondition() {  }

Lock锁的使用

Lock 有 4 种加锁方法,其中 lock 是最基础的。Lock 获取锁和释放锁都是显式的,不像 synchronized 是隐式的。所以 synchronized 会在抛异常时自动释放锁,而 Lock 只能是主动释放,加解锁都必须有显式的代码控制。

lock()、unlock()方法

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Text21 {
    public static void main(String[] args) {
        Runnable r=new Runnable() {
            @Override
            public void run() {
                sm();
            }
        };
        new Thread(r).start();
        new Thread(r).start();
        new Thread(r).start();
    }
   public static Lock lock=new ReentrantLock();
    public  static  void  sm()
    {
        lock.lock();//获得锁
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
        lock.unlock();//释放锁
    }
}

lock的获得和释放就相当于synchronized代码块

trylock(定时锁)方法

trylock(long time,TimeUnit unit)的作用在给定的等待时长内锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁,通过该方法可以实现锁对象的限时等待

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockText {
    public static void main(String[] args) {
        TimeLock timeLock=new TimeLock();
        Thread t1=new Thread(timeLock);
        Thread t2=new Thread(timeLock);
        t1.start();
        t2.start();
    }
    static  class  TimeLock implements  Runnable
    {
        static ReentrantLock reentrantLock=new ReentrantLock();
        @Override
        public void run() {
            try {
                if(reentrantLock.tryLock(3, TimeUnit.SECONDS))//3秒内获得锁返回true
                {
                    System.out.println(Thread.currentThread().getName()+"获得锁");
                    Thread.sleep(4000);
                }
                else
                    System.out.println(Thread.currentThread().getName()+"没有获得锁");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                if(reentrantLock.isHeldByCurrentThread())//如果锁被当前线程持有
                {
                    reentrantLock.unlock();//释放锁
                }
            }
        }
    }
}

Thread-0在获得锁之后休眠了4秒,Thread-1尝试等待3秒获得锁,还没有获得就不会继续等待了,释放锁了。

trylock()

仅在调用的时候锁定未被其他线程持有的锁,如果调用此方法时,被其他方法占用则放弃返回false.

trylock可以用来避免死锁,线程在获取在尝试获取Thread-0,获取不到一直尝试获取,获取到了继续获取Thread-1,如果获取不到Thread-1就会释放Thread-0,这样避免了死锁,两个锁都不会被对方占用而无法释放。

lockInterruptibly方法

lockInterruptibly方法表示当前线程没有被中断就可以获得锁,如果中断就会产生异常

一个线程在等待锁,只有两种结果,要么获得锁继续执行,要么就是在等待锁,可以理解为不限时的trylock,对于可重入锁来说还有一种可能,就是在需要的时候取消对锁的请求。

通过在使用lock的时候,lockInterruptibly方法也可以避免死锁的产生

newCondition方法

关键字synchronized与wait、notify这两个方法一起使用可以实现等待/通知模式,Lock的newCondition方法返回Condition对象,Condition类也可以实现等到/通知模式。

使用notify通知时,JVM会随机唤醒,Condition类可以进行选择性通知,比较常用的两个方法:

await():会使当前线程等待,同时释放锁

signal():用于唤醒一个线程

注意:在调用Condition的await、signal方法前也需要线程持有相关的锁。例如

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionText {
    public static void main(String[] args) throws InterruptedException {
        Text24 text24=new Text24();
        text24.start();
        Thread.sleep(3000);//睡眠三秒唤醒子线程
        lock.lock();//指定锁 并锁住
        condition.signal();//在唤醒之前需要持有锁
        lock.unlock();
    }
    static Lock lock=new ReentrantLock();
    static Condition condition=lock.newCondition();
    static  class  Text24 extends Thread
    {
        @Override
        public void run() {
            lock.lock();
            System.out.println("begin---》");
            try {
                condition.await();
                System.out.println("wait--》");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                lock.unlock();
                System.out.println("解锁成功");
            }
        }
    }
}

公平锁与非公平锁

大多数情况下,锁的申请都是非公平的,如果i当线程1线程2都在请求锁A,当锁A可用时,系统只是会从阻塞队列中随机的选择一个线程,并不能保证公平性。

公平的锁会按照时间的先后顺序,保证先到先得,公平锁这一特点不会出现线程饥饿现象。

synchronized内部锁就是非公平的,Reentrantlock(boolean fair),在创建锁的时候可以传递实参为true,就可以实现公平锁。

import java.util.concurrent.locks.ReentrantLock;
public class Text25 {
    static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) {
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                while (true)
                {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName()+"获得了锁对象");
                    lock.unlock();
                }
            }
        };
        for (int i = 0; i < 5; i++) {
            new Thread(runnable).start();
        }
    }
}

这里一直是同一个线程一直获得锁对象,而不是差不多分配好的。这里明显的是非公平锁,系统倾向于再次获得已经持有的锁,这种分配是高效的但是非公平。

为了实现公平锁,只需要在ReentrantLock方法传入参数true,表示实现公平锁

但是要实现公平锁,必须要求系统维护一个有序对队列,实现成本较高,不是特别的需求,一般不使用公平锁。

Reentrantlock常用方法

int getHoldCount()//可以返回调用lock()方法的次数
int getQueueLength()//可以返回等待获得锁的线程预估数量
int getWaitQueueLength()//返回Condition条件相关的线程预估数量
boolean hasQueuedThread()//查询指定的线程是否还有线程在等待获得该锁
boolean hasWaiters()//查询线程是否有线程正在等待指定以Condition
boolean isHeldByCurrentThread()//判断当前线程是否持有该锁
boolean isLocked()//判断锁是否被线程持有


相关文章
|
13天前
|
Java
并发编程的艺术:Java线程与锁机制探索
【6月更文挑战第21天】**并发编程的艺术:Java线程与锁机制探索** 在多核时代,掌握并发编程至关重要。本文探讨Java中线程创建(`Thread`或`Runnable`)、线程同步(`synchronized`关键字与`Lock`接口)及线程池(`ExecutorService`)的使用。同时,警惕并发问题,如死锁和饥饿,遵循最佳实践以确保应用的高效和健壮。
25 2
|
14天前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
【6月更文挑战第20天】在Java多线程编程中,`synchronized`和`Lock`是两种关键的同步机制。`synchronized`作为内置关键字提供基础同步,简单但可能不够灵活;而`Lock`接口自Java 5引入,提供更复杂的控制和优化性能的选项。在低竞争场景下,`synchronized`性能可能更好,但在高并发或需要精细控制时,`Lock`(如`ReentrantLock`)更具优势。选择哪种取决于具体需求和场景,理解两者机制至关重要。
|
4天前
|
分布式计算 并行计算 安全
在Python Web开发中,Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个核心概念,它直接影响了Python程序在多线程环境下的执行效率和性能表现
【6月更文挑战第30天】Python的GIL是CPython中的全局锁,限制了多线程并行执行,尤其是在多核CPU上。GIL确保同一时间仅有一个线程执行Python字节码,导致CPU密集型任务时多线程无法充分利用多核,反而可能因上下文切换降低性能。然而,I/O密集型任务仍能受益于线程交替执行。为利用多核,开发者常选择多进程、异步IO或使用不受GIL限制的Python实现。在Web开发中,理解GIL对于优化并发性能至关重要。
22 0
|
14天前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
【6月更文挑战第20天】Java多线程同步始于`synchronized`关键字,保证单线程访问共享资源,但为应对复杂场景,`Lock`接口(如`ReentrantLock`)提供了更细粒度控制,包括可重入、公平性及中断等待。通过实战比较两者在高并发下的性能,了解其应用场景。不断学习如`Semaphore`等工具并实践,能提升多线程编程能力。从同步起点到专家之路,每次实战都是进步的阶梯。
|
1天前
|
调度 C语言
深入浅出:C语言线程以及线程锁
线程锁的基本思想是,只有一个线程能持有锁,其他试图获取锁的线程将被阻塞,直到锁被释放。这样,锁就确保了在任何时刻,只有一个线程能够访问临界区(即需要保护的代码段或数据),从而保证了数据的完整性和一致性。 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程可以包含一个或多个线程,而每个线程都有自己的指令指针和寄存器状态,它们共享进程的资源,如内存空间、文件句柄和网络连接等。 线程锁的概念
|
10天前
|
Java
Java中的`synchronized`关键字是一个用于并发控制的关键字,它提供了一种简单的加锁机制来确保多线程环境下的数据一致性。
【6月更文挑战第24天】Java的`synchronized`关键字确保多线程数据一致性,通过锁定代码块或方法防止并发冲突。同步方法整个方法体为临界区,同步代码块则锁定特定对象。示例展示了如何在`Counter`类中使用`synchronized`保证原子操作和可见性,同时指出过度使用可能影响性能。
21 4
|
14天前
|
安全 Java Python
GIL是Python解释器的锁,确保单个进程中字节码执行的串行化,以保护内存管理,但限制了多线程并行性。
【6月更文挑战第20天】GIL是Python解释器的锁,确保单个进程中字节码执行的串行化,以保护内存管理,但限制了多线程并行性。线程池通过预创建线程池来管理资源,减少线程创建销毁开销,提高效率。示例展示了如何使用Python实现一个简单的线程池,用于执行多个耗时任务。
21 6
|
14天前
|
安全 Java 开发者
揭秘!为什么大神都爱用Lock接口处理线程同步?
【6月更文挑战第20天】Java多线程中,Lock接口正替代`synchronized`成为优选,因为它提供更细粒度的控制和高级特性,如可中断、超时等待、重入及读写锁。`synchronized`的局限性在高并发场景下暴露,而Lock通过明确的`lock()`和`unlock()`确保异常安全,支持公平锁选择。通过`Condition`和`tryLock`,Lock能更好地应对中断和超时,`ReentrantLock`和`ReentrantReadWriteLock`则提升了并发性能和灵活性。
|
14天前
|
Java
多线程同步新姿势:Lock接口助你“一统江湖”!
【6月更文挑战第20天】Java多线程高手之路,不仅要懂`synchronized`,还要精通`Lock`接口。`Lock`自Java 5起提供更灵活的同步,包括可中断、超时等待和公平/非公平锁。`ReentrantLock`是重要实现,支持重入,适用于复杂场景。通过显式`lock()`和`unlock()`管理锁,避免异常导致的死锁。`Condition`接口实现精确线程控制,如生产者-消费者模式。掌握这些,你将在Java并发世界中游刃有余。
|
9天前
|
Java
java线程之读写锁
java线程之读写锁
11 0