精华内容
下载资源
问答
  • 理解什么是线程安全性、原子

    万次阅读 2019-12-29 11:56:30
    •原子 加锁机制 •写在前面 进程想要执行任务需要依赖线程,换句话说就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。提到多线程这里要说两个概念,就是串行和并行,搞清楚这个我们才能更好...

    目录

    •写在前面

    •原子性

    加锁机制


    •写在前面

    进程想要执行任务需要依赖线程,换句话说就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。提到多线程这里要说两个概念,就是串行和并行,搞清楚这个我们才能更好的理解多线程。所谓串行其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子,我们下载多个文件,在串行中它是按照一定的顺序去进行下载的,也就是说必须等下载完A之后,才能开始下载B,它们在时间上是不可能发生重叠的。

    要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问。要是的对象是线程安全的,需要采用同步机制来协同对对象可变状态的访问,如果无法实现协同,那么可能会导致数据破坏以及其他不该出现的结果。java中的同步机制是关键字synchronized,它提供了独占式的加锁方式,不仅如此还包括volatile类型变量,显式锁Lock以及原子变量。

    单线程近似定义为“所见即所知”,那么定义线程安全性,则当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。一个类在单线程中运行都不正确,那么它肯定是不会是线程安全的。如果正确的实现了某个对象,那么在任何操作中(包括调用对象的公有方法或者对其公有域进行读写操作)都不会违背不变性条件或后验条件。在线程安全类的对象实例上执行的任何串行或并行操作都不会处于无效状态。

    无状态的对象一定是安全的,啥是无状态?这个类既不包括任何域,也不包括任何对其他类中域的引用。举个例子,大多数Servlet都是无状态的,从而极大的降低了在实现Servlet线程安全性时的复杂性,只有当Servlet在处理请求时需要保存一些信息,线程安全性才会成为一个问题。

    •原子性

    其实原子性就是一个不可分割的,我们怎么理解呢?举个例子,当我们在无状态对象中增加一个状态时,会发生什么情况?比我们要计算一个类调用了多少次(这个类可以是Servlet),我们定义一个私有的Long类型的域(命名为count),在方法中没调用一次就将这个值加一(即count++),如下。

       Integer count = 0;
       public void getCount() {
           count ++;
           System.out.println(count);
       }

    由于我们没有对这个类进行任何的同步机制操作,所以这个类是非线程安全的,虽然在单线程环境中能正确运行,但在多线程情况下就会出现问题。我们注意到count++这个操作,这个操作看上去是一个操作,其实这个操作并非原子性的,因为它并不会作为一个不可分割的操作来执行,实际上count++ 包含了三个独立的操作,分为读取count的值,将值加1,然后计算结构写入count中,这是一个“读取-修改-写入”的操作序列,并且其结果依赖于之前的状态。在并发下就会出现问题,我再多线程下跑了上面那段代码,结果如下

    我们可以看到,这里出现了两个26,为什么会出现这种情况,出现这种情况显然表明我们这个方法根本就不是线程安全的,出现这种问题的原因有很多,我们说最常见的一种,就是我们A线程在进入方法后,拿到了count的值,刚把这个值读取出来还没有改变count的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的count值是一样的。

    在并发编程中,这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,它有一个正式的名字,叫“竞态条件”。竞态条件是啥?当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件,最常见的竞态条件类型就是“先检查后执行”操作,即通过一个可能失效的观察结果来决定下一步的动作。比如首先观察到某个条件为真(例如文件X不存在),然后根据这个观察结果采用相应的动作(创建文件X),但事实上,在你观察到这个结果以及开始创建文件之间,观察结果可能变得无效(另一个线程在这个期间创建了文件X),从而导致各种问题(未预期的异常、数据覆盖、文件被破坏等)。

    与大多数并发错误一样,竞态条件并不总是会产生错误,还需要某种不恰当的执行时序。原子操作是对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式执行的操作。而我们前面提到的类似count++的这种操作,叫做复合操作,即包含了一组必须以原子方式执行的操作以确保线程安全性。在实际情况中,应该尽可能的使用现有的线程安全对象来管理类的状态,与非线程安全的对象相比,判断线程安全对象的可能状态及其状态转换情况要更为容易,从而也更容易维护和验证线程安全性。这里值得一提的是,java中给我们弄好了很多原子操作的类型,在这个包下java.util.concurrent.atomic。

    加锁机制

    上面我们说了,我们可以对单个类进行原子性操作,这样可以保证我们程序的安全性,但是,我们想一想,如果当多个原子性的操作同时进行时,而且各个原子性操作之间都存在相互依赖的关系,这种情况下,我们怎么保证程序运行正确(线程安全)?如果还是使用原子性操作的方法,那么我们要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

    说到这里,我们就不得不提一下java提供的一种内置的锁机制来支持原子性,即同步代码块(synchronized block),同步代码快包括两部分,一个作为锁的对象引用,一个作为由这个锁保护的代码块。以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。每个Java对象都可以用作一个实现同步的锁,这些所被称为内置锁或监视器锁。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,这里要说,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。值得一提的是,内置锁相当于一种互斥锁,意味着最多只有一个线程能持有这种锁。要注意了,任何一个执行同步代码块的线程,都不能看到有其他线程正在执行由同一锁保护的同步代码块(这里涉及到可见性的问题)。synchronized直接加在方法上,虽然能很方便的解决线程安全的问题,但是也会带来性能低下的问题,所以synchronized怎么使用,也是值得学习的。

    可重入函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。

    当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会被阻塞,然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是线程,而不是调用。重入的一种实现方式是,为每一个锁关联一个获取计数值和一个所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次请求获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应的递减,当计数值为0时,这个锁将被释放。

    到此我们就可以将多个复合操作封装到一个同步代码块中,但是这样是不够的,如果用同步代码块来协调对某个变量的访问,那么在访问这个变量的所有位置上都需要使用同步。而且,当使用锁来协调对某个变量的访问是,在访问变量的所有位置上都要使用同一个锁。而且,并不是只有在写入共享变量时才需要使用同步,对于可能被多个线程同时访问的可变状态变量,在访问它时都需要有同一个锁,这种情况下,我们成状态变量时由这个锁保护的。

    在这里值得一提的是,当使用锁时,你应该清楚代码块中实现的功能,以及在执行该代码块时是否需要很长的时间。无论是执行计算密集的操作,还是在执行某个可能阻塞的操作,如果持有锁的时间过长,那么都会带来活跃性或性能的问题。当执行时间较长的计算或者可能无法快速完成的操作时(例如网络IO或控制台IO),一定不要持有锁。

    展开全文
  • 线程安全性详解(原子、可见、有序)

    千次阅读 多人点赞 2019-10-22 10:00:52
    当多个线程访问某个类时,不管运行时环境采用 何种调度方式 或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类就是线程安全的。 当设计线程...

    一、定义:什么是线程安全性

    当多个线程访问某个类时,不管运行时环境采用 何种调度方式 或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类就是线程安全的。

    二、线程安全性的三个体现

    • 原子性:提供互斥访问,同一时刻只能有一个线程对数据进行操作(Atomic、CAS算法、synchronized、Lock)
    • 可见性:一个主内存的线程如果进行了修改,可以及时被其他线程观察到(synchronized、volatile)
    • 有序性:如果两个线程不能从 happens-before原则 观察出来,那么就不能观察他们的有序性,虚拟机可以随意的对他们进行重排序,导致其观察观察结果杂乱无序(happens-before原则)

    三、线程安全性:原子性

    3.1、原子性 — Atomic包

    在Java jdk中里面提供了很多Atomic类

    • AtomicXXX:CAS、Unsafe.compareAndSwapInt
    • AtomicLong、LongAdder
    • AtomicReference、AtomicReferenceFieldUpdater
    • AtomicStampReference:CAS的ABA问题

    由于CAS原语的直接操作与计算机底层的联系很大,CAS原语有三个参数,内存地址期望值新值。我们在Java中一般不去直接写CAS相关的代码,JDK为我们封装在AtomicXXX中,因此,我们直接使用就可以了。

    我们在 java.util.concurrent.atomic目录中可以看到我们这些类,包下提供了AtomicBooleanAtomicLongAtomicInteger三种原子更新基本类型和一个比较好玩的类AtomicReference,这些类都有一个共同点,都支持CAS,以AtomicInteger为重点讲解。
    在这里插入图片描述

    3.1.1、AtomicInteger

    AtomicInteger是一个提供原子操作的Integer类,通过线程安全的方式操作加减

    以下是AtomicIntege基本包括的方法:

    public final int getAndSet(int newValue)       //给AtomicInteger设置newValue并返回加oldValue
    public final boolean compareAndSet(int expect, int update)    //如果输入的值和期望值相等就set并返回true/false
    public final int getAndIncrement()     //对AtomicInteger原子的加1并返回当前自增前的value
    public final int getAndDecrement()   //对AtomicInteger原子的减1并返回自减之前的的value
    public final int getAndAdd(int delta)   //对AtomicInteger原子的加上delta值并返加之前的value
    public final int incrementAndGet()   //对AtomicInteger原子的加1并返回加1后的值
    public final int decrementAndGet()    //对AtomicInteger原子的减1并返回减1后的值
    public final int addAndGet(int delta)   //给AtomicInteger原子的加上指定的delta值并返回加后的值
    

    示例:

    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.atomic.AtomicInteger;
    
    @Slf4j
    public class AtomicIntegerExample {
    
        // 请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static AtomicInteger count = new AtomicInteger(0);
    
        public static void main(String[] args) throws Exception {
        	//获取线程池
            ExecutorService executorService = Executors.newCachedThreadPool();
            //定义信号量
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal ; i++) {
                executorService.execute(() -> {
                    try {
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count.get());
        }
    
        private static void add() {
            count.incrementAndGet();
        }
    }
    
    

    这里我们使用请求总数为:5000,同时执行的并发线程数为:200,我们最终需要得到结果为:5000,这个执行结果才算正确。

    查看返回结果:

    13:43:26.473 [main] INFO com.mmall.concurrency.example.atomic.AtomicIntegerExample - count:5000
    

    最后结果是 5000表示是线程安全的。

    我们来看看 AtomicInteger底层代码中到底为我们做了什么?首先我们来看 AtomicInteger.incrementAndGet()方法

    public class AtomicInteger extends Number implements java.io.Serializable{
    /**
         *  对AtomicInteger原子的加1并返回加1后的值
         * @return 更新的值
         */
        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }
    }
    

    AtomicInteger调用了java底层的 unsafe.getAndAddInt()方法,这里是实现CAS 的关键。

    incrementAndGet()是将自增后的值返回,还有一个方法getAndIncrement()是将自增前的值返回,分别对应++ii++操作。同样的decrementAndGet()getAndDecrement()则对--ii--操作。

    Unsafe类是在sun.misc包下,不属于Java标准。但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于
    Unsafe类开发的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率,增强Java语言底层操作
    能力方面起了很大的作用。Unsafe类使Java拥有了像C语言的指针一样操作内存空间的能力,同时也带来了指针的问题。
    过度的使用Unsafe类会使得出错的几率变大,因此Java官方并不建议使用的,官方文档也几乎没有。通常我们最好也不
    要使用Unsafe类,除非有明确的目的,并且也要对它有深入的了解才行。
    

    再来看 Unsafe.getAndAddInt()方法

    	/*
    	 * 其中getIntVolatile和compareAndSwapInt都是native方法
    	 * getIntVolatile是获取当前的期望值
    	 * compareAndSwapInt就是我们平时说的CAS(compare and swap),通过比较如果内存区的值没有改变,那么就用新值直接给该内存区赋值
    	 */
        public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
            return var5;
        }
        
        public native int getIntVolatile(Object var1, long var2);
        public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
           
    

    我们可以看到getAndAddInt(Object var1, long var2, int var4),传进来的第一个参数是当前的一个对象,也就是我们的:count.incrementAndGet(),在getAndAddInt()中,var1就是count,var2就是当前的值,比如当前循环中count值为 2,var4为每次递增1

    其次getAndAddInt()方法中涉及到的两个方法调用都定义为native,即java底层实现的本地方法

    • getIntVolatile():获取保存当前对象count的主存地址的引用(注意不是对象的值,是引用)。
    • compareAndSwapInt():比较当前对象的值和底层该对象的值是否相等,如果相等,则将当前对象值加1,如果不相等,则重新去获取底层该对象的值,这个方法的实现就是CPU的CAS(compare and swap)操作。

    我们知道volatile具有一致性的特征,但是它不具备原子性,为什么AtomicInteger却同时具备一致性和原子性,原来在AtomicInteger源码中实现了这样一串代码:private volatile int value;,在AtomicInteger内部实现就使用了volatile关键字,这就是为什么执行CAS(对CAS有疑问的点击此处)操作的时候,从底层获取的数据就是最新的数据:

    如果当前要保存的值和内存中最新的值不相等的话,说明在这个过程中被其他线程修改了,只
    能获取更新当前值为最新值,再那这个当前值再去和重新去内存获取的最新值比较,直到二者
    相等的时候,才完成+1的过程.
    

    使用AtomicInteger的好处在于,它不同于sychronized关键字或lock用锁的形式来实现原子性,加锁会影响性能,而是采用循环比较的形式来提高性能。

    3.1.2、AtomicLong

    AtomicLong是作用是对长整形进行原子操作,依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。

    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.atomic.AtomicLong;
    
    @Slf4j
    public class AtomicLongExample {
    
        // 请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static AtomicLong count = new AtomicLong(0);
    
        public static void main(String[] args) throws Exception {
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal ; i++) {
                executorService.execute(() -> {
                    try {
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count.get());
        }
    
        private static void add() {
            count.incrementAndGet();
            // count.getAndIncrement();
        }
    }
    
    

    执行结果:

    14:59:38.978 [main] INFO com.mmall.concurrency.example.atomic.AtomicLongExample - count:5000
    

    最后结果是 5000表示是线程安全的。

    3.1.3、AtomicBoolean

    AtomicBoolean位于java.util.concurrent.atomic包下,是java提供给的可以保证数据的原子性操作的一个类

    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    @Slf4j
    public class AtomicBooleanExample {
    
        private static AtomicBoolean isHappened = new AtomicBoolean(false);
    
        // 请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static void main(String[] args) throws Exception {
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal ; i++) {
                executorService.execute(() -> {
                    try {
                        semaphore.acquire();
                        test();
                        semaphore.release();
                    } catch (Exception e) {
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("isHappened:{}", isHappened.get());
        }
    
        private static void test() {
            if (isHappened.compareAndSet(false, true)) {
                log.info("execute");
            }
        }
    }
    
    

    返回结果:

    15:04:54.954 [pool-1-thread-2] INFO com.mmall.concurrency.example.atomic.AtomicBooleanExample - execute
    15:04:54.971 [main] INFO com.mmall.concurrency.example.atomic.AtomicBooleanExample - isHappened:true
    

    这里我们发现log.info("execute");,在代码中只执行了一次,并且isHappened:true的值为true,这是为啥呢?

    这里是因为当程序第一次compareAndSet()的时候,使得isHappend变为了true,因为原子性的关系,没有其他线程进行干扰,通过使用AtomicBoolean,我们使某段代码只执行一次。

    3.1.4、AtomicReference

    AtomicReferenceAtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等,而AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。

    多个线程之间的操作无论采用何种执行时序或交替方式,都要保证不变性条件不被破坏,要
    保持状态的一致性,就需要在单个原子操作中更新相关的状态变量。
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    @Slf4j
    public class AtomicReferenceExample {
    
        private static AtomicReference<Integer> count = new AtomicReference<>(0);
    
        public static void main(String[] args) {
            count.compareAndSet(0, 2); 
            count.compareAndSet(0, 1);
            count.compareAndSet(1, 3); 
            count.compareAndSet(2, 4); 
            count.compareAndSet(3, 5); 
            log.info("count:{}", count.get());
        }
    }
    
    

    大家觉得我们输出的结果会是多少?

    返回结果:

    15:26:59.680 [main] INFO com.mmall.concurrency.example.atomic.AtomicReferenceExample - count:4
    

    为什么是4呢?
    首先我们 要说的是public final boolean compareAndSet(V expect, V update)这个方法,这个方法主要的作用是通过比对两个对象,然后更新为新的对象,这里的比对两个对象,比对的方式不是equals而是==,意味着比对的是内存的中地址。

    1、首先我们创建count的初始化为0
    2、在main方法中 count.compareAndSet(0, 2);,判断count为0时赋值为2
    3、在count.compareAndSet(0, 1);count.compareAndSet(1, 3);判断count是否为1或者0,因为上一步我们已经赋值为2了,所以判断不成立
    4、在count.compareAndSet(2, 4);判断count是否为2,等式成立
    5、最好输出结果为4

    count.compareAndSet(0, 2); //count=0?赋值 2,判断成立,此时count=0,更新后为2
    count.compareAndSet(0, 1); //count=0?赋值 1,判断不成立,此时count=2
    count.compareAndSet(1, 3); //count=1?赋值 3,判断不成立,此时count=2
    count.compareAndSet(2, 4); //count=2?赋值 4,判断成立,此时count=2,更新后count=4
    count.compareAndSet(3, 5); //count=3?赋值 5,判断不成立,此时count=4
    

    所以我们输出结果为:4

    3.1.5、CAS中ABA问题的解决

    CAS并非完美的,它会导致ABA问题,例如:当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。比如在链表中。
    如何解决这个ABA问题呢,大多数情况下乐观锁的实现都会通过引入一个版本号标记这个对象,每次修改版本号都会变话,比如使用时间戳作为版本号,这样就可以很好的解决ABA问题。
    在JDK中提供了AtomicStampedReference类来解决这个问题,这个类维护了一个int类型的标记stamp,每次更新数据的时候顺带更新一下stamp。

    3.2、原子性 — synchronized

    synchronized是一种同步锁,通过锁实现原子操作。
    1、修饰代码块:大括号括起来的代码,作用于调用的对象
    2、修饰方法:整个方法,作用于调用的对象
    3、修饰静态方法:整个静态方法,作用于所有对象
    4、修饰类:括号括起来的部分,作用于所有对象

    详细可以查看,我写的关于:synchronized的博客,因为写过所以就不做过多描述。

    3.3、原子性 — 对比

    • Atomic:竞争激烈时能维持常态,比Lock性能好, 只能同步一个值
    • synchronized:不可中断锁,适合竞争不激烈,可读性好的情况
    • Lock:可中断锁,多样化同步,竞争激烈时能维持常态

    四、线程安全性:可见性

    简介:一个线程对主内存的修改可以及时被其他线程观察到

    导致共享变量在线程间不可见的原因:
    1.线程交叉执行
    2.重新排序结合线程交叉执行
    3.共享变量更新后的值没有在工作内存中与主内存间及时更新

    4.1 可见性 — syncronized

    JMM关于syncronized的两条规定:

    • 线程解锁前,必须把共享变量的最新值刷新到主内存中
    • 线程加锁时,将清空工作内存中共享变量的值,从而使得使用共享变量时需要从主内存中重新读取最新的值(注意:加锁与解锁是同一把锁
      由于syncronized可以保证原子性及可见性,变量只要被syncronized修饰,就可以放心的使用

    4.2 可见性 — volatile

    通过加入内存屏障禁止重排序优化来实现可见性。
    具体实现过程:

    • volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
    • volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量

    volatile不能保证操作的原子性,也就是不能保证线程安全性, 如果需要使用volatile必须满足以下两个条件:

    • 对变量的写操作不依赖与变量当前的值。
    • 该变量没有包含在具有其他变量的不变的式子中。

    所以volatile修饰的变量适合作为状态标记量。

    注:以下图片为资料中获取,如有雷同,纯属巧合
    在这里插入图片描述
    在这里插入图片描述

    示例:

    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    @Slf4j
    public class VolatileExample {
    
        // 请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static volatile int count = 0;
    
        public static void main(String[] args) throws Exception {
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal ; i++) {
                executorService.execute(() -> {
                    try {
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count);
        }
    
        private static void add() {
            count++;
        }
    }
    

    返回结果:

    16:12:01.404 [main] INFO com.mmall.concurrency.example.count.VolatileExample4 - count:4986
    

    通过执行代码我们可以发现,返回结果并不是我们想看到的5000,说明这个是线程不安全的类

    主要是因为当我们执行conut++时分成了三步:
    1、取出当前内存count值,这时count值时最新的
    2、+1操作
    3、重新写回主存

    例如:有两个线程同时在执行count++,两个内存都执行了第一步,比如当前count值为99,它们都读到了这个count值,然后两个线程分别执行了+1,并写回主存,这样就丢掉了一次+1的操作。

    五、线程安全性:有序性

    • 在JMM中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
    • 通过volatile、synchronized、lock保证有序性

    5.1 happens-before原则

    • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
    • 锁定规则:一个unLock操作先行发生于后面对同一个锁的Lock()操作,也就是说只有先解锁才能对下面的线程进行加锁
    • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
    • 传递规则:如果操作A先行发生与操作B,而操作B先行发生于操作C,则操作A先行发生于操作C
    • 线程启动规则Thread对象start()方法先行发生于此线程的每一个动作,一个线程只有执行了start()方法后才能做其他的操作
    • 线程终端规则:对线程interrupt()方法的调用先行发生与被中断线程的代码检测到中断事件的发生(只有执行了interrupt()方法才可以检测到中断事件的发生)
    • 线程终结规则:线程中所有操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值手段检测到线程已经终止执行
    • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

    六、线程安全性:总结

    • 原子性:Atomic包、CAS算法、synchronized、Lock
      原子性做了互斥方法,同一个线程只能有一个进行操作
    • 可见性:synchronized、volatile
      一个主内存的线程如果进行了修改,可以及时被其他线程观察到,介绍了volatile如何被观察到的
    • 有序性:happens-before原则
      happens-before原则,观察结果,如果两个线程不能偶从happens-before原则观察出来,那么就不能观察他们的有序性,虚拟机可以随意的对他们进行重排序
    展开全文
  • 总结了线程安全性的二十四个精华问题

    千次阅读 多人点赞 2019-12-05 19:19:27
    1、对象的状态:对象的状态是指存储在状态变量中的数据,对象的状态可能包括其他依赖对象的域。在对象的状态中包含了任何可能影响其外部可见行为的数据。 2、一个对象是否是线程安全的,取决于它是否被多个线程...

     

    1、对象的状态:对象的状态是指存储在状态变量中的数据,对象的状态可能包括其他依赖对象的域。在对象的状态中包含了任何可能影响其外部可见行为的数据。

     

    2、一个对象是否是线程安全的,取决于它是否被多个线程访问。这指的是在程序中访问对象的方式,而不是对象要实现的功能。当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变量的访问。同步机制包括synchronized、volatile变量、显式锁、原子变量。

     

    3、有三种方式可以修复线程安全问题:

    1)不在线程之间共享该状态变量

    2)将状态变量修改为不可变的变量

    3)在访问状态变量时使用同步

     

    4、线程安全性的定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

     

    5、无状态变量一定是线程安全的,比如局部变量。

     

    6、读取-修改-写入操作序列,如果是后续操作是依赖于之前读取的值,那么这个序列必须是串行执行的。在并发编程中,由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,它称为竞态条件(Race Condition)。最常见的竞态条件类型就是先检查后执行的操作,通过一个可能失效的观测结果来决定下一步的操作。


    7、复合操作:要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改状态的过程中。假定有两个操作A和B,如果从执行A的线程看,当另一个线程执行B时,要么将B全部执行完,要么完全不执行B,那么A和B对彼此来说就是原子的。原子操作是指,对于访问同一个状态的所有操作来说,这个操作是一个以原子方式执行的操作。

    为了确保线程安全性,读取-修改-写入序列必须是原子的,将其称为复合操作。复合操作包含了一组必须以原子方式执行的接口以确保线程安全性。

     

    8、在无状态的类中添加一个状态时,如果这个状态完全由线程安全的对象来管理,那么这个类仍然是线程安全的。(比如原子变量)

     

    9、如果多个状态是相关的,需要同时被修改,那么对多个状态的操作必须是串行的,需要进行同步。要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

     

    10、内置锁:synchronized(object){同步块}

    Java的内置锁相当于一种互斥体,这意味着最多只有一个线程能持有这种锁,当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或阻塞,直到线程B释放这个锁。如果B永远不释放锁,那么A也将永远地等待下去。

     

    11、重入:当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。重入意味着获取锁的操作的粒度是线程,而不是调用。重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并且将获取计数值置1。如果一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数值会相应递减。当计数值为0时,这个锁将被释放。

     

    12、对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的。

    每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。

    一种常见的加锁约定是,将所有的可变状态都封装在对象内部,并提供对象的内置锁(this)对所有访问可变状态的代码路径进行同步。在这种情况下,对象状态中的所有变量都由对象的内置锁保护起来。

     

    13、不良并发:要保证同步代码块不能过小,并且不要将本应是原子的操作拆分到多个同步代码块中。应该尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去,从而在这些操作的执行过程中,其他线程可以访问共享状态。

     

    14、可见性:为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。

     

    15、加锁与可见性:当线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一个同步代码块中的所有操作结果。如果没有同步,那么就无法实现上述保证。加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或写操作的线程都必须在同一个锁上同步。

     

    16、volatile变量:当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或其他对处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。volatile的语义不足以确保递增操作的原子性,除非你能确保只有一个线程对变量执行写操作。原子变量提供了“读-改-写”的原子操作,并且常常用做一种更好的volatile变量。

     

    17、加锁机制既可以确保可见性,又可以确保原子性,而volatile变量只能确保可见性。

     

    18、当且仅当满足以下的所有条件时,才应该使用volatile变量:

    1)对变量的写入操作不依赖变量的当前值(不存在读取-判断-写入序列),或者你能确保只有单个线程更新变量的值

    2)该变量不会与其他状态变量一起纳入不可变条件中

    3)在访问变量时不需要加锁

     

    19、栈封闭:在栈封闭中,只能通过局部变量才能访问对象。维护线程封闭性的一种更规范的方法是使用ThreadLocal,这个类能使线程的某个值与保存值的对象关联起来,ThreadLocal通过了get和set等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本,因此get总是返回由当前执行线程在调用set时设置的最新值。

     

    20、在并发程序中使用和共享对象时,可以使用一些使用的策略,包括:

    1)线程封闭:线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。

    2)只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象(从技术上来说是可变的,但其状态在发布之后不会再改变)。

    3)线程安全共享。线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。

    4)保护对象。被保护的对象只能通过持有对象的锁来访问。保护对象包括封装在其他线程安全对象中的对象,以及已发布并且由某个特定锁保护的对象。

     

    21、饥饿:当线程由于无法访问它所需要的资源而不能继续执行时,就发生了饥饿(某线程永远等待)。引发饥饿的最常见资源就是CPU时钟周期。比如线程的优先级问题。在Thread API中定义的线程优先级只是作为线程调度的参考。在Thread API中定义了10个优先级,JVM根据需要将它们映射到操作系统的调度优先级。这种映射是与特定平台相关的,因此在某个操作系统中两个不同的Java优先级可能被映射到同一优先级,而在另一个操作系统中则可能被映射到另一个不同的优先级。

    当提高某个线程的优先级时,可能不会起到任何作用,或者也可能使得某个线程的调度优先级高于其他线程,从而导致饥饿。

    通常,我们尽量不要改变线程的优先级,只要改变了线程的优先级,程序的行为就将与平台相关,并且会导致发生饥饿问题的风险。

     

    事务T1封锁了数据R,事务T2又请求封锁R,于是T2等待。T3也请求封锁R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T的请求......T2可能永远等待

     

    22、活锁

    活锁是另一种形式的活跃性问题,该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。活锁通常发生在处理事务消息的应用程序中。如果不能成功处理某个消息,那么消息处理机制将回滚整个事务,并将它重新放到队列的开头。虽然处理消息的线程并没有阻塞,但也无法继续执行下去。这种形式的活锁通常是由过度的错误恢复代码造成的,因为它错误地将不可修复的错误作为可修复的错误。

     

    当多个相互协作的线程都对彼此进行响从而修改各自的状态,并使得任何一个线程都无法继续执行时,就发生了活锁。要解决这种活锁问题,需要在重试机制中引入随机性。在并发应用程序中,通过等待随机长度的时间和回退可以有效地避免活锁的发生。

     

    23、当在锁上发生竞争时,竞争失败的线程肯定会阻塞。JVM在实现阻塞行为时,可以采用自旋等待(Spin-Waiting,指通过循环不断地尝试获取锁,直到成功),或者通过操作系统挂起被阻塞的线程。这两种方式的效率高低,取决于上下文切换的开销以及在成功获取锁之前需要等待的时间。如果等待时间较短,则适合采用自旋等待的方式,而如果等待时间较长,则适合采用线程挂起方式。

     

    24、有两个因素将影响在锁上发生竞争的可能性:锁的请求频率,以及每次持有该锁的时间。如果二者的乘积很小,那么大多数获取锁的操作都不会发生竞争,会因此在该锁上的竞争不会对可伸缩性造成严重影响。然而,如果在锁上的请求量很高,那么需要获取该锁的线程将被阻塞并等待。在极端情况下,即使仍有大量工作等待完成,处理器也会被闲置。

    有3种方式可以降低锁的竞争程度:

    1)减少锁的持有时间:

    ①缩小锁的范围,将与锁无关的代码移出同步代码块,尤其是开销较大的操作以及可能被阻塞的操作(IO操作)。

    当把一个同步代码块分解为多个同步代码块时,反而会对性能提升产生负面影响。在分解同步代码块时,理想的平衡点将与平台相关,但在实际情况中,仅可以将一些大量的计算或阻塞操作从同步代码块移出时,才应该考虑同步代码块的大小。

    ②减小锁的粒度:锁分解和锁分段

    锁分解是采用多个相互独立的锁来保护独立的状态变量,从而改变这些变量在之前由单个锁来保护的情况。这些技术能减小锁操作的粒度,并能实现更高的可伸缩性,然而,使用的锁越多,那么发生死锁的风险也就越高。

    锁分段:比如JDK1.7及之前的ConcurrentHashMap采用的方式就是分段锁的方式。

    2)降低锁的请求频率

    3)使用带有协调机制的独占锁,这些机制允许更高的并发性

    比如读写锁,并发容器等

     

    展开全文
  • 我们正站在新变革来临的边缘,互联网正在经历去中心...5月26日,美国国防部宣布与加密通讯开发商ITAMCO签署合同,共同开发用在美国军方的基于区块链的创新应用——一款“安全,不可侵入的消息传递、交易平台”。据称...

    我们正站在新变革来临的边缘,互联网正在经历去中心化的阶段。经过了20年的科学研究,在密码学领域和去中心化计算网络上都产生了新的进展,带来诸如区块链技术(blockchain)之类的前沿技术,而这些技术可能潜含着从底层的改变社会运转方式的力量。

    5月26日,美国国防部宣布与加密通讯开发商ITAMCO签署合同,共同开发用在美国军方的基于区块链的创新应用——一款“安全,不可侵入的消息传递、交易平台”。据称,这项合作计划能够为美国军方更安全地在总部和地面部队之间的通讯、专员及五角大楼之间的情报传递提供安全可靠的通道并确保不可侵入。

    区块链这个名词,对于大部分读者而言并不陌生,但区块链技术背后的特点、价值、应用在安全领域的作用、当前的技术局限,大家却未必完全熟悉。在此背景下,本文将带领大家细细来看区块链的安全优势及其局限。

    一、区块链价值:帮助解决信任问题

    首先,区块链是什么?人们或多或少都已听过这个名词,知道区块链是一种分布式共享加密数据库。正如大家所知道的那样,区块链是一种分布式共享加密数据库。中国工信部在2016年将其定义为一种分布式数据存储、点对点传输、共识机制、加密算法等计算机技术在互联网时代的创新应用模式。在实际交易过程中,区块链使用过程包括节点的连接、交易和记账等基本步骤。


    1.1 区块链技术发展历程

    区块链技术本身最早在2008年得到提出,中本聪在一次密码学探究分享会上提出了比特币的概念及其背后的技术方法。比特币这套数字货币运行系统的底层所运用的技术即是区块链技术及其思想的原型。潜藏在比特币背后的区块链,是被设计用来达到一种去中心化的、无需信任的货币运行平台。任何互不了解的人可以通过加入该总账,通过点对点的记账、数据传输、认证或是合约,而不需要借助任何一个中间方就能来达成信用共识。这个总账包括了过去所有的交易记录、历史数据及其他相关信息,所有信息都分布式存储并透明可查,并以密码学协议的方式保证其不能被非法篡改。

    在2010年,比特币成立了交易所,并逐渐被更多人了解。至2011年开始,其全球化趋势愈加强烈,对于区块链技术进行投资和研究的机构也迅速增多,区块链技术从初级的实现程度不断得到补充和完善。此举让比特币货币体系得到迅速的成长机会,但其设计之初相对简单的货币运行体制也得到了一定的挑战:文件访问权限的丢失,服务器密码的泄漏、货币失窃、匿名货币难以追踪监管等漏洞让人们发现了这个货币体制的缺陷。这些不足甚至影响到人们对于比特币的信任及其货币价值,但也以此作为契机,更多的人增加了对于底层区块链技术的关注。

    由此,在2015-2016年区块链技术度过了停留在理论和研究的阶段,走向应用。许多传统金融机构、创业企业及其他组织从实际应用的方面使用了该项技术,如交易结算、物联网、数字资产管理、股权交易、公证、供应链等领域。随着行业的发展,各个国家及地方组织也加强了对区块链的潜能研究及监管。

    到现在,这种最先隐藏在比特币底层的分布式账本技术逐渐走向公众的视线,甚至成为学术乃至产业界的“热点”话题,其可能比数字货币本身更具价值的本质特点受到了越来越多的重视。

    一以概之,区块链的价值在于其安全性上——它能够解决信任问题。

    1.2 区块链的共识机制


    自互联网连接世界各地的计算机及人类之后,信任问题都是由第三方企业、银行机构、政府部门等大型中介解决的。人们在网络上进行身份认证、银行转账、消费交易,都是基于对这些大型中介的信任。大型中介在运营平台,提供服务的同时,从大量的交易中抽取一部分佣金作为利润来源,“雪球”随之越滚越大,人们对其的信任感也随着“网络效应”持续增长。

    与此相反,区块链提出的是一种所谓的“机器信任”或“民主化的信任”。在区块链社区的对等网络中,没有管理员之类的角色对于人们的交易进行集权控制,而是使用共识机制对于人们的交易行为进行验证确认,并在网络中直接对于价值信息进行传递。也就是说在一个互不信任的网络中,区块链中提出的解决方案是让各个节点出于自身利益最大化的考虑,自动遵循某种规则进行交易记录的真实性验证,然后将经过判断后真实的交易记入区块链中。

    目前,现在常见的共识机制为四种,分别是工作量证明算法(PoW)、权益证明算法(PoS)、股份授权证明(DPoS)以及Pool验证池。

    在大家平常比较熟悉的比特币中使用的共识算法既是工作量证明算法(PoW),网络中的节点需要通过一定工作量的计算得到随即哈希散列的数值解,才能通过节点竞争获取记账的权利(挖矿)。一般节点的计算机算力越强越容易得到记账权利及相应奖励。但这种共识机制会有一些局限,也因为耗费计算机算例和资源遭人诟病。

    随后在PoW的基础上改进而来的PoS——权益证明机制,需要记账用户对于区块链中数字资产的所有权益的证明。相对而言,拥有数字资产越多,寻找随机数的速度就越快。因为拥有资产越多的人,也越不愿意自己的资产蒙受损失吧。

    二、区块链安全优势

    如今黑客可以破坏整个网络、篡改数据或诱导粗心的用户落入安全陷阱。他们窃取盗用身份信息,并通过对中心化数据库的攻击及单点故障引发其他安全威胁。但区块链技术中的数据存储和共享数据的模式,与目前信息安全是截然不同的做法。比特币和以太坊都使用相同的密码学技术来保障安全交易,但现在也能够作为一种防范安全攻击和安全威胁的工具。

    区块链在信息安全上的的优势主要在于以下三个方面:

    1.利用高冗余的数据库保障信息的数据完整性

    2.利用密码学的相关原理进行数据验证,保证不可篡改

    3.在权限管理方面,运用了多私钥规则进行访问权限控制

    利用区块链的安全优势可以进行多重安全应用的开发。目前已有的安全应用场景是PKI,认证等,在此可以简单以两个例子进行说明。

    由MIT开发的CertCoin可能是第一个应用基于区块链的PKI。 PKI是一种常见形式的公钥密码可以用来保护邮件,消息应用,网站和其他形式的通讯。然而由于多数PKI接口需要依赖中心化的,受信任的第三方认证机构(CA)来发行、吊销、和为每个参与者保存钥匙对,黑客能够通过冒用用户的身份进入加密通讯中获取信息。而CertCoin移除了中心化的权利机构,使用区块链作为分布式账本分发公钥,能够有效降低黑客单点侵入的风险。


    而在认证领域,同样也有很多例子,如公政通Factom系统。它基于区块链建立链式结构的存储,将认证分解成存在性证明、过程性证明和可审计证明三部分。对于任何数字资产的认证处理,都可以按照这三个步骤实现数据记录的安全性和监督合规。

    三、 区块链安全局限

    然而,现在的区块链尽管不断得到研究、应用,依旧存在着一定的安全局限,导致在技术层和业务层都面临诸多挑战。这些挑战也许会是区块链得到更广泛推广及应用时的难点,也可能成后今后区块链技术的技术突破点。

    3.1 对共识机制的挑战

    对于区块链技术中的共识算法现在已经提出了多种共识机制,最常见的如PoW、PoS系统。但这些共识机制是否能实现并保障真正的安全,需要更严格的证明和时间的考验。

    区块链中采用的非对称加密算法可能会随着数学、 密码学和计算技术的发展而变的越来越脆弱。以现在超级计算机的算力为例,产生比特币SHA256哈希算法的一个哈希碰撞大约需要2^48年, 但随着今后量子计算机等新计算技术的发展, 未来技术中对于非对称加密算法可能具有一定的破解性。其次,在比特币的机制下,私钥是存储在用户的本地终端中,如果用户的私钥被偷窃,依旧会对用户的资金造成严重损失。区块链技术上的私钥是否容易窃取的问题仍待进一步的探索与解决。

    3.2 51%攻击

    在比特币中,如果一个人控制节点中绝大多数的计算资源,他就能掌控整个比特网络并可以按照自己的意愿修改公有账本。这被称为51%攻击,一直是比特币系统中受到诟病的设计之一。

    即便这还是一种理论上的假想,这种51%攻击会很有意思。由于真实的区块链网络是自由开放的,所以区块链网络上没有一个管理员能够阻止拥有足够多计算资源(实际上会需要花费很多资源)的人做任何事情。如果这样的攻击发生了,该种数字货币的信用可能会丧失,货币价值会迅速下降。

    拥有整个网络51%算力的人可以做到以下这些事情:

    1.他们可以不经过验证就阻止交易的发生,让交易变得无效,潜在地阻止人们交易货币。

    2.他们在掌控网络的这段时间内也可能你想改变交易的双方(出现双重花费问题),并且可能阻止其他的人寻找到新的区块。

    比特币和其他加密货币都是基于区块链系统的,因此也可以称作分布式账本。这些数字文件记录在每个交易中被创造出来,存储在加密货币网络上,网络节点上的任何用户都可以浏览记录,这意味着没有人能够将货币花费两次,如果能够伪造货币进行支付,这会迅速摧毁对于该种货币价值的信任。

    由于区块链中包含了一系列的区块,区块中包含的是大量存储在一段给定时间内完成的交易数据(对于比特币,大约每10分钟产生一个新区块)。一旦区块被发现,或“挖矿”成功,就不再能够改变,因为分布式账本中的伪造版本很快就能被网络用户识别并遭到拒绝。然而,通过控制网络上绝大部分的算力(>51%),攻击者就可以介入记录新区块的过程中。他们可以阻止其他挖矿者的开采,从理论上垄断新区块的生成并获取成果(对于比特币,奖励会是12.5个新比特币,数量是会随着时间递减,最后会降到0)。他们可以阻止其他用户进行交易。他们可以进行交易,然后撤销交易,表现成依然拥有刚才支付出去的货币的样子。这个漏洞,被称为双重花费,是对于区块链而言最急需跨越的加密货币障碍。一个能够允许双重花费的网络系统是会迅速失去用户信任的。但即便是在51%攻击中,攻击者想要改变已经发生在区块上的交易信息依旧是非常困难的。因为攻击开始之前的交易是与之前的区块牢牢绑定在一起的,越历史悠久的交易信息想要修改就越是不可能的。

    拥有足够算力可能会导致系统上的混乱(上述这些都是不被允许的事情),但并不会迅速破坏这个区块网络体系——至少不是在很短的时间内。他们无法反转以前发生的历史交易,也无法凭空创造新的资产(除非是正常挖矿),或者从其他用户的钱包中偷取数字资产。

    在现实情况下,发起51%攻击是具有一定可行性的,特别是随着矿池兴起的当下。尽管攻击者的潜在威胁并不大,我们也应该考虑到这种针对区块链系统的安全威胁的存在并寻找解决策略。

    3.3  N@S攻击 

    针对PoW共识算法容易遭受51%攻击的问题,一些研究人员对PoW共识算法进行了改进,现在有基于PoS共识的区块链系统。它一定程度上已经能够面对51%攻击问题,但在解决旧有问题的同时,也引入了区块分叉时的N@S (Nothing at stake)攻击问题。

    虽然每个区块只有一个父区块,但在某些情况下一个区块可以暂时拥有两个子区块。这种分叉状态出现时,一般是两个矿工都在短时间内发现了工作量的解答,然后将所添加的传播到临近网络中去,其他节点便慢慢形成两种版本(子区块)的区块链。区块链中规定,这种情况下的判定条件是选择工作总量最大的那条子区块。

    出于自身利益最大的考虑,节点上可以同时在两个分支上继续工作,以保证利益的获取。假设99%的节点用户都如此理性地考虑收益的话,即便攻击者只掌握1%的权益依旧可以决定区块链的分支走向。他可以发出交易请求,获得资产 ,然后再从另一条分支上获取添加货币到自己的钱包中。

    现在已有中国研究者提出了共识算法改进建议,通过构造同时依赖高算力和高内存的PoW共识算法可以实现51 %攻击问题的解决,但更为安全和有效的共识机制尚有待于更加深入的研究和设计。

    四、结束语


    科技的精灵已经从瓶中跑了出来,但我们还不知道真正降临的时刻。

    ——Don Tapscott

    优势与局限,恰如剑之双刃,我们期待着区块链技术的更新和完善,盼望理念真正落入现实——建立起去中心化的可信网络。随着互联网与信息技术的快速发展的当今时代,区块链技术给我们带来了新的可能的同时也提出了新的安全挑战,如何挖掘和完善新技术的优势并不断破除安全局限,也许是下阶段需要思考的问题。


    展开全文
  • 【Java并发编程实战】02线程安全性

    万次阅读 多人点赞 2020-01-03 19:03:00
    当多个线程访问某个类时,不管运行时环境采用何种调用方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
  • 依赖倒置原则

    千次阅读 2013-01-08 15:12:51
    对于研究一个设计看它是否能够在另一个不同的应用中复用的设计人员,一个设计在新应用中的良好表现留下深刻的印象。但是,如果一个设计内部是严重相互依赖的,设计人员把需要的部分从其它不需要的部分中分离出来就会...
  • HMAC算法安全性浅析

    千次阅读 2016-06-30 22:55:20
    HMAC算法安全性浅析  1 引言  2004年8月17日在美国加州圣巴巴拉召开的国际密码学会议(Crypto’2004)上,来自中国山东大学的王小云教授做了破译MD5、HAVAL-128、 MD4和RIPEMD算法的报告。报告结束时,与会者长...
  • 通过前面六篇文章,我们开启...并发编程中我们需要注意的问题有很多,很庆幸前人已经帮我们总结过了,主要有三个方面,分别是:安全性问题、活跃问题和性能问题。下面我就来一一介绍这些问题。 安全性问题 相信你...
  • 这是作者的系列网络安全自学教程,主要是关于网安工具和实践操作的在线笔记,特分享出来与博友共勉,希望您们喜欢,一起进步。前文分享了Web渗透的第一步工作,涉及网站信息、域名信息、端口信息、敏感信息及指纹...
  • 学习过Spring框架的人一定都会听过Spring的IoC(控制反转) 、DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC 、DI这两个概念是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring...
  • 模糊测试--强制性安全漏洞发掘

    千次阅读 2014-01-20 09:06:19
    文档分享地址链接:... 前 言 我知道"人类和鱼类能够和平共处" 。...--George W....模糊测试的概念至少已经流传了20年,但是直到最近才引起广泛的关注。安全漏洞困扰了许多流行的客户端应用程序
  • 本篇将带领大家进入并发编程领域,说到并发编程,不得不提的就是线程安全性。 在构建稳健安全的并发程序的时候,必须正确的使用线程和锁,但这只是实现机制,核心在于对状态访问操作进行管理,尤其是共享状态...
  • 现实一再证明,并不存在绝对安全的系统,也就说没有万全之法能使得企业和个人免受攻击。在这个万物互联时代里,我们能做的就是接受自己随时可能遭受网络攻击的前提假设,多为安全做些考量。
  • App安全之网络传输安全

    千次阅读 2016-07-15 09:14:59
    移动端App安全如果按CS结构来划分的话,主要涉及客户端本身数据安全,Client到Server网络传输的安全,客户端本身安全又包括代码安全和数据存储安全。所以当我们谈论App安全问题的时候一般来说在以下三类范畴当中。 ...
  • 安全测试===8大前端安全问题(下)

    千次阅读 2018-11-28 11:37:03
    防火防盗防猪队友:不安全的第三方依赖包 用了HTTPS也可能掉坑里 本地存储数据泄露 缺乏静态资源完整校验 防火防盗防猪队友:不安全的第三方依赖包 现如今进行应用开发,就好比站在巨人的肩膀上写代码。据...
  • 如果您熟悉使用 Maven 或 Ivy 的方法,那么你会很高兴地知道,Gradle 完全兼容这两种方法,除此之外,它还有足够的灵活,以支持完全自定义的方法。 这里是 Gradle 支持的依赖管理的主要亮点: 依赖管理传递: ...
  • Python依赖管理那点事

    千次阅读 2019-06-30 10:29:09
    关于Python依赖管理的一些碎碎念。
  • web常见安全漏洞

    万次阅读 2018-09-08 10:04:25
    随着Web2.0、网络社交等一系列新型的互联网产品的诞生,基于Web环境的...Web业务的迅速发展吸引了黑客们的强烈关注,接踵而至的就是Web安全威胁的凸显。黑客利用网站操作系统的漏洞和Web服务程序的SQL注入漏洞...
  • import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger;... * 线程安全: 当多个线程访问某个类时, 这个类始终都能表现出正确的行为, 那么就称这个类是线程安全的。 * @au
  • websocket安全分析

    万次阅读 2016-11-17 14:13:52
    摘要 WebSocket为web应用和服务提供了双向实时...同样,这篇论文提出了在web浏览器中应该有一些安全的特性去余额宝用户的安全。浏览器供应商在提供安全特性方面充当着重要角色。WS至今还没有标准化,但是WS的利用会
  • 硬件安全技术-芯片安全设计技术一

    千次阅读 2020-05-27 19:35:36
    在谷歌采用TLS(安全传输层协议)不久后,ChaCha20和Poly1305均用在OpenSSH中的ChaCha20-Poly1305新算法中,这使得OpenSSH可能避免因编译时间对OpenSSL产生依赖。ChaCha20还用于OpenBSD(一种多平台类UNIX操作系统)...
  • 线程安全

    千次阅读 2020-01-12 23:49:03
    线程安全
  • 接口常见安全漏洞说明

    千次阅读 2019-12-31 18:19:48
    可利用:★★★ 普遍:★★★ 危害:★★★ 漏洞描述: 攻击者可以通过操作请求中发送的对象的ID,导致未经授权访问敏感数据暴露。这个问题在基于API的应用程序中非常常见,因为服务器组件通常不完全跟踪客户...
  • 历史信息安全事件回顾

    千次阅读 2018-09-15 10:43:08
    1、信息风险隐患驱动信息安全发展:科技脆弱、缺乏安全管理、不规范的配置等; 2、企业安全事件频发聚焦社会关注:内鬼勾结外泄、离职员工泄露、黑客攻击资历被盗等 3、政策法律驱动网络安全行业发展:国家网络...
  • 这是作者的系列网络安全自学教程,主要是关于网安工具和实践操作的在线笔记,特分享出来与博友共勉,希望您们喜欢,一起进步。这篇文章主要分享作者10月24日参加上海GeekPwn极客大赛的体会,包括各种安全技术、...
  • 安全套接层(SSL)协议

    千次阅读 2017-08-16 11:15:43
    随着计算机网络技术向整个经济社会各层次延伸,整个社会表现对Internet、Intranet、Extranet等使用的更大的依赖性。随着企业间信息交互的不断增加,任何一种网络应用和增值服务的使用程度将取决于所使用网络的信息...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 106,749
精华内容 42,699
关键字:

安全依赖型的表现