作者专注于Java、架构、Linux、小程序、爬虫、自动化等技术。 工作期间含泪整理出一些资料,微信搜索【程序员高手之路】,回复 【java】【黑客】【爬虫】【小程序】【面试】等关键字免费获取资料。
前言
线程安全一般是多线程的安全,首先可以了解一些知识点:
一、什么是线程安全
当多个线程访问一个类(对象或者方法),被访问者始终都能表现出正确的行为,那么这个类(对象或者方法)就是线程安全的。
二、保证线程安全的三个特性
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的随机分配,可以使用队列解决