多线程(二)多线程的锁机制(java)

目录

 一、前言:

二、 synchronized(对类、方法、代码块加锁-非公平锁)

      2.1 概念 

      2.2 用法

           2.2.1  对方法中的代码块进行加对象锁(对象锁)

           2.2.2  对普通方法进行加锁(对象锁)

           2.2.3  对静态方法加锁(类锁)

 ​编辑

三、 volatile (声明对多线程都可见的资源信息)

      3.1 概念

      3.2 用法

             3.2.1  可见性(一个线程修改,一个线程观察)

      3.2.2   有序性(经典的单例设计模式的例子)

四、ReentrantLock(实现lock接口的AQS锁)

      4.1 概念

      4.2 用法

                4.2.1 使用ReentrantLock进行加解锁

                4.2.2 公平锁示范(按照顺序获取到锁)

                4.2.3 非公平锁示范 (随机获取到锁)

 

五、CAS (compare and swap  比较交换)

        5.1 cas概念

        5.2 synchronized与CAS进行比较


 一、前言:

        对于上文中,我们知道了什么是线程和进程,同时也知道了对于多线程进行同一个资源(变量)操作的时候,会产生线程安全的问题,那么为了这个问题,我们最合理的方式一般有两种:

      其一:对该资源进行控制,同一时刻只允许单个线程对此资源进行操作 ;

      其二:将该资源分割成若干等分并分配给单个线程,作为私有资源进行处理。

往往第二种方式瞬息万变的外在环境下不太好进行分割控制,故一般我们采用第一种方式;第一种方式的落地方案就是多线程的锁机制。

二、 synchronized(对类、方法、代码块加锁-非公平锁)

      2.1 概念 

         synchronized是java关键字,主要作用是对以一个类或其对象作为标识,对某段代码块进行锁定,只有获取该标识的线程才能执行synchronized标识作用域(代码块或方法)的代码。当代码块中的代码发生异常时,synchronized会自动释放锁。

        一般的如果是直接将synchronized关键字用在了普通方法上,那么默认是加了一个对象锁,如果是放在了静态方法上面,那么就是使用了类锁。对于代码块,形如 xxxx.class的代码块则相当于加上了类锁,如果是使用某个实例对象,这是对这个对象加锁。对于加了锁的执行都需要获取锁之后才能进行,如果中间发生了任何异常,synchronized会自动释放锁资源。

            特别注意: synchronized锁对象不要用String、Integer等包装类型的对象。(1. 当其值发生变化的时候,其对象也发生了改变,容易被忽略。2. 例如String lockKey =  123+12等进行凭借的时候,会发生意想不到的情况。)

      2.2 用法

           2.2.1  对方法中的代码块进行加对象锁(对象锁)

示例:

    public static void main(String[] args) {
        Object o = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o) {
                    System.out.println("I get the lock and please all thread waiting 60s");
                    TimeUtils.sleep(1000);
                    System.out.println("waiting end!");
                }
            }
        }).start();

        // 防止下面代码在run方法之前执行,休息0.1秒
        TimeUtils.sleep(100);

        synchronized (o) {
            System.out.println("I get the lock");
        }
    }

 

结果:

 

           2.2.2  对普通方法进行加锁(对象锁)

示例:

public class T_Synchronized {


    public static void main(String[] args) {
        T_Synchronized t = new T_Synchronized();

        new Thread(new Runnable() {
            @Override
            public void run() {
                t.print("我是新建的线程");
            }
        }).start();

        // 保证run方法先运行
        TimeUtils.sleep(100);

        t.print("我是主函数里面的线程");
    }


    public synchronized void print(String words) {
        System.out.println("thread:" + Thread.currentThread().getName() + ",print[" + words + "]");
        TimeUtils.sleep(2000);
        System.out.println("thread:" + Thread.currentThread().getName() + " print end");
    }


}

结果:

           2.2.3  对静态方法加锁(类锁)

示例:

 

public class T03_Synchronized {

    public synchronized static void print() {
        System.out.println("T03_Synchronized created " + TimeUtils.currentTime());
    }


    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (T03_Synchronized.class) {
                    TimeUtils.sleep(1000);
                    System.out.println("sleep end");
                }
            }
        }).start();

        TimeUtils.sleep(100);

        T03_Synchronized.print();
    }
}

结果:

 

三、 volatile (声明对多线程都可见的资源信息)

      3.1 概念

        volatile也是java中的一个较为常见的关键字,主要用于修饰java中的变量,使得这个变量具有在各个线程中的可见性。其主要作用有:

        1. 可见性,当被volatile修饰的变量在发生修改时,使用其变量的线程回去主内存更新该值数据,以保证其修改后的数据被各个线程可见。(可见但并不保证其原子性)

        2. 有序性,当变量volatile被修饰后,不会被指令重排序掉。

      3.2 用法

             3.2.1  可见性(一个线程修改,一个线程观察)

                    示例:

public class T01_volatile {

    public volatile static Integer num = 0;

    public static void main(String[] args) {

        // 写线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (num < 5) {
                    TimeUtils.sleep(1000);
                    num++;
                    System.out.println(Thread.currentThread().getName() + "--> num has add and now is " + num);
                }
            }
        }).start();

        TimeUtils.sleep(500);


        // 读线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (num < 5) {
                    TimeUtils.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "--> I read num is " + num);
                }
            }
        }).start();


    }

}

结果:

      3.2.2   有序性(经典的单例设计模式的例子)

            示例:

public class T02_volatile {


    private static volatile T02_volatile instance;


    private T02_volatile() {
    }

    public T02_volatile getInstance() {

        if (instance == null) {
            synchronized (this) {
                if (instance == null) {
                    instance = new T02_volatile();
                }
            }
        }

        return instance;
        
    }

}

四、ReentrantLock(实现lock接口的AQS锁)

      4.1 概念

                ReentrantLock是是实现了lock接口的AQS(AbstractQueuedSynchronizer)锁,其和synchronized区别主要有:1. 使用lock和unlock方法进行加锁和解锁过程。2. 当发生异常时,synchronized会主动进行释放过程,但是ReentrantLock这需要使用try{}cathe进行异常捕捉和进行释放。3. 其二都是可重入的锁。4. synchronized是非公平锁,但是ReentrantLock是公平锁。

      4.2 用法

                4.2.1 使用ReentrantLock进行加解锁

public class T_ReentrantLock {


    public static void main(String[] args) {
        Lock lock = new ReentrantLock();

        lock.lock();

        try {

        } catch (Exception e) {

        } finally {
            lock.unlock();
        }


    }


}

                4.2.2 公平锁示范(按照顺序获取到锁)

public class T01_ReentrantLock {


    public static void main(String[] args) {

        // 创建一个公平锁的对象
        Test test = new Test(true);

        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.print();
                }
            }).start();
        }

    }


    public static class Test {

        private ReentrantLock lock;

        public Test() {
            // 创建非公平的ReentrantLock锁
            this.lock = new ReentrantLock(true);
        }

        public Test(boolean fair) {
            this.lock = new ReentrantLock(fair);
        }

        public void print() {
            lock.lock();

            try {
                System.out.println(Thread.currentThread().getName() + "获取到锁");
                TimeUtils.sleep(1000);
            } catch (Exception e) {

            } finally {
                System.out.println(Thread.currentThread().getName() + "释放锁");
                lock.unlock();
            }
        }


    }


}

运行结果:

                4.2.3 非公平锁示范 (随机获取到锁)

主要是在4.2.2的基础上,将 Test test = new Test(true); 改成  Test test = new Test(false);

        

public class T01_ReentrantLock {


    public static void main(String[] args) {

        Test test = new Test(false);
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.print();
                }
            }).start();
        }

    }


    public static class Test {

        private ReentrantLock lock;

        public Test() {
            // 创建非公平的ReentrantLock锁
            this.lock = new ReentrantLock(true);
        }

        public Test(boolean fair) {
            this.lock = new ReentrantLock(fair);
        }

        public void print() {
            lock.lock();

            try {
                System.out.println(Thread.currentThread().getName() + "获取到锁");
                TimeUtils.sleep(1000);
            } catch (Exception e) {

            } finally {
                System.out.println(Thread.currentThread().getName() + "释放锁");
                lock.unlock();
            }
        }


    }


}

结果:

 

 

                

五、CAS (compare and swap  比较交换)

        5.1 cas概念

                cas:从字面意思是先比较再进行值的交换;也就是说比如某个变量x,我们需要将其从值A变成B时,首先我们先知道这个要变的变量x,并且知道其当前的值是x=A,下一步进行值变换的时候要比较一下,在我在准备和正在变换的过程中这个变量x的值还是不是A,如果是这进行变换,不是则失败,这就是cas的概念和核心原理。

                ABA问题:  当进行cas的时候会发现一个事情,就是当我们需要将A转换成B时,可能这个值已经从A转换成C再转换成了A,那么此时再进行变更的时候发现其状态已经发生了改变,那么这个问题如何解决了?很简答,加个版本号,那么比较不仅仅只是值比较了,还有其版本比较,每个将此资源修改之后都将更新其版本号,便可以杜绝此类问题 。

        5.2 synchronized与CAS进行比较

         我们可以进行以下比较,将数字从0加到一百万,来比较 synchronized、AutomaticInteger、LongAdder的运行效率,可以得出一些结果:

        synchronized:所有线程都会被这个锁住,获取到锁的更新后被不公平的分配给其他线程进行增加,速度较慢。

        AutomaticInteger:基于CAS的自旋锁,没有加锁和解锁的过程,拼手速了,速度较快。

        LongAdder:分段CAS锁,这个会将这个按照策略分配不同的粒度,分段计算后进行相加,对于数量比较大的数据速度应该是这三者最快的。

        示例和结果如下:

public class T01_Cas {

    // sync 锁,线程都要阻塞
    static long count1 = 0L;

    //CAS操作,无锁原子操作,效率更高
    static AtomicLong count2 = new AtomicLong(0L);

    // 分段锁(锁内CAS操作)--将所有的线程分成几个等分,然后将几个线程的数据统一再加起来
    static LongAdder count3 = new LongAdder();

    public static void main(String[] args) {
        T01_Cas t01 = new T01_Cas();

        t01.syncCount();

        t01.AtomicCount();

        t01.LongAdderCount();
    }

    public void syncCount() {

        final Object o = new Object();
        List<Thread> threads = new ArrayList<>(10);

        Long start = System.currentTimeMillis();

        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(new Runnable() {
                @Override
                public void run() {

                    for (int i = 0; i < 100000; i++) {
                        synchronized (o) {
                            count1++;
                        }
                    }

                }
            }));
        }

        for (Thread t : threads) t.start();
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("sync 执行结果是:" + count1 + ",执行时间为:" + (System.currentTimeMillis() - start) + "ms");

    }

    public void AtomicCount() {

        final Object o = new Object();
        List<Thread> threads = new ArrayList<>(10);

        Long start = System.currentTimeMillis();

        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(new Runnable() {
                @Override
                public void run() {

                    for (int i = 0; i < 100000; i++) {
                        count2.incrementAndGet();
                    }

                }
            }));
        }

        for (Thread t : threads) t.start();
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("AtomicLong 执行结果是:" + count2 + ",执行时间为:" + (System.currentTimeMillis() - start) + "ms");

    }

    public void LongAdderCount() {

        final Object o = new Object();
        List<Thread> threads = new ArrayList<>(10);

        Long start = System.currentTimeMillis();

        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(new Runnable() {
                @Override
                public void run() {

                    for (int i = 0; i < 100000; i++) {
                        count3.increment();
                    }

                }
            }));
        }

        for (Thread t : threads) t.start();
        for (Thread t : threads) {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("LongAdder 执行结果是:" + count1 + ",执行时间为:" + (System.currentTimeMillis() - start) + "ms");

    }
}

        结果:

 

上一章:多线程(一)线程与进程

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值