线程安全
1.什么是线程安全
在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
2.如何实现线程安全
2.1可以利用三大特性中的一些特性
比如原子性可以保证操作不可分割,可见性保证在缓存中的更新可以更新到内存中,有序性中著名的单例模型也是线程安全的一种体现。。。
如果不太理解,可以查看三大特性
2.2 ThreadLocal类
ThreadLocal能够做到线程独立,是因为值并不存在ThreadLocal中,而是存储在线程对象中。换句话说,我们就是操作缓存中的数据。
/**
* 线程安全 ThreadLocal能够做到线程独立,是因为值并不存在ThreadLocal中,而是存储在线程对象中。
* @author 86184
*
*/
public class ThreadLocalDemo {//线程的本地化,必须设置初始值
private static final ThreadLocal<Integer> local= new ThreadLocal<Integer>() {
protected Integer initialValue() {//初始第一个值为0
return 0;
};
};
public static class DemoThread extends Thread{
@Override
public void run() {
for(int i =1;i<5;i++) {
int curr= local.get();//得到当前数字,放在线程对象中
local.set(curr+1);
System.out.println(this.getName()+":"+local.get());
}
}
}
public static void main(String[] args) {
Thread t1 =new DemoThread();
t1.setName("A");
Thread t2 =new DemoThread();
t2.setName("B");
Thread t3 =new DemoThread();
t3.setName("C");
t1.start();
t2.start();
t3.start();
}
}
2.3加锁
2.3.1 synchronized
synchronized是一种悲观锁。synchronized的实现有两种方式:
1.在方法前面加上关键字synchronized,即实现了方法的同步。
public synchronized void show(){
//功能代码
}
2.在需要同步的区域实现同步块,即实现了对象的同步。
public void show(){
synchronized(this){
//功能代码
}
}
2.3.2 CAS乐观锁
其中,原子性的实现依靠了 Compare And Swap(CAS)算法进行实现。顾名思义,该算法涉及到了两个操作,
比较(Compare)和交换(Swap)。
CAS 算法的思路如下:
- 该算法认为不同线程对变量的操作时产生竞争的情况比较少。
- 该算法的核心是对当前读取变量值 E 和内存中的变量旧值 V 进行比较。
- 如果相等,就代表其他线程没有对该变量进行修改,就将变量值更新为新值 N。
- 如果不等,就认为在读取值 E 到比较阶段,有其他线程对变量进行过修改,不进行任何操作。
但是,这样的话,我们又会遇到新的问题。我们无法知道当前值是否只有我们改变,或者是被其他线程改变多次后又复原。这就是ABA。
CAS 算法是基于值来做比较的,如果当前有两个线程,一个线程将变量值从 A 改为 B ,再由 B 改回为
A ,当前线程开始执行 CAS 算法时,就很容易认为值没有变化,误认为读取数据到执行 CAS 算法的期
间,没有线程修改过数据。
那么这个问题该怎么解决呢??
我们可以新增一个版本号,每次使用的时候版本号加一,每次变量更新的时候版本号加一。我们就可以根据版本号来判断是否被其他线程使用过。
原子性就是CAS算法的常用场景。。。
2.4 ReentrantLock(重入锁)
jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。
这里我们通过模拟买卖票来了解一下重入锁的功能。
public class Resource {
public String name;
public Resource(String name) {
this.name=name;
}
@Override
public String toString() {
return "Resource [name=" + name + "]";
}
}
import java.util.concurrent.locks.ReentrantLock;
public class Ticket {
private int num =100;
private static final ReentrantLock lock =new ReentrantLock();//创建一个ReentrantLock锁
/*
* 线程安全:1。加synchronized锁 ----------->public synchronized void sold(String name)
* 2.加ReentrantLock锁
*/
//卖票
public void sold(String name) {
lock.lock();//枷锁
try {
int s =--num;
System.out.println(name+"卖出一张票,剩余"+s);
} finally {
lock.unlock();//释放锁
}
}
public class TicketThread extends Thread{
private Ticket ticket;
private String name;
//全参构造方法
public TicketThread(Ticket ticket,String name) {
this.ticket=ticket;
this.name=name;
}
@Override
public void run() {
while(ticket.getNum()>0) {
ticket.sold(name);
try {
sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class TicketTest {
public static void main(String[] args) {
Ticket ticket =new Ticket();//创建一个新的ticket对象
TicketThread A =new TicketThread(ticket, "A");
TicketThread B =new TicketThread(ticket, "B");
TicketThread C =new TicketThread(ticket, "C");
A.start();
B.start();
C.start();
}
}
这样就不会产生一张票被重复卖两次等线程不安全的情况了。。。