目录
1、线程安全
当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。
2、内存模型JMM
共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
3、多线程有三大特性
原子性
即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
一个很经典的例子就是银行账户转账问题:
比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
在项目中,只要是全局共享变量,一般都需要加上Volatile关键字
可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值。
在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主存。
有序性
程序执行的顺序按照代码的先后顺序执行。
一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。如下:
int a = 10; //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a*a; //语句4
则因为重排序,他还可能执行顺序为 2-1-3-4,1-3-2-4
但绝不可能 2-1-4-3,因为这打破了依赖关系。
显然重排序对单线程运行是不会有任何问题,而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了。
4、Volatile特性
1.保证此变量对所有的线程的可见性,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存来完成。
2.禁止指令重排序优化。如下:
volatile int a = 10;
int r = 2; //语句1
r = 4; //语句2
a = a + 3; //语句3
r = 2*2; //语句4
语句1、2一定在语句3前执行,而语句4一定在语句3后执行。
volatile 性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
5、ThreadLocal
ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量。
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
class Res {
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
}
};
public Integer getNumber() {
int count = threadLocal.get() + 1;
threadLocal.set(count);
return count;
}
}
public class Test extends Thread {
private Res res;
public Test(Res res) {
this.res = res;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "," + res.getNumber());
}
}
public static void main(String[] args) {
Res res = new Res();
Test t1 = new Test(res);
Test t2 = new Test(res);
t1.start();
t2.start();
}
}
ThreadLoca实现原理
ThreadLoca通过map集合
Map.put(“当前线程”,值);
6、线程安全解决办法
1)Lock锁
Lock lock = new ReentrantLock();
lock.lock();
try{
//可能会出现线程安全的操作
}finally{
//一定在finally中释放锁
//也不能把获取锁在try中进行,因为有可能在获取锁的时候抛出异常
lock.unlock();
}
2)同步代码块synchronized
private static Object obj = new Object();
synchronized (obj){
}
3)同步方法
在方法上修饰synchronized 称为同步方法
①非静态同步方法使用的是this锁
class ThreadDemo implements Runnable {
// 同时多个窗口共享100
private volatile static int count = 100;// 存放方法区中
public boolean flag = true;
public static Object obj = new Object();
public void run() {
if(flag){
while (count > 0) {
synchronized (this){
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",出售" + (100 - count + 1) + "张票");
count--;
}
}
}
}else{
while (count > 0) {
sale();
}
}
}
public synchronized void sale() {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",出售" + (100 - count + 1) + "张票");
count--;
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
ThreadDemo threadDemo02 = new ThreadDemo();
Thread t1 = new Thread(threadDemo02, "窗口1");
Thread t2 = new Thread(threadDemo02, "窗口2");
t1.start();
Thread.sleep(40);
threadDemo02.flag=false;
t2.start();
}
}
②静态同步方法使用的是class锁
class ThreadDemo implements Runnable {
// 同时多个窗口共享100
private volatile static int count = 100;// 存放方法区中
public boolean flag = true;
public static Object obj = new Object();
public void run() {
if(flag){
while (count > 0) {
synchronized (ThreadDemo.class){
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",出售" + (100 - count + 1) + "张票");
count--;
}
}
}
}else{
while (count > 0) {
sale();
}
}
}
public static synchronized void sale() {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (count > 0) {
System.out.println(Thread.currentThread().getName() + ",出售" + (100 - count + 1) + "张票");
count--;
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
ThreadDemo threadDemo02 = new ThreadDemo();
Thread t1 = new Thread(threadDemo02, "窗口1");
Thread t2 = new Thread(threadDemo02, "窗口2");
t1.start();
Thread.sleep(40);
threadDemo02.flag=false;
t2.start();
}
}
Lock与synchronized 关键字的区别
Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
Lock 接口能被中断地获取锁 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到的锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回。