前言
线程安全一般是多线程的安全,首先可以了解一些知识点:
一、什么是线程安全
当多个线程访问一个类(对象或者方法),被访问者始终都能表现出正确的行为,那么这个类(对象或者方法)就是线程安全的。
二、保证线程安全的三个特性
1.原子性
提供互斥访问,同一时刻只能有一个线程对数据进行操作(atomic,synchronized);
synchronized修饰的对象有四种:
(1)修饰代码块,作用于调用的对象;
(2)修饰方法,作用于调用的对象;
(3)修饰静态方法,作用于所有对象;
(4)修饰类,作用于所有对象。
2.可见性
一个线程对主内存的修改可以及时地被其他线程看到(volatile);
3.有序性一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱无序(happens-before原则)。
1.程序次序规则:在一个单独的线程中,按照程序代码书写的顺序执行。
2.锁定规则:一个unlock操作happen—before后面对同一个锁的lock操作。
3.volatile变量规则:对一个volatile变量的写操作happen—before后面对该变量的读操作。
4.线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。
5.线程终止规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。
6.线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件的发生。
7.对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。
8.传递性:如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before操作C。
三、线程安全举例
设计Thread类的子类,总共5个数,每启动一个线程,数量就减一。
package com.han; public class MyThread extends Thread{ private int count = 5; @Override public void run() { count--; System.out.println(this.currentThread().getName() + " count = " + count); } public static void main(String[] args) { MyThread myThread = new MyThread(); Thread t1 = new Thread(myThread, "t1"); Thread t2 = new Thread(myThread, "t2"); Thread t3 = new Thread(myThread, "t3"); Thread t4 = new Thread(myThread, "t4"); Thread t5 = new Thread(myThread, "t5"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); } }
四、出现问题
运行上面的代码出现类似下面的结果:
t1 count = 3 t3 count = 2 t2 count = 3 t4 count = 1 t5 count = 0
有两个问题:
1.剩余数量不正确,本应该是4、3、2、1、0
2.启动顺序不正确,本应该是t1、t2、t3、t4、t5
四、解决问题
1、剩余数量是由run方法里面计算的,显然有多个线程在同一时间拿到了count并进行计算。使用synchronized修饰run方法是一种比较简单的方式,还可以使用Lock,这样就能在同一时间只有一个线程拿到count
2.这个顺序不是代码的顺序,而是CPU的随机分配,可以使用队列解决
参考文献:
[1].Java中如何保证线程安全性