lock锁操作

1.复习Synchronized关键字

1.概述

Synchronized是java的关键字

2.使用

修饰一个代码块,被修饰的代码称为同步代码块
修饰一个方法
修饰一个静态方法:作用范围是这个静态方法,作用的对象是这个类的所有对象;
修饰一个类;其作用的范围是 synchronized 后面括号括起来的部分,作用主的对象是这个类的所有对象

3.注意

Synchronized的方法是不可被继承的,当子类覆盖率这个方法会导致锁失效

2.什么是Lock接口

1.概念

Lock 锁实现提供了比使用Synchronized方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比 synchronized 更多的功能。(可以手动加锁)

2.什么是可重入锁

加了可重入锁后,其他线程在外等待,等可重入锁释放。下一个线程进入再次加锁,释放锁。循环执行

3.lock和Synchronized的区别

Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个接口,通过实现类可以实现同步访问;
Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断
通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
Lock 可以提高多个线程进行读操作的效率。

从性能上讲Lock的性能高于Synchronized,竞争不激烈两者差不多。

4.ReentrantLock实现类

ReentrantLock 是唯一实现了 Lock 接口的类,并且 ReentrantLock 提供了更多的方法。下面通过一些实例看具体看一下如何使用。

  1. 实现可重入锁
Lock lock=new  ReentrantLock();
  1. 手动加锁
 lock.lock();
  1. 手动释放锁
lock.unlock()

3.线程间通信

因为线程执行不一定,所以通过线程间通信让线程能有序执行。
synchronized方式加锁:使用wait() 和notifyAll()方法。注意必须要在对象加锁的情况下使用wait()等待的时候会释放锁。其他线程可以操作该对象。wait的线程进入等待池中。notifyAll唤醒所有线程。从那里唤醒就从哪里继续执行。wait()必须要和while一起使用,防止出现虚假唤醒。
lock方式手动加锁:是要实现lock接口,调用newCondition()方法,返回一个新Condition绑定到该Lock实例。调用await()方法等待和signalAll() 方法唤醒等待线程
await()的详情:相关的锁Condition(开锁钥匙)以原子方式释放,并且当前线程的线程调度目的就退出,一直处于休眠状态等待下列情况唤醒:
一些其它线程调用signal()方法或当前线程碰巧被选择作为被唤醒线程;
发生"虚假唤醒"调用interrupt()方法中断进程
代码示例

class print{
    private int number =0;
    Lock lock = new ReentrantLock();
    /*钥匙实例化*/
    Condition a=lock.newCondition();
    Condition b=lock.newCondition();
    Condition c=lock.newCondition();
    public void printA(){
        lock.lock();
        try {
            while (number!=0){
                a.await();
            }
            for(int i=1;i<=5;i++){
                System.out.println(Thread.currentThread().getName()+"打印"+i);
            }
            number =1;
            b.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    public void printB(){
        lock.lock();
        try {
            while (number!=1){
                b.await();
            }
            for(int i=1;i<=10;i++){
                System.out.println(Thread.currentThread().getName()+"打印"+i);
            }
            number =2;
            c.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    public void printC(){
        lock.lock();
        try {
            while (number!=2){
                c.await();
            }
            for(int i=1;i<=15;i++){
                System.out.println(Thread.currentThread().getName()+"打印"+i);
            }
            number =0;//标志位
            a.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}
public class LockPrint {
    public static void main(String[] args) {
        print print = new print();
        new Thread(()->{
            for(int j=0;j<2;j++){
                print.printA();
            }
        },"A").start();
        new Thread(()->{
            for(int j=0;j<2;j++){
                print.printB();
            }
        },"B").start();
        new Thread(()->{
            for(int j=0;j<2;j++){
                print.printC();
            }
        },"C").start();
    }
}

4.使用Lock实现买票实例

class Insert{
    private  int number =30;

    Lock lock=new ReentrantLock();
    /*买票*/
    public void sale(){
        /*手动加锁*/
        lock.lock();
        if(number==0){
            System.out.println("买票结束");
            return;
        }
        try {
            number--;
            System.out.println(Thread.currentThread().getName()+"卖出一张票");
            System.out.println("剩余票"+number);
        }finally {
            /*手动释放锁*/
            lock.unlock();
        }

    }
}

public class LockSaleTicket {
    public static void main(String[] args) {
        Insert ticket = new Insert();
        Thread thread = new Thread() {
            @Override
            public void run() {
                for(int j=0;j<15;j++){
                    ticket.sale();
                }

            }
        };
        Thread thread1 =new Thread(()->{
            for(int j=0;j<15;j++){
                ticket.sale();
            }
        });
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int j=0;j<15;j++){
                    ticket.sale();
                }

            }
        },"xiao").start();
        thread.start();
        thread1.start();

    }
}

5.集合的线程安全

java.util.ConcurrentModificationException多线程同时修改集合会出现该异常。

for(Integer i: arr){ 
    arr.remove(i);
}
Itr.next()调用Itr.checkForComodification()时,
会发现modCount和expectedModCount两个值不相等!
因为在这个删除操作的过程中没有对expectedModCount重新赋值,所以就抛出异常了。
for(**:**)的本质上还是集合的迭代。

1.如何解决Arraylist线程(并发修改异常)不安全问题?

1.通过vector解决(不常用)
具体是vector中的方法加上了synchronized
2.使用Collections工具类(不常用)

具体是调用工具类方法。Collections.synchronizdList(new ArrayList());

3.使用CopyOnWriteArrayList类创建一个安全的集合对象(写时复制技术)

其中的add方法使用了lock手动加锁操作

2.如何解决HashSet线程不安全问题

hashset的底层也是HashMap6
答:使用CopyOnWriteArraySet创建对应的集合底层分析,也是和上面一项的写时复制技术。每次add的时候都会复制之前数组,性能不是很高。

3.如何解决HashMap线程不安全问题

1.使用ConcurrentHashMap类,创建map集合
缺点:效率比较低,但是单单只是进行遍历查找的话,效率比较高
4.CopyOnWriterArrayList的原理

通过动态数组与线程安全来分析。
“动态数组”机制 它内部有个“volatile 数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile 数组”, 这就是它叫做 CopyOnWriteArrayList 的原因由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList 效率很低;但是单单只是进行遍历查找的话,效率比较高。

5.“线程安全”机制,加锁

通过 volatile 和互斥锁来实现的。
通过“volatile 数组”来保存数据的。一个线程读取 volatile 数组时,总能看
到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读
取到的数据总是最新的”这个机制的保证。
通过互斥锁来保护数据。在“添加/修改/删除”数据时,会先“获取互斥锁”,
再修改完毕之后,先将数据更新到“volatile 数组”中,然后再“释放互斥
锁”,就达到了保护数据的目的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值