架构系列——线程安全的简单探索

前言

线程安全一般是多线程的安全,首先可以了解一些知识点:

架构系列——进程与线程的关系探索

架构系列——并发、并行与多线程关系探索

一、什么是线程安全

当多个线程访问一个类(对象或者方法),被访问者始终都能表现出正确的行为,那么这个类(对象或者方法)就是线程安全的。

二、保证线程安全的三个特性

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中如何保证线程安全性

[2].java线程安全问题以及同步的几种方式

发布了131 篇原创文章 · 获赞 1663 · 访问量 56万+

猜你喜欢

转载自blog.csdn.net/qq_26230421/article/details/104250756