一篇文章让你懂得Java多线程编程

http://www.toutiao.com/i6464063882628432397/


背景:

多线程开发中,重要的一点关注点,就是线程安全,保障数据的一致性,不会出现脏数据。

而对于多线程,主要有三个特性,原子性、可见性、有序性。

线程内存管理:

  1. 每一个线程都拥有一份工作内存,是私有的本地内存(local memory),并将共享变量拷贝一份副本存储在私有本地内存中。

  2. 线程之间通过主内存(main memory)进行资源共享,所有的变量都存储在主内存中。

  3. 线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写主内存中的变量

一篇文章让你懂得Java多线程编程

Java内存机制

线程通信:

一篇文章让你懂得Java多线程编程

线程通信类型

通过线程之间的通信保持数据的一致性。

消息传递:通过显示的方式进行消息的传递,比如说调用wait(),notify()等方法进行通信。线程之间没有公共的状态,只能通过明确的发送消息来进行通信。

共享内存:线程之间通过读写共享变量来进行隐式的通信。线程与进程(本地内存与公共内存),有8种内存操作:lock/unlock,read/load/use,assign/store/write。

一篇文章让你懂得Java多线程编程

原子操作知识图

Volatile:

既然每一个线程都有自己的工作内存,会把变量先创建一个副本存储于自己的工作内存内,也就是说,线程并不会实时的去主内存获取变量的值。如果该变量是一个共享变量,这将会导致一系列的问题。比如,该变量是一个开关,那么它将起不了作为一个开关的作用,此时,我们可以使用volatile。

volatile:告诉线程该变量是易变的,不稳定的,在需要使用到由该关键词修饰的变量时,都需要从主内存中重新获取一次,也就是要同时执行read-load-use,执行后对volatile变量操作后,要同时执行assign-store-write。保证所修饰变量的可见性,在一个线程中修改变量的值以后,在其他线程中能够看到这个值。

然而volatile仅仅保证了可见性,但并没有保证原子性。也就是一个对volatile变量的操作,并不是马上回写到主内存中的。

synchronized:

synchronized主要针对的是执行顺序,通过synchronized来控制一段代码是否允许并发来达到对执行顺序的控制。同时,synchronized还会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性。

Java原子操作atomic

一篇文章让你懂得Java多线程编程

Java提供原子操作

Java自身提供了一些保障原子操作的对象,如上图所示。如我们常用的AtomicInteger,能够保证原子性。它提供了一些自增自减的原子性操作,保障了多线程并发的线程安全。

以上是JAVA多线程涉及一些基础知识,下面我们将通过一个个实例,进一步深入理解多线程。

首先,我们编写一个公共的多线程执行类:

一篇文章让你懂得Java多线程编程

多线程执行类

案例一--没有任何保护的多线程任务:

下面我们采用共享内存的方式进行通信:

一篇文章让你懂得Java多线程编程

简单业务类

一篇文章让你懂得Java多线程编程

运行结果

解析:

每一个线程在执行时,都会执行一次read-load的原子操作。然而大量线程执行read-load操作时,前面的线程还未执行store-write操作进行回写时,后面的线程已经执行了read-load操作。出现read-read-load-load-store-store-write-write的操作。致使一些自加未正确的统计到count中。

案例二--使用volatile保证可见性

一篇文章让你懂得Java多线程编程

使用volatile修饰变量

一篇文章让你懂得Java多线程编程

运行结果

解析:

Volatile仅仅保证了可见性,也就是说,保证了每一个线程使用该变量时,都会从共享内存中去获取最新的值,但并没有保证原子性,意味着,后面的线程去取这个值时,前面的线程并还没有将执行后的值回写到主内存中。

案例三--使用原子操作对象AtomicInteger

一篇文章让你懂得Java多线程编程

使用AtomicInteger保证原子性

一篇文章让你懂得Java多线程编程

运行结果

解析:

终于,这里我们获得了正确的数据了。因为AtomicInteger能够保证对象的原子性。即每次只能有一个线程能够对该对象进行操作,且操作前是从主内存获取并且执行完后又立即回写到主内存中。

案例四--使用synchronized锁住非static方法:

一篇文章让你懂得Java多线程编程

synchronized加锁非static方法

然后我们改写执行类:

一篇文章让你懂得Java多线程编程

更改为每条线程创建一个对象

一篇文章让你懂得Java多线程编程

执行结果

我们再恢复一下多线程执行类:

一篇文章让你懂得Java多线程编程

只创建一个对象

一篇文章让你懂得Java多线程编程

执行结果

一篇文章让你懂得Java多线程编程

但运行结果却在第103条线程结束时已经打印

解析:

首先,我们要正确理解synchronized修饰普通方法时的意义:

一篇文章让你懂得Java多线程编程

如上图,当你使用到如左图methodA时,其实它的真实作用如右图所示。synchronized会锁住自身整个对象。所以在本案例中,我们先采用的是每一条线程new一个新对象,这样,在多线程执行的时候,每一条线程synchronized加锁的都是自己所new的对象,并没有能够起到一个互斥的作用。而在后面,我们修改了一些多线程的代码,所有的线程都共享一个对象,这时候synchronized加锁对象只有一个选择,就能在各个线程中起到了一个互斥的作用。

但在执行过程中,我们print统计对象Count时,在第103条线程执行完就已经print出来,因为此时,主线程已经发起了2000条线程,只是因为互斥,大家都在排队等待执行。

既然synchronized修饰非static方法时,是锁住this,也就是对象本身,那我们扩展一下,以下methodA和methodB可以同时调用吗?

一篇文章让你懂得Java多线程编程

答案是否定的,因为使用这两个方法的其中一个,都需要先加锁该对象,所以这两个方法是互斥。

案例五--使用synchronized锁住static方法

上一个案例,我们讲解了synchronized修饰非static方法,自然也有人会问,如果用synchronized修饰static方法会怎样?

一篇文章让你懂得Java多线程编程

synchronized修饰static方法

一篇文章让你懂得Java多线程编程

执行结果

解析:

在这个案例,我没有贴出多线程执行类,因为不管你怎么多线程调用(只创建一个对象还是每条线程创建一个对象,都会一样的结果,均可以获得准确的结果。)

这是为什么呢?同样,我们也要清晰理解,这时候synchronized加锁的是什么东西。

一篇文章让你懂得Java多线程编程

synchronized修饰static方法,它相当于锁住的是类。所以不管你创建多少对象,它锁住的是这个class,一样可以起到互斥的作用。

案例六--同一个类中既有synchronized修饰的static方法也有synchronized修饰非static方法:

一篇文章让你懂得Java多线程编程

一篇文章让你懂得Java多线程编程

每条线程两次调用count++

一篇文章让你懂得Java多线程编程

结果

解析:

单独调用inc()或者inc2()方法,都可以得到2000,但每条线程同时调用inc()和inc2(),却得到4000。

让我们回忆一下,案例4和案例5所描述的,synchronized修饰普通方法时,synchronized锁住的是this,也就是对象。而synchronized修饰static方法时,synchronized锁住的是class。所以同时调用这两个方法,并不能起到一个互斥的作用。

案例七--创建一个lock属性,用synchronized进行加锁

lock为普通属性:

一篇文章让你懂得Java多线程编程

我们采用案例4的多线程执行类:

如果只所有线程只创建一个对象,可以稳定得到2000。

如果每条线程都创建一个自己的对象,那么将得不到2000。

lock为静态属性:

一篇文章让你懂得Java多线程编程

不管是否共享一个对象还是创建多个对象,依然可以稳定得到2000。

解析:

synchronized修饰对象与修饰方法具有同样的道理。

结语:

希望通过以上一些简单的小例子,能够让你对Java的多线程有了一个初步的了解。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值