在咱们日常的开发工做中,或多或少的都能接触到多线程编程或者一些并发问题,随着操做系统和系统硬件的升级,并发编程被愈来愈多的运用到咱们的开发中,咱们使用多线程的最初的想法是可以更大程度的利用系统资源,可是咱们在使用多线程的时候,也会有一些问题的存在,咱们先来看一段代码。java
private static int i = 0; private static void increse(){ i++; } public static void main(String[] args) { Thread[] threads = new Thread[20]; for (int i = 0; i < threads.length; i++){ threads[i] = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++){ increse(); } } }); threads[i].start(); } while (Thread.activeCount() > 1){ Thread.yield(); } System.out.println(i); }
首先看看这段代码是没有问题的,可是若是在多线程的环境中,这段代码运行的结果基本是都不同的,这里是开启20个线程,而后每个线程调用increse()
方法对变量i
进行一个赋值操做,预期的一个输出应该是200000
,可是为何会每一次的输出都不太同样呢?缘由就在于这个地方i++
,这里就是产生并发问题的根本缘由,那看起来很简单的一个i++
为何会有这种问题?这里就简单的从java内存模型
(JMM)来了解一下,首先JMM
规定,每个线程运行时都会有一个工做内存,而后变量i
是存储在主内存的,每次线程在计算数据的时候都要去主内存中获取当前变量的值,那么简单的来讲,就是一个线程将变量i
计算获得结果后,尚未将这个数据刷新到主内存,在这个时候,其余的线程已经获取到了原来的值,换句话说,本线程中获取到的数据是不是最新的,这个是不知道的。可是这只是从JMM
角度来简单的说一下,i++
这个看似简单的操做其实包含了三个操做,获取i
的值,对i
进行自增,而后对i
进行赋值。就是在这几个操做中,其余的线程会有不少的时间来作不少的事情,那有人会问,是否是将这个i++
操做同步就能够了?是的,那该如何同步呢?编程
有人说用volatile
来修饰一下变量i
不就能够了么?是的,java
中volatile
这个关键字确实是提供了一个同步的功能,可是为何在这里修改一下仍是没有效果呢?缘由就在于若是一个变量用volatile修饰以后,只是会让其余的线程当即可以知道当前变量的值是多少,这里就叫作可见性
,可是仍是解决不了i++
这几个操做的问题,那如何处理,又有人提出用synchronized
这个关键字,不得不认可,这个关键字确实很强大,是可以解决这个问题,那咱们有没有考虑过这个为何能够解决这个问题,是如何解决的,那仍是简单的说一下,首先synchronized
是属于JVM级别的,有这个关键字的方法或者代码块,最后会被解释成monitorenter
和monitorexit
指令,这两个字节码都明确须要一个reference
类型的参数来指出要锁定或者解锁的对象,像这样:安全
public synchronized String f(){ //code } synchronized(object){ //code }
看到这里,咱们应该能明白synchronized
为何能够解决上面程序的问题,可是咱们还应该要明确一个概念就是原子性
,换句话说,就是咱们在处理一些多线程的问题的时候,应该保证一些共享数据的操做是原子性的,这样才能保证正确性,看到这里,相信你也有了一个大概的理解,那咱们来总结一下,在处理多线程的问题的时候,哪些点是值得注意的,可见性
,原子性
,有序性
,这几个点是保证多线程可以正确的一个前提条件,至于什么是有序性,这里涉及到内存指令的重排序,不在讨论范围内,之后再来讨论。多线程
这里还要指出一个问题,就是是否咱们在处理多线程问题的时候,必定要同步,或者说必定要加锁,这个也不是必定的,以前网上有一个说笑的方式,就是咱们在处理多线程的问题的时候,有时候就会发现,代码又被写成了单线程,固然这只是一个玩笑话,可是这里咱们也能看出来,是否是单线程的程序就不会有这些问题?答案是确定的,由于单线程不存在资源竞争的问题,也就不须要再讨论了。并发
那么咱们何时须要使用同步,何时又不须要呢?咱们来看一段代码ide
public String f(String s1, String s2, String s3){ return s1 + s2 +s3; }
这是一个字符串拼接的一个方法,咱们来反编译看一下,这里JVM究竟是怎么作的?优化
这里很明显的可以看出来,最后是经过StringBuilder来为咱们生成了最后的结果,那有人会问,这里线程安全么?是的,这里是线程安全的,由于在这个方法中,虽然也有变量的使用,可是都是属于线程内部在使用,其余的线程根本不会访问到或者说这些变量也不会让其余线程访问到,咱们称其为没有方法逃逸
,也就是说只能在本线程中使用这些变量,这里是线程安全的,至于什么是逃逸分析
,简单的提一下就是这是JVM的高级优化的一种方式,说的再简单一点,就是别的线程访问不到这个变量,这样的代码是不须要同步的。ui
在多线程的问题上面概念比较多,也须要慢慢理解,其实JVM也在多线程的锁的上面作了不少优化,还有互斥同步
和非互斥同步
,还有不少概念,什么是自旋和自适应自旋
,锁消除
(顺便提一下,上面的字符串拼接的例子就是用到了这种优化方式),锁粗化
,咱们下次再继续分享。spa