坚持学习,总会有一些不一样的东西。
引用一下百度百科的定义——
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
文字定义总是很含糊,举个反例就很清楚了,想起之前总结过单例模式,就从单例模式开始吧。如果不清楚单例模式的新同学,可以看一下这篇总结:
java中全面的单例模式多种实现方式总结
单例模式中,懒汉式的实现方案如下:
该方法在单线程中运行是没有问题的,但是在多线程中,某些情况下,多个线程同时都判断到 sSingleton == null,然后又都执行 sSingleton = new Singleton(),这样就不能保证单例了,我们说它不是线程安全的。
一种改进方法:
上面这种实现,实际上效率非常低,是完全不推荐使用的。主要是因为加了 sychronized 关键字,意为同步的,也就是内置锁。使用 synchronized 关键字修饰方法, 是对该方法加锁,这样在同一时刻,只有一个线程能进入该方法,这样保证了线程安全,但是也正因为如此,效率变得很低,因为当对象创建之后,再次调用该方法的时候,直接使用对象就可以了,无需再同步了。于是有了下面改进的实现方式—— DCL(双重检查锁):
public class Singleton { private Singleton() { } /** * volatile is since JDK5 */ private static volatile Singleton sSingleton; public static Singleton getInstance() { if (sSingleton == null) { synchronized (Singleton.class) { // 未初始化,则初始instance变量 if (sSingleton == null) { sSingleton = new Singleton(); } } } return sSingleton; } }sSingleton = new Singleton() 不是一个原子操作。故须加 volatile 关键字修饰,该关键字在 jdk1.5 之后版本才有。下面就来说说 synchronized 和 volatile 这两个关键字。
二、同步与 synchronized synchronized 锁代码块java 提供了一种一种内置锁,来实现同步代码块,同步代码块包含两个部分:一个作为锁的对象引用,一个由锁保护的代码块。形式如下:
synchronized (lock) { // 由锁保护的代码块 }每个 java 对象都可以作为实现同步的锁,java 的内置锁也称为互斥锁。同一时刻只能有一个线程获得该锁,获得该锁的线程才能进入由锁保护的代码块,其它线程只能等待该线程执行完代码块之后,释放该锁后,再去获得该锁。例子:
public class SynchronizedDemo1 { private Object lock = new Object(); public static void main(String[] args) { SynchronizedDemo1 demo = new SynchronizedDemo1(); new Thread(() -> { try { demo.test1(); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); try { demo.test1(); } catch (InterruptedException e) { e.printStackTrace(); } } public void test1() throws InterruptedException { System.out.println("--- test1 begin - current thread: " + Thread.currentThread().getId()); Thread.sleep(1000); synchronized (lock) { System.out.println("--- test1 synchronized - current thread: " + Thread.currentThread().getId()); Thread.sleep(5000); } System.out.println("--- test1 end - current thread: " + Thread.currentThread().getId()); } }执行结果:
从结果可以清楚地看到一个线程进入同步代码块之后,另一个线程阻塞了,需要等到前者释放锁之后,它获得锁了才能进入同步代码块。
上面代码中,我们创建的 lock 对象作为锁。用 synchronized 修饰方法又是什么充当了锁呢?
synchronized 修饰方法以关键字 synchronized 修饰的方法就是一种横跨挣个方法的同步代码块,其中该同步代码块的锁就是调用该方法的对象。
class A { public synchronized void a(){ System.out.println("hello"); } }等价于
class A { public void a(){ synchronized(this) { System.out.println("hello"); } } }静态方法用 类名.方法名 来调用,以关键字 synchronized 修饰的静态方法则以 Class 对象作为锁。
class A { public static synchronized void a(){ System.out.println("hello"); } }等价于
class A { public void a(){ synchronized(A.class) { System.out.println("hello"); } } }