多线程的锁相关内容

重入锁

1.重入锁基本操作:

public class ReentranLockTest implements Runnable{
    private static ReentrantLock lock = new ReentrantLock();

    public void run() {
            // TODO Auto-generated method stub
            lock.lock();
            System.out.println("i get the lock");
            System.out.println("other threads can not reach this area");
            lock.unlock();
            }
}

如果一个线程在lock.lock()到lock.unlock()之间,其他线程如果也在访问这段,将会阻塞在lock.lock()上,即在等待获取锁。

重入锁叫重是因为获取锁的那个线程可以设置多重锁,如果需要,可以:

    @Override
    public void run() {
        // TODO Auto-generated method stub

        lock.lock();
        lock.lock();
        System.out.println("i get the lock");
        lock.unlock();
        lock.unlock();

需要记得的是,锁定了几次就要解锁几次,否则就其他线程就永远无法获得锁了。

2.可中断的重入锁

首先重入锁的使用场景是,如果线程A已经占据着锁了,另外一个线程B在申请锁的过程中必然阻塞,如果这个时候线程B设置了中断变量,如果是用Synchronsized或者1中的lock.lock()来阻塞等待的话,是无法相应中断的。所以引入了 lock.lockInterruptibly();

示例代码:

public class ReentranLockTest implements Runnable{
    private static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        // TODO Auto-generated method stub//
        try {
            //由于锁被主线程持有,所以此时阻塞在这里。
            lock.lockInterruptibly();
            System.out.println("get the lock");
            lock.unlock();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            System.out.println("give up the lock");
            e.printStackTrace();
        }   

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //主线程先获取锁
        lock.lock();
        Thread t = new Thread(new ReentranLockTest());
        t.start();
        //将线程中断标志位置为中断
        t.interrupt();
        lock.unlock();
    }
}

控制台打印如下:

give up the lock
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
    at cn.senninha.concurrent.annotations.ReentranLockTest.run(ReentranLockTest.java:15)
    at java.lang.Thread.run(Thread.java:745)

线程在等待获取锁的情况下依然响应了中断,放弃对锁的申请。

3.tryLock()

    public class ReentranLockTest implements Runnable {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    @Override
    public void run() {
        // TODO Auto-generated method stub//
        try {
            //等待1s,如果1s内不能获得锁,返回false。
            if (lock.tryLock(1, TimeUnit.SECONDS)) {
                System.out.println("get the lock");
                lock.unlock();
            } else {
                System.out.println("cannot get the lock after 1s");
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        lock.lock();
        Thread t = new Thread(new ReentranLockTest(), "b");
        t.start();

    }
}

运行结果:

cannot get the lock after 1s

4.重入锁配合Conditon使用

ReentrantLock与Condition的使用与 Synchronized与wait的使用搭配有相似之处。

首先Condition由ReentrantLock.newCondition()来实例化,实际上实例化是这样:

  final ConditionObject newCondition() {
            return new ConditionObject();
        }

public class ReentranLockTest implements Runnable{
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    @Override
    public void run() {
        // TODO Auto-generated method stub//
        try {
            lock.lockInterruptibly();
            System.out.println("wait");
            System.out.println(System.currentTimeMillis());
            //等待。需要唤醒才能停止等待。
            condition.await();
            System.out.println("after wait");
            System.out.println(System.currentTimeMillis());
            lock.unlock();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            System.out.println("give up the lock");
            e.printStackTrace();
        }   

    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //首先启动线程,线程马上进入等待状态,并释放锁
        Thread t = new Thread(new ReentranLockTest());
        t.start();
        //然后主线程重新申请锁
        lock.lock();
        try {
        //等待1s后唤醒其他等待的线程。
            condition.await(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        condition.signal();
        //记得释放当前的锁
        lock.unlock();
    }
}

运行结果:

wait
1491476750599
after wait
1491476751599

如预期,线程b先获取锁,然后在调用condition.await()后释放锁,然后主线程获取了锁,并且等待了1s后唤醒在condition上等待的线程b,然后主线程释放锁,等待的b线程重新申请获取锁后继续运行。

需要注意的点:

  • 调用conditon的相关方法需要在获取锁的前提下,否则会抛出

java.lang.IllegalMonitorStateException

  • 在等待过程中会释放锁,这个和Object.wait()是一样的。

5.信号量(Semaphore)

信号量可以限制临界区可以有多少个线程进入:

public class SemaphoreDemo implements Runnable{
    //第一个参数是信号量个数,第二个是是否是公平的信号量。
    private static Semaphore semaphore = new Semaphore(5,true);

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            //申请信号量
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + System.currentTimeMillis());
            //休眠1s
            Thread.sleep(1000);
            //释放信号量
            semaphore.release();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        SemaphoreDemo demo = new SemaphoreDemo();
        //申请10个线程
        ExecutorService exec = Executors.newFixedThreadPool(10);
        for(int i = 0 ; i < 10 ; i++){
            exec.submit(demo);
        }
        try {
            /休眠3s后停止线程池
            Thread.sleep(3000);
            exec.shutdown();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

由于只允许5个线程同时进入临界区,所以十个线程会分两批进入,那么打印出的时间戳应该是差1000ms的,结果如下:

pool-1-thread-11491479241712

pool-1-thread-31491479241715

pool-1-thread-21491479241715

pool-1-thread-51491479241716

pool-1-thread-41491479241716

pool-1-thread-61491479242713

pool-1-thread-71491479242715

pool-1-thread-81491479242715

pool-1-thread-91491479242717

pool-1-thread-101491479242717

可以看到先打印出来的前5个和后5个差了1000ms

4.读写锁(ReadWriteLock)

读写锁就是可以多个读进程进入,在读多写少的应用场景,可以大大提高效率。

当前操作 新增加操作 新增操作是否阻塞
阻塞
非阻塞
阻塞
阻塞

来个先写然后读的操作示例:


public class ReadWriteLockDemo {
    //实例化读写锁
    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static ReadLock rLock = lock.readLock();
    private static WriteLock wLock = lock.writeLock();

    private static void read(Lock rLock) {
        //在申请读取锁不等待1s
        rLock.lock();
        System.out.println(Thread.currentThread() + "read get the lock");
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread() + "read release the lock");
            rLock.unlock();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private static void wirte(Lock wLock) {
        //在申请写锁后等待1s
        wLock.lock();
        System.out.println(Thread.currentThread() + "write get the lock");
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread() + "write release the lock");
            wLock.unlock();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        long start = System.currentTimeMillis();

        //读线程
        Thread t0 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                read(rLock);
            }

        }, "t0");

        //写线程
        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                wirte(wLock);
            }

        }, "t1");

        try {
            //为了确保读操作先进行
            t0.start();
            //,主线程休眠100ms
            Thread.sleep(100);
            //写线程开始
            t1.start();
            //主线程等待t1线程完成
            t1.join();
            //获取消耗时间
            System.out.println("cost time" + (System.currentTimeMillis() - start));
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

消耗时间2s,因为读操作的时候,会阻塞写操作。

Thread[t0,5,main]read get the lock

Thread[t0,5,main]read release the lock

Thread[t1,5,main]write get the lock

Thread[t1,5,main]write release the lock

cost time2003

多线程读取,将代码做一点小修改

Thread t0 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                read(rLock);
            }

        }, "t0");

        Thread t1 = new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                wirte(wLock);
            }

        }, "t1");

结果1103ms,因为我们在线程之间等待了100ms,所以1103ms减去100ms就是1s了,说明多线程的读是不阻塞的。

Thread[t1,5,main]read get the lock

Thread[t0,5,main]read release the lock

Thread[t1,5,main]read release the lock

cost time1103

5.CountDownLatch(倒计时器)

倒计时器可以设置进入临界区的线程数量到了某个数量后就进停止等待,往下执行

首先是临界区设置,每进入一个线程计数一次:

CountDownLatch.countDown();

然后一个线程等待,只有指定数量的线程进入后才会停止等待

CountDownLatch.await();

具体代码:

public class ConutDownLatchTest implements Runnable{
    //指定五个线程计数量
    private static CountDownLatch cdl = new CountDownLatch(5);

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            Thread.currentThread().sleep(1000);
            //计数
            cdl.countDown();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("count");
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ExecutorService service = Executors.newFixedThreadPool(5);
        ConutDownLatchTest test = new ConutDownLatchTest();
        for(int i = 0 ; i < 5 ; i++){
            service.submit(test);
        }
        try {
            //等待5个计数的线程进入
            cdl.await();
            System.out.println("five thread finish");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

其实就是五次调用countDown()计数后即可使之停止等待。。。循环里改成五次调用countDown()函数,然后就会发现不会等待。。直接就继续运行了。。

6.CyclicBarrier(循环栅栏)

循环栅栏,是要等到所有的线程都准备完毕了才会继续执行

public class CyclicBarrierTest implements Runnable{
    private static CyclicBarrier barrier = new CyclicBarrier(5);

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            //开始等待五个线程进入啦
            barrier.await();
            //如果是等待五个线程才继续执行,那么这里五个线程打印的时间应该是很接近的,后面看运行结果
            System.out.println("run~" + System.currentTimeMillis());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ExecutorService service = Executors.newFixedThreadPool(5);
        for(int i = 0 ; i < 5 ; i++){
            try {
                //提交线程进入,为了显示是五个线程准备好才进入的,休眠1000ms
                service.execute(new CyclicBarrierTest());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

}

运行结果:

run 1491582362091

run 1491582362092

run 1491582362092

run 1491582362092

run 1491582362092

果然是等待五个线程后才一起执行。

7.LockSupport 线程阻塞工具

与wait()相比,LockSuport不用获取某个对象的锁,并且不会抛出中断异常InterruptedExcetion

示例代码如下,

public class LockSupportTest implements Runnable{

    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName() + "before park");
        //挂起当前线程
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "after park");
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        LockSupportTest lt0 = new LockSupportTest();
        LockSupportTest lt1 = new LockSupportTest();
        Thread t1 = new Thread(lt0,"t1");
        Thread t2 = new Thread(lt1,"t2");
        t1.start();
        t2.start();
        //使线程2停止挂起
        LockSupport.unpark(t2);
    }

}

运行结果:

t1before park

t2before park

t2after park

而且LockSuppor.unpark()方法发生在LockSupprot.park()之前也不会影响

比如,稍微修改一下run方法:

@Override
    public void run() {
        // TODO Auto-generated method stub
        //挂起之前就unpark()
        LockSupport.unpark(Thread.currentThread());
        System.out.println(Thread.currentThread().getName() + "before park");
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "after park");
    }

结果:

t1before park

t1after park

t2before park

t2after park

结果当然是可以正常结束,有点像信号量的意思。。但是这个这个unpark()是不像信号量一样可以累加的。有且只有一个。

作者:senninha
链接:https://www.jianshu.com/p/4d054a417de5
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

原文地址:https://www.cnblogs.com/qiumingcheng/p/9028790.html

时间: 2024-08-27 11:30:03

多线程的锁相关内容的相关文章

java锁相关内容

什么是锁 在单线程情况下,看下面代码: public class Test { // 计数器 private Integer count = 0; // 累加操作 public void addOne() { count += 1; } // 获取计算器的值 public Integer getCount(){ return this.count; } } 始终只会被一个线程累加,调用addOne()10次,count的值一定就累加了10这是单线程情况下的运行结果, 多线程情况下,有一个线程A调

【多线程】Python进程,队列和锁相关的一些问题

1.需求 一般的服务都提供一个代理或者监听进程,接收网络数据,然后根据数据类型分发到业务处理进程. 业务处理进程再根据实际情况处理或者创建新的进程线程. 我这里有一个listen_task监听进程,定义queue_from_net队列 __listen_control负责把网络数据缓存放入queue_from_net __listen_handle负责从queue_from_net获取处理数据,并根据实际情况, 新建两个__query_schedule进程放入query_queue或者selec

sql 锁相关(转)

锁是数据库中的一个非常重要的概念,它主要用于多用户环境下保证数据库完整性和一致性. 我们知道,多个用户能够同时操纵同一个数据库中的数据,会发生数据不一致现象.即如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数据时可能会发生问题.这些问题包括:丢失更新.脏读.不可重复读和幻觉读: 1.当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,会发生丢失更新问题.每个事务都不知道其它事务的存在.最后的更新将重写由其它事务所做的更新,这将导致数据丢失.例如,两个编辑人员制作了

【原创】libevent2中锁相关代码

在 bufferevent-internal.h 中 [bufferevent的锁操作函数] /** Internal: Given a bufferevent, return its corresponding bufferevent_private. */ // 内部使用宏 // 通过 bufferevent 结构获取其所属的 bufferevent_private 结构 #define BEV_UPCAST(b) EVUTIL_UPCAST((b), struct bufferevent_

MySQL学习笔记-锁相关话题

在事务相关话题中,已经提到事务隔离性依靠锁机制实现的.在本篇中围绕着InnoDB与MyISAM锁机制的不同展开,进而描述锁的实现方式,多种锁的概念,以及死锁产生的原因. Mysql常用存储引擎的锁机制 MyISAM和MEMORY采用表级锁(table-level locking): BDB采用页面锁(page-leve locking)或表级锁,默认为页面锁: InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁: 各种锁特点 表级锁(table-level loc

并发编程-锁相关的内存语义

锁的内存语义本质上可以说是对共享变量的更新,能及时让其他线程观察到:并且通过内存屏障,组织编译器或处理器指令重排序,导致多线程下不一致的现象. 1. volatile内存语义 见上一篇文章. 2. 锁的内存语义 (1)锁的释放和获取的内存语义 当线程释放锁时,JMM会将本地内存中的共享变量同步到主内存中: 当线程获取锁时,JMM会将该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取共享变量. (2)锁内存语义的实现 ReentrantLock的实现依赖于Abstra

线程互斥锁相关程序《转载》

#include <stdio.h>#include <pthread.h>#include <unistd.h> pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;int count = 0; void* function1( void* arg ){ int tmp = 0; while( 1 ) { pthread_mutex_lock( &mutex ); tmp = count++; pthread_m

(转)mysql锁相关知识

原文地址:http://www.cnblogs.com/RicCC/archive/2009/09/25/mysql.html 存储引擎 Attribute MyISAM Heap BDB InnoDB Transactions No No Yes Yes Lock granularity Table Table Page (8 KB) Row Storage Split files In-memory Single file per table Tablespace(s) Isolation

CAS锁相关讲解

感谢GOOGLE强大的搜索,借此挖苦下百度,依靠百度什么都学习不到! 参考文档: http://www.blogjava.net/xylz/archive/2010/07/04/325206.html http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html http://www.searchsoa.com.cn/showcontent_69238.htm http://ifeve.com/atomic-op