美文网首页
锁与多线程同步的实现

锁与多线程同步的实现

作者: 不知名的蛋挞 | 来源:发表于2020-02-17 09:37 被阅读0次

Java当中的锁都是为了保证多线程同步执行。如果没有锁的话,多线程是异步执行的。

什么是多线程同步?

请看下面的代码:

public class SynTest {

    public static void main(String[] args){
        Thread t1 = new Thread(){
            @Override
            public void run(){
              testsync();
            }
        };
        t1.setName("t1");

        Thread t2 = new Thread(){
            @Override
            public void run(){
                testsync();
            }
        };
        t2.setName("t2");

        t1.start();
        t2.start();
    }

    public static void testsync(){
        System.out.print(Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出:

t1
t2
或者
t2
t1

可以看到线程t1和t2基本上是同时打印出来的。为了让线程按顺序一前一后执行,我们就可以给方法加锁。

public class SynTest {

    static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args){
        Thread t1 = new Thread(){
            @Override
            public void run(){
              testsync();
            }
        };
        t1.setName("t1");

        Thread t2 = new Thread(){
            @Override
            public void run(){
                testsync();
            }
        };
        t2.setName("t2");

        t1.start();
        t2.start();
    }

    public static void testsync(){
        reentrantLock.lock();
        System.out.print(Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            reentrantLock.unlock();
        }
    }
}

此次输出就是有间隔地输出,也就是第一个线程执行完释放锁之后第二个线程才开始执行。

Java当中有了synchronized关键字为啥还要用ReentrantLock呢?JDK1.6之前,synchronized关键字实现同步是一个重量级的锁,因为会去调用OS的函数(native方法),所以synchronized关键字没有源码,它都是用c++实现的。

比如当t1拿到锁执行synchronized中的同步代码时,线程t2此时尝试去执行synchronized中的同步代码,此时synchronized会去调用OS函数,CPU就需要切换成内核态,此时这个OS函数会进入阻塞状态。t1执行完释放锁之后t2拿到锁需要继续往下执行同步代码的话,CPU又要切换成用户态。

有个人看不惯这种同步方法,所以就开发了一个包解决同步技术。ReentrantLock就是这个包里面的一个同步技术。这个锁比synchronized关键字还快。

sun公司这下也坐不住了,所以在JDK1.7做了优化,synchronized虽然也会触发到内核,但是会让同步在JVM层面解决而不是在OS层面解决,优化成偏向锁、轻量锁和重量锁。

多线程同步内部是如何实现同步的

wait/notify,synchronized,ReentrantLock

模拟一些同步的思路

(1)自旋

所谓自旋锁就是循环循环自己不断循环。

   // 标识---是否有线程在同步块---是否有线程上锁成功
    volatile int status = 0;

    void lock(){
        // 不断循环while 直到拿到锁才跳出while循环
        while (!compareAndSet(0,1)){
        }
        //lock
    }

    void unlock(){
        status = 0;
    }

    boolean compareAndSet(int except,int newValue){
        // cas操作,修改status成功则返回True
    }

缺点:耗费cpu资源。没有竞争到锁的线程会一直占用cpu资源进行cas操作,假如一个线程获得锁后要花费Ns处理业务逻辑,那另一个线程就会白白地花费Ns的cpu资源。

改进思路:让得不到锁的线程让出cpu

(2)yield+自旋

    volatile int status = 0;

    void lock(){
        while (!compareAndSet(0,1)){
            yield();// 自己实现
        }
        //lock
    }

    void unlock(){
        status = 0;
    }

要解决自旋锁的性能问题必须让竞争锁失败的线程不空转,而是在获取不到锁定的时候把cpu给让出来,yield()方法就能让出cpu资源,当线程竞争锁失败时,会调用yield方法让出cpu。自旋锁+yield的方法并没有完全解决问题,当系统只有两个线程竞争锁时,yield是有效的。需要注意的是该方法只是当前让出cpu,有可能操作系统下次还是选择运行该线程。

(3)sleep+自旋

   volatile int status = 0;

    void lock(){
        while (!compareAndSet(0,1)){
            sleep(10);
        }
        //lock-------------5minute
    }

    void unlock(){
        status = 0;
    }

sleep的时间为什么是10?怎么控制呢?就是你是调用者其实很多时候你也不知道这个时间怎么确定。

比如线程t1拿到锁执行5minute,但是没拿到锁的线程t2只是睡眠10s,也就是每10s就去看一下可不可以拿到锁。而假设t1只执行了1s,但是t2睡了10s,那就足足浪费了9s。

(4)park+自旋

public class ParkTest {

    public static void main(String[] args){
        Thread t1 = new Thread(){
            @Override
            public void run(){
                testsync();
            }
        };
        t1.setName("t1");
        t1.start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.print("-----------main");

        // 叫醒t1
        LockSupport.unpark(t1);
    }

    public static void testsync(){
        System.out.print("t1 -1");
        // 线程t1执行到park()时就会一直睡眠,需要被叫醒
        LockSupport.park();
        System.out.print("t1 -2");
    }
}

输出:

t1 -1
-----------main
t1 -2

t1执行testsync()方法时,首先"t1 -1",然后就沉睡但是此时main()方法是继续往下执行的,所以打印了"-----------main",然后执行unpark()方法叫醒t1,t1醒过来之后就打印"t1 -2"。

park()方法并不是由LockSupport类提供的,LockSupport仅仅对park()方法做了封装,park()方法是由unsafe类提供的。

park()的实现机制如下:

   volatile int status = 0;
   Queue parkQueue; //集合 数组 list

    void lock(){
        while (!compareAndSet(0,1)){
            park();
        }
        //lock
        ....
        unlock();
    }

    void unlock(){
        status = 0;
        lock_notify();
    }

    void park(){
        // 将当前线程加入到等待队列
        parkQueue.add(currentThread);
        // 将当前线程释放cpu,被释放cpu的线程执行到这一步就不再往下执行了,不会继续执行while
        releaseCpu()
    }

    void lock_notify(){
        // 得到要唤醒的线程头部线程
        Thread t = parkQueue.header();
        // 唤醒等待线程
        unpark(t)
    }

ReentrantLock其实就是基于这个机制实现的。

相关文章

  • JUC(一) | 同步辅助类浅谈

    Java多线程环境中存在内置锁与同步锁,内置锁即由synchronized修饰的代码,借助于对象的内置锁实现,为重...

  • 起底多线程同步锁(iOS)

    起底多线程同步锁(iOS) 起底多线程同步锁(iOS)

  • 第5章 Java的锁

    基本概念: 锁:控制多线程并发访问资源;队列同步器:管理同步状态,实现锁;同步状态:同步器的操作对象,int类型;...

  • 线程锁

    探讨iOS开发中各种锁使用NSCondition实现多线程同步 NSCondition是线程同步, 阻塞线程。 取...

  • 高并发下Java多线程编程基础

    Java线程同步与异步 线程池 无锁化的实现方案 分布锁的实现方案 分享的目的: 进一步掌握多线程编程和应用的技巧...

  • 锁与多线程同步的实现

    Java当中的锁都是为了保证多线程同步执行。如果没有锁的话,多线程是异步执行的。 什么是多线程同步? 请看下面的代...

  • iOS面试之多线程模块

    多线程 多线程内容如下: GCD NSOperation NSThread 多线程与锁 1.GCD 同步/异步和串...

  • Java多线程基础-使用多线程(二)

    |-目录|  同步锁  -|同步锁使用范围  -|对象锁与静态锁  -|死锁|  volatile实现’内存共享’...

  • Java的CAS乐观锁原理解析

    CAS全称 Compare And Swap(比较与交换),在不使用锁的情况下实现多线程之间的变量同步。属于硬件同...

  • iOS多线程同步

    多线程情况下访问共享资源需要进行线程同步,线程同步一般都用锁实现。从操作系统层面,锁的实现有临界区、事件、互斥量、...

网友评论

      本文标题:锁与多线程同步的实现

      本文链接:https://www.haomeiwen.com/subject/shmjfhtx.html