当多个线程要共享一个实例对象的值的时候,那么在考虑安全的多线程并发编程时就要保证下面3个要素。
原子性(Synchronized, Lock)一个或某几个操作只能在一个线程执行完之后,另一个线程才能开始执行该操作,也就是说这些操作是不可分割的,线程不能在这些操作上交替执行。
可见性(Volatile,Synchronized,Lock)一个线程对共享变量的修改,另一个线程能够立刻看到。
有序性(Volatile,Synchronized, Lock)程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排)。
当完成了并发三要素后,自然也就解决了线程安全问题。
线程安全问题:当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
原子性:
看一个例子:
i++;
它相当于三个原子性操作:
1.读取变量i的值;
2.将变量i的值加1;
3.将结果写入变量i中。

1.线程1执行自增方法时先读取i的值,发现是5,此时切换到线程2执行自增方法时,读取到变量i的值也是5,
2.线程1执行将变量i的值加1的操作,线程2也执行此操作,
3.线程1将结果赋给变量i,线程2也将结果赋给变量i。
这两个线程都执行了一次自增方法之后,最后的结果都是i从5变到了6,而不是我们想要的7....
由于CPU的速度非常快,这种交叉执行在执行次数较低的时候体现的并不明显,但是在执行次数非常多的时候,就十分明显了。也就是说,线程的切换会带来原子性的问题。
如何解决原子性问题?
1.尽量使用局部变量,局部变量是存储在栈内存的。线程是私有的,局部变量和方法是不可共享的。
public void threadMethod(int j) {int i = 1;j = j + i;}
这段代码就不会出现线程安全问题,两个线程同时访问这个方法,因为没有共享的数据,所以他们之间的行为,并不会影响其他线程的操作和结果。
2.加锁 synchronized 或者 lock
synchronized 代码举例:
public class ThreadDemo {int count = 0; // 记录方法的命中次数public synchronized void threadMethod(int j) {count++ ;int i = 1;j = j + i;}}
对于成员方法来说,我们可以直接用this作为锁。
对于静态方法来说,我们可以直接用Class对象作为锁
lock代码举例
private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子类private void method(Thread thread){lock.lock(); // 获取锁对象try {System.out.println("线程名:"+thread.getName() + "获得了锁");// Thread.sleep(2000);}catch(Exception e){e.printStackTrace();} finally {System.out.println("线程名:"+thread.getName() + "释放了锁");lock.unlock(); // 释放锁对象}}
lock和synchronized区别:一个是手动挡一个是自动档。手动挡的自由度更大,lock可以有更多的操作,像是trylock。Lock在获取锁的时候,如果拿不到锁,就一直处于等待状态,直到拿到锁,但是tryLock()却不是这样的,tryLock是有一个Boolean的返回值的,如果没有拿到锁,直接返回false,停止等待,它不会像Lock()那样去一直等待获取锁。
可见性:
缓存导致的可见性

举个栗子:
int v = 0;// 线程 A 执行v=v+1 ;// 线程 B 执行System.out.print("v=" v);
如何保证可见性
禁用缓存能保证可见性,volatile关键字可以禁用缓存。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取共享变量时,它会去内存中读取新值。
普通的共享变量不能保证可见性,因为普通共享变量被修改后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。
更新主存的步骤:当前线程将其他线程的工作内存中的缓存变量的缓存行设置为无效,然后当前线程将变量的值跟新到主存,更新成功后将其他线程的缓存行更新为新的主存地址
其他线程读取变量时,发现自己的缓存行无效,它会等待缓存行对应的主存地址被更新之后,然后去对应的主存读取最新的值。
有序性
导致有序性的原因是编译优化
int a = 0; // 语句 1int b = 0; // 语句 2i ; // 语句 3b ; // 语句 4
再来个例子:
// 线程 1init();inited = true;// 线程 2while(inited){work();}
init(); 与 inited = true; 并没有数据的依赖,在单线程看来,如果把两句的代码调换好像也不会出现问题。
但此时处于一个多线程的环境,而处理器真的把这两句代码重新排序,那问题就出现了,若线程 1 先执行 inited = true; 此时,init() 并没有执行,线程 2 就已经开始调用 work() 方法,此时很可能造成一些奔溃或其他 BUG 的出现。
如何解决?
volatile关键字可以解决这个问题,volatile 关键字可以保证有序性,让处理器不会把这行代码进行优化排序




