zoukankan      html  css  js  c++  java
  • 有锁编程

    当多线程共同读写共享资源的时候,为了达到线程安全的目的,从而有了有锁编程。先从基本概念谈起:

    什么叫多线程?

    一段程序加载到内存,引导启动后,操作系统会给该程序创建一个以PID为唯一标示的进程。进程简而言之就是这段程序在操作系统之上的一次动态执行(从加载到内存,引导启动,运行,到结束)。进程包含很丰富的信息(PID,进程控制单元,进程空间,分配的内存资源,以及操作系统调度得到的CPU资源,还有别的资源),同时也是比较重量级的。而一个进程中为了让CPU以及IO资源使用的更到位,从而有了线程。所以线程诞生的目的:尽量吃满资源(CPU,IO),或者换句话说就是尽量更充分的使用计算机资源,从而达到有更高的吞吐量。一个进程内部的线程可以共享该进程的资源,同时线程也有自己的TID,线程栈空间,线程间公用的内存空间。

    好,说完上面基本信息,所以将你的程序设计成多线程程序就水到渠成了。

    什么叫共享资源?

    共享资源可以是一个变量,可以是一个文件,也可以是一个复杂的数据结构。因为某团厕所坑位比较紧张,所以用WC坑位举栗子。

    正常情况下一个坑位同时只能有一个人占用,这个是不允许同个人同时享用的。所以在这里坑位就可以理解成一个共享资源。那多个人(多线程)想使用一个坑位,怎么办呢?很简单给每个坑位上一把锁,有个这个锁就可以保证坑位这个共享资源同时只被一个人使用。

    通俗的栗子讲完,那进入操作系统的世界中,如何理解共享资源?下面将两个计算机世界的例子

    线程间状态变量可见的栗子

    两个线程共享isRunning变量,第一个线程对isRunning有修改成false,但是第二个线程一直读取不到该变量的修改。

    public class VolatileExampleV2 {
        boolean isRunning = true;
        long gap = 50;
    
        public static void main(String[] args) {
    
            new VolatileExampleV2().test();
        }
    
        private void test() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    doSomeThing(2000);
    
                    isRunning = false;
                    System.out.println("first thread currentTime:" + System.currentTimeMillis());
    
                    doSomeThing(500);
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (isRunning) {
    
                    }
    
                    System.out.println("Second Thread currentTime:" + System.currentTimeMillis());
                }
            }).start();
        }
    
        private static void doSomeThing(long time) {
            long sum = 0;
            long size = time * 100000;
    
            for (int i = 0; i < size; i++) {
                sum += i;
            }
        }
    }
    
    
    ###########只打印一行内容,程序一直在第二个线程跑,并未结束
    first thread currentTime:1442215575596
    

    通过volatile关键字就可以控制线程间变量的可见性,栗子如下:

    public class VolatileExampleV2 {
        volatile boolean isRunning = true;
        long gap = 50;
    
        public static void main(String[] args) {
    
            new VolatileExampleV2().test();
        }
    
        private void test() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    doSomeThing(2000);
    
                    isRunning = false;
                    System.out.println("first thread currentTime:" + System.currentTimeMillis());
    
                    doSomeThing(500);
                }
            }).start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (isRunning) {
    
                    }
    
                    System.out.println("Second Thread currentTime:" + System.currentTimeMillis());
                }
            }).start();
        }
    
        private static void doSomeThing(long time) {
            long sum = 0;
            long size = time * 100000;
    
            for (int i = 0; i < size; i++) {
                sum += i;
            }
        }
    }

    #####################这次两个线程都能跑完,第一个线程对共享变量的修改,被第二个线程看见了。

    Second Thread currentTime:1442215856761
    first thread currentTime:1442215856761

    PS:想写这个栗子很不容易,因为必须确保第二个线程每次读取共享状态变量是从该CPU的独立缓存中读取,不能从内存中读取,不然就实现不了线程可见性的例子。

    读写修改共享参数的栗子

    public class SynchronizedExample {
        private Count wangxin = new Count();
    
        public static void main(String[] args) {
            new SynchronizedExample().test();
        }
    
        private void test() {
            System.out.println("user count:" + wangxin);
    
            List<Thread> threadList = new ArrayList<Thread>();
            for (int i = 0; i < 10; i++) {
                threadList.add(new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        saveMoeny();
                    }
                }));
            }
    
            for (Thread each : threadList) {
                each.start();
            }
    
            try {
                Thread.currentThread().join(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("user count:" + wangxin);
        }
    
        public void saveMoeny() {
            int moeny = wangxin.getMoeny();
            wangxin.setMoeny(moeny + 100);
        }
    }
    
    
    public class Count {
        private int moeny = 100;
    
        public int getMoeny() {
            return moeny;
        }
    
        public void setMoeny(int moeny) {
            this.moeny = moeny;
        }
    
        @Override
        public String toString() {
            return "Count{" +
                    "moeny=" + moeny +
                    '}';
        }
    }
    
    ##########一共存10份钱,最后账户只剩下了800块钱。原因就是账户这个共享资源没有做好保护,导致账户钱少了。
    user count:Count{moeny=100}
    user count:Count{moeny=800}
    

      通过synchronized关键字进行共享资源的保护,从而安全的保证了多人存款。

    public class SynchronizedExample {
        private Count wangxin = new Count();
    
        public static void main(String[] args) {
            new SynchronizedExample().test();
        }
    
        private void test() {
            System.out.println("user count:" + wangxin);
    
            List<Thread> threadList = new ArrayList<Thread>();
            for (int i = 0; i < 10; i++) {
                threadList.add(new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        saveMoeny();
                    }
                }));
            }
    
            for (Thread each : threadList) {
                each.start();
            }
    
            try {
                Thread.currentThread().join(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("user count:" + wangxin);
        }
    
        public synchronized void saveMoeny() {
            int moeny = wangxin.getMoeny();
            wangxin.setMoeny(moeny + 100);
        }
    }
    

    什么叫线程安全? 

    从上面第二个栗子应该可以看到线程安全的本质含义了。针对共享的资源,如果不做保护的话,同时被多个线程写操作会导致结果不可预期,可能每次跑的结果都不一致。所以线程安全就是在任何情况下同一段代码被多线程执行完毕,结果都一致,并且符合预期。

    如何做到线程安全?

    从上面的例子也可以看出有两种手段:

    1. 采用volatile关键字保证线程间状态变量的可见性
    2. 采用synchronized关键字保证线程对临界区的串行读写

    volatile关键字在JVM中如何实现的?

    JVM的内存模型中保证:

    1. volatile关键字修饰的变量都只从内存读取。不会从CPU的缓存中读取
    2. volatile关键字修饰的变量写操作,写回内存,同时CPU各级缓存该变量失效

    由JVM上面两条就能保证,CPU的各个核都能从内存中读取到数据,从而保证了各个线程可见。

    synchronized关键字在JVM中如何实现?

    synchronized关键字要搞定的事情是临界区代码保证线程串行访问,就是上面的厕所坑串行的被使用。

    Java编译器在.java文件被编译的时候,在临界区代码的入口处和出口处插入了monitorenter和monitorexit字节码指令。

    同时在对象创建的时候,在其对象头部用两个字节(MarkWord)来表示该对象上是否有锁,同时被那条线程占用。

  • 相关阅读:
    .NET 动态向Word文档添加数据
    .NET FileUpLoad上传文件
    Jquery 客户端生成验证码
    ASP.NET MVC 5 基本构成
    .NET 发布网站步骤
    Jquery 选择器大全
    .NET 知识整理笔记
    .NET 三层架构
    C#知识整理笔记
    .NET MD5加密解密代码
  • 原文地址:https://www.cnblogs.com/xinxinwang/p/4807760.html
Copyright © 2011-2022 走看看