Java学习---多线程之锁机制

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wangxiaobinglanmuyu/article/details/49799225

在程序中,对共享变量的使用一般遵循一定的模式,即读取、修改和写入三步组成。之前碰到的问题是,这三步执行中可能线程执行切换,造成非原子操作。锁机制是把这三步变成一个原子操作。

1、synchronized
把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility)。

1.1 原子性
原子性意味着个时刻,只有一个线程能够执行一段代码,这段代码通过一个monitor object保护。从而防止多个线程在更新共享状态时相互冲突。

1.2 可见性
可见性则更为微妙,它要对付内存缓存和编译器优化的各种反常行为。它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 。

作用:如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。
原理:当对象获取锁时,它首先使自己的高速缓存无效,这样就可以保证直接从主内存中装入变量。 同样,在对象释放锁之前,它会刷新其高速缓存,强制使已做的任何更改都出现在主内存中。 这样,会保证在同一个锁上同步的两个线程看到在 synchronized 块内修改的变量的相同值。

一般来说,线程以某种不必让其他线程立即可以看到的方式(不管这些线程在寄存器中、在处理器特定的缓存中,还是通过指令重排或者其他编译器优化),不受缓存变量值的约束,但是如果开发人员使用了同步,那么运行库将确保某一线程对变量所做的更新先于对现有synchronized 块所进行的更新,当进入由同一监控器(lock)保护的另一个synchronized 块时,将立刻可以看到这些对变量所做的更新。类似的规则也存在于volatile变量上。

——volatile只保证可见性,不保证原子性!

1.3 何时要同步?
可见性同步的基本规则是在以下情况中必须同步:
读取上一次可能是由另一个线程写入的变量
写入下一次可能由另一个线程读取的变量
一致性同步:当修改多个相关值时,您想要其它线程原子地看到这组更改—— 要么看到全部更改,要么什么也看不到。

这适用于相关数据项(如粒子的位置和速率)和元数据项(如链表中包含的数据值和列表自身中的数据项的链)。

在某些情况中,您不必用同步来将数据从一个线程传递到另一个,因为 JVM 已经隐含地为您执行同步。这些情况包括:

由静态初始化器(在静态字段上或 static{} 块中的初始化器)
初始化数据时
访问 final 字段时 ——final对象呢?
在创建线程之前创建对象时
线程可以看见它将要处理的对象时

1.4 synchronize的限制
synchronized是不错,但它并不完美。它有一些功能性的限制:

它无法中断一个正在等候获得锁的线程;
也无法通过投票得到锁,如果不想等下去,也就没法得到锁;
同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。
2、读写锁ReadWriterLock
例子:

package com.thread;

import java.util.Random;

public class SynData {
    private int data;//共享数据
    public synchronized void set(int data) {    
        System.out.println(Thread.currentThread().getName() + "准备写入数据");    
        try {    
            Thread.sleep(20);    
        } catch (InterruptedException e) {    
            e.printStackTrace();    
        }    
        this.data = data;    
        System.out.println(Thread.currentThread().getName() + "写入" + this.data);    
    }       
    public synchronized  void get() {    
        System.out.println(Thread.currentThread().getName() + "准备读取数据");    
        try {    
            Thread.sleep(20);    
        } catch (InterruptedException e) {    
            e.printStackTrace();    
        }    
        System.out.println(Thread.currentThread().getName() + "读取" + this.data);    
    }    

    public static void main(String[] args) {
        final SynData data = new SynData();    

      //写入  
      for (int i = 0; i < 3; i++) {    
          Thread t = new Thread(new Runnable() {    
              @Override  
      public void run() {    
                  for (int j = 0; j < 5; j++) {    
                      data.set(new Random().nextInt(30));    
                  }    
              }    
          });  
          t.setName("Thread-W" + i);  
          t.start();  
      }    
      //读取  
      for (int i = 0; i < 3; i++) {    
          Thread t = new Thread(new Runnable() {    
              @Override  
      public void run() {    
                  for (int j = 0; j < 5; j++) {    
                      data.get();    
                  }    
              }    
          });    
          t.setName("Thread-R" + i);  
          t.start();  
      }    

    }

}

运行结果:

Thread-W0准备写入数据
Thread-W0写入20
Thread-R2准备读取数据
Thread-R2读取20
Thread-R2准备读取数据
Thread-R2读取20
Thread-R1准备读取数据
Thread-R1读取20
Thread-R1准备读取数据
Thread-R1读取20
Thread-R1准备读取数据
Thread-R1读取20
Thread-R1准备读取数据
Thread-R1读取20
Thread-R1准备读取数据
Thread-R1读取20
Thread-R0准备读取数据
Thread-R0读取20
Thread-R0准备读取数据
Thread-R0读取20
Thread-W2准备写入数据
Thread-W2写入27
Thread-W2准备写入数据
Thread-W2写入6
Thread-W2准备写入数据
Thread-W2写入4
Thread-W2准备写入数据
Thread-W2写入12
Thread-W1准备写入数据
Thread-W1写入5
Thread-W1准备写入数据
Thread-W1写入16
Thread-W1准备写入数据
Thread-W1写入21
Thread-W1准备写入数据
Thread-W1写入24
Thread-W1准备写入数据
Thread-W1写入9
Thread-W2准备写入数据
Thread-W2写入11
Thread-R0准备读取数据
Thread-R0读取11
Thread-R0准备读取数据//R0和R2应该可以同时读取数据,不应该互斥!
Thread-R0读取11
Thread-R0准备读取数据
Thread-R0读取11
Thread-R2准备读取数据
Thread-R2读取11
Thread-R2准备读取数据
Thread-R2读取11
Thread-R2准备读取数据
Thread-R2读取11
Thread-W0准备写入数据
Thread-W0写入24
Thread-W0准备写入数据
Thread-W0写入5
Thread-W0准备写入数据
Thread-W0写入15
Thread-W0准备写入数据
Thread-W0写入24

读写线程可以互斥,但是两个读线程应该不用互斥!
故此我们用到了ReadWriteLock 锁:

class Data {
    private int data;// 共享数据    
    private ReadWriteLock rwl = new ReentrantReadWriteLock();       
    public void set(int data) {    
        rwl.writeLock().lock();// 取到写锁    
        try {    
            System.out.println(Thread.currentThread().getName() + "准备写入数据");    
            try {    
                Thread.sleep(20);    
            } catch (InterruptedException e) {    
                e.printStackTrace();    
            }    
            this.data = data;    
            System.out.println(Thread.currentThread().getName() + "写入" + this.data);    
        } finally {    
            rwl.writeLock().unlock();// 释放写锁    
        }    
    }       

    public void get() {    
        rwl.readLock().lock();// 取到读锁    
        try {    
            System.out.println(Thread.currentThread().getName() + "准备读取数据");    
            try {    
                Thread.sleep(20);    
            } catch (InterruptedException e) {    
                e.printStackTrace();    
            }    
            System.out.println(Thread.currentThread().getName() + "读取" + this.data);    
        } finally {    
            rwl.readLock().unlock();// 释放读锁    
        }    
    }    

运行结果:
Thread-R1准备读取数据
Thread-R0准备读取数据
Thread-R0读取0
Thread-R1读取0
Thread-W1准备写入数据
Thread-W1写入5
Thread-W0准备写入数据
Thread-W0写入17
Thread-W0准备写入数据
Thread-W0写入24
Thread-W0准备写入数据
Thread-W0写入25
Thread-W2准备写入数据
Thread-W2写入12
Thread-R2准备读取数据
Thread-R0准备读取数据
Thread-R1准备读取数据
Thread-R0读取12
Thread-R2读取12
Thread-R1读取12
Thread-W1准备写入数据
Thread-W1写入28
Thread-W1准备写入数据
Thread-W1写入24
Thread-W1准备写入数据
Thread-W1写入28
Thread-W1准备写入数据
Thread-W1写入4
Thread-W0准备写入数据
Thread-W0写入26
Thread-W0准备写入数据
Thread-W0写入22
Thread-W2准备写入数据
Thread-W2写入23
Thread-W2准备写入数据
Thread-W2写入28
Thread-W2准备写入数据
Thread-W2写入11
Thread-R0准备读取数据
Thread-R2准备读取数据
Thread-R1准备读取数据
Thread-R0读取11
Thread-R1读取11
Thread-R2读取11
Thread-W2准备写入数据
Thread-W2写入0
Thread-R0准备读取数据
Thread-R1准备读取数据
Thread-R2准备读取数据
Thread-R0读取0
Thread-R0准备读取数据
Thread-R2读取0
Thread-R2准备读取数据
Thread-R1读取0
Thread-R1准备读取数据
Thread-R0读取0
Thread-R2读取0
Thread-R2准备读取数据
Thread-R1读取0
Thread-R2读取0

理论上,使用读写锁允许的并发性增强将带来更大的性能提高。

展开阅读全文

没有更多推荐了,返回首页