线程安全问题分析:
https://blog.csdn.net/yojofly/article/details/115386382
造成线程安全的原因:
产生原因,cpu,内存,io的读写速率不同,cpu>内存>io
1、原子性问题
2、可见性问题
3、有序性问题
join()规则
如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作(改变公共变量)happens-before(多线程可见)于线程A
package com.jokin.learn.Thread;
import java.util.concurrent.atomic.AtomicInteger;
public class Threaddemo {
public static AtomicInteger atomicInteger = new AtomicInteger(0);
static Integer a=1;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[5];
Thread t1 = new Thread(()->{
try {
Thread.sleep(1000); //因为延迟线程,导致主线程先执行完
} catch (InterruptedException e) {
e.printStackTrace();
}
a=2;
System.out.println("子线程的值: "+a);
});
System.out.println("子线程start前主线程的值: "+a);
t1.start();
System.out.println("join前主线程的值: "+a);
t1.join();//加入 join 则子线程插队, 主线程在此处 会阻塞等待 ; 没有此句 主线程最后输出会直接 a==1
System.out.println("最后主线程的值: "+a);
}
}
输出结果:
子线程start前主线程的值: 1
join前主线程的值: 1
子线程的值: 2
最后主线程的值: 2
Process finished with exit code 0
start()规则
如果线程A执行操作ThreadB.start()启动B线程,那么A线程的ThreadB.start()操作happens-before与线程B中的任意操作
即:主线程调用t1.start() 之前,所有对共享变量a的修改,在子线程t1中均可见 即a=3对所有子线程均可见。但是 start之后对共享变量的变更a=4是否对子线程可见(生效)要看执行效率 是有线程安全问题的
package com.jokin.learn.Thread;
import java.util.ArrayList;
public class Threaddemo2 {
static Integer a=1;
public static void main(String[] args) throws InterruptedException {
ArrayList<Thread> al = new ArrayList();
for (int i = 0; i < 2; i++) {
Thread t1 = new Thread(()->{
System.out.println("子线程的值: "+a);
});
al.add(t1);
}
a=3;
System.out.println("子线程start前主线程的值: "+a);
for (Thread temp:al
) {
temp.start();
}
Thread.sleep(1000);//不加sheep就则子线程也会被a=4赋值改变
a=4;
System.out.println("最后主线程的值: "+a);
}
}
volatile可以用来解决可见性和有序性问题
可见性,有序性的解决方案
volatile,synchronized,final关键字
原子性问题的解决方案
1、synchronized,
2、Lock
3、JUC包下的Atomic类
同步关键字synchronized
可以解决可见性,原子性,有序性问题
synchronized锁的范围
对于普通同步方法,锁是当前实例对象
对于静态同步方法,锁是当前类的class对象
对于同步方法块,锁是synchronized括号里配置的对象
public class SynchronizedTest {
//对象锁
public synchronized void test1(){}
//类锁
public static synchronized void test2(){}
public static void main(String[] args) {
SynchronizedTest test1 = new SynchronizedTest();
SynchronizedTest test2 = new SynchronizedTest();
//这里开启两个线程,不会存在互斥现象,因为synchronized加载普通方法上
new Thread(()->{
test1.test1();
}).start();
new Thread(()->{
test2.test1();
}).start();
//如果要实现互斥,必须采用同一个对象调用
new Thread(()->{
test1.test1();
}).start();
new Thread(()->{
test1.test1();
}).start();
//当锁加在static静态方法上,这里不管是否使用同一个对象,都能实现互斥关系,因为是类锁
new Thread(()->{
test1.test2();
}).start();
new Thread(()->{
test2.test2();
}).start();
}
public void test3(){
//这里的this,可以用SynchronizedTest test1 = new SynchronizedTest();替换表示对象所
//也可以使用SynchronizedTest.class对象替换表示类锁
synchronized (this){
}
}
}
volatile关键字分析
volatile可以用来解决可见性和有序性问题
实例:代码中加入volatile关键字
public class Stopdemo2 {
//public volatile static boolean stop = false;
public static boolean stop = false;// 不加volatile 则 子线程while中无限循环,进程不会停止;即stop = true被中高速缓存区打入内存,让子线程可见
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
int i = 0;
while(!stop) { //while(true)
i++;
}
System.out.println("运行结束: "+i);
});
t1.start();
Thread.sleep(1000);
stop = true;
System.out.println("赋值 stop = true");
}
}
JUC包下的Atomic类 实例:
atomicInteger.incrementAndGet();
public class AtomicTest {
//public static int i = 0;
public static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void inc() throws InterruptedException {
Thread.sleep(1);
atomicInteger.incrementAndGet();
//i++;
}
public static void main(String[] args) throws InterruptedException {
for (int i1 = 0; i1 < 1000; i1++) {
new Thread(()->{
try {
AtomicTest.inc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
//休眠4秒的原因为了让子线程执行完
Thread.sleep(4000);
//System.out.println(i);
System.out.println(atomicInteger);
}
}
ThreadLocal的使用和原理(确保线程安全)
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal
是除了加锁这种同步方式之外的另一种保证多线程访问变量时的线程安全的方法;如果每个线程对变量的访问都是基于线程自己的变量这样就不会存在线程不安全问题。
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized
来保证同一时刻只有一个线程对共享变量进行操作。这种情况下其实还可以将变量放到ThreadLocal
类型的对象中,使变量在每个线程中都有独立拷贝,在一个线程中对变量的任何操作都不会影响到其它线程的变量。在很多情况下,ThreadLocal
比直接使用synchronized
同步机制解决线程安全问题更简单,更方便,同时还能保证程序的性能。
https://zhuanlan.zhihu.com/p/133433717
ThreadLocal 是否绝对线程安全??? 见文章