Java并发编程基础(二)

前言

熬过了考试周,终于可以继续我的博客分享了。大笑,哈哈,装个X。
本篇博客主要记录的是自己使用线程间通信的过程。

线程间的通信

并发常常伴随着多线程的运行,然后这就会涉及到多个线程间的配合工作,即线程间的通信。

Volatile和Synchronized

—Volatile—
我们都知道,Java中是支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程是可以拥有这个变量的拷贝(其中拷贝和引用需要注意区分),尽管对象及其成员变量是分配的内存是在共享内存中的(这里需要查看Java的内存分配),但每个线程还是可以拷贝一份的。这种设计加快了程序的执行,同时也会带来一定的问题。

例如,一个对象A它被分配在共享内存中,线程t1对他进行了内存的拷贝,并对A的成员变量进行了一定的修改,还没来得及更新到共享内存中,此时线程t2也需要访问A,实际上他是需要访问被t1修改后的对象A,而此时线程t1还没来的及更新到共享内存中,这时便会带来一定的问题。

Volatile便是出来解决上述问题。Volatile用来修饰字段(成员变量),程序中对该修饰的变量的访问都是从共享内存中获取,并且对其进行的改变必须要同步刷新回共享内存,以保证所有线程对修饰的变量的访问的可见性(感知被volatile修饰的变量的变化)。但这也会降低程序的执行效率。此时的并发级别是无障碍(具体查看初始Java并发

—Synchronized—
Java中对临界区的访问,常常可使用Synchronized,Synchronized用于修饰方法或者代码块,它用于确保同一个时刻,只能有一个线程处于方法或者修饰的代码块中,从而确保线程对变量的访问是可见和排他的。此时的并发级别是阻塞(具体查看初始Java并发

等待/通知机制

通知机制可以简单的理解为一个线程修改了一个变量的值,让另外一个线程能感知该线程对变量进行了修改,并作出相应的操作。
如何实现通知机制,需要使用到Java API 中的wait()notify()函数,并结合关键字Synchronized来实现通知机制。
示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1.线程A调用wait方法
synchronized(obj) {
while(!condition) {
obj.wait();
}
obj.doSomething();
}
//2.线程B调用notify方法
synchronized(obj) {
condition = true;
obj.notify();
}
//其中伪代码中的obj扮演的是内置锁的角色。
//说明1:java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
//说明2:java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去

理解:
A表示线程A,B表示线程B

1.调用obj的wait(), notify()方法前,必须获得obj锁,也就是该方法的调用必须写在synchronized(obj) {…} 代码段内。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
2.当线程B调用obj.notify/notifyAll的时候,线程B正持有obj锁,因此, A虽被唤醒,但是仍无法获得obj锁。直到B退出synchronized块,释放obj锁后,A才有机会获得锁继续执行。
3. 当线程A调用obj.wait()后,线程A进入阻塞状态,并释放obj的锁。否则线程B无法获得obj锁,也就无法在synchronized(obj) {…} 代码段内唤醒A。
4.当线程B调用obj.notify方法时,便会通知在obj对象上等待的线程A,使其从obj.wait()方法返回后,线程A 需要再次获得obj锁 ,才能继续执行。
5.如果有线程A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。
6.obj.notifyAll()则能全部唤醒线程A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如线程A1获得对象锁,其余的需要等待A1释放obj锁之后才能继续执行。

其他常见的通信方式

JDK API中的join()函数
thread.Join意思是把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestJoin implements Runnable{
public static void main(String[] sure) throws InterruptedException {
Thread t = new Thread(new TestJoin());
long start = System.currentTimeMillis();
t.start();//t线程
t.join();//t线程join到主线程main中,所以需要依次顺序执行两个线程
System.out.println(System.currentTimeMillis()-start);//打印出时间间隔
System.out.println("Main finished");//打印主线程结束
}
@Override
public void run() {
//dosomething ...
System.out.println("TestJoin coming");
}
}

ThreadLocal的使用
ThreadLocal,是线程变量,是一个map形式的存储结构,其中key为ThreadLocal对象,Value为任意存储的变量。它是被附带在线程上的,一个线程可以根据ThreadLocal对象查询到绑定到这个线程上的一个值。参考ThreadLocal
ThreadLocal主要用于线程间特殊的数据的隔离,如何用好ThreadLocal需要明确以下几个问题,网络上也有很多ThreadLocal介绍的,建议还是需要自己去阅读源码。

1
2
3
4
1.ThreadLocal的在Java多线程编程中扮演一个什么角色,起到一个什么作用。
2.ThreadLocal存储不同线程内的变量是如何做到隔离的。
3.THreadLocal如何高效的存储数据,并对存储的数据进行操作的?
4.ThreadLocal内存泄漏的问题,ThreadLocal对线程变量存储涉及到很多变量,内存回收的时候,是如何有效的做到避免内存泄漏。

后记

续前2篇
初始Java并发
Java并发编程基础(一)

-------------本文结束感谢您的阅读-------------

本文标题:Java并发编程基础(二)

文章作者:ComeOnJian

发布时间:2017年12月31日 - 09:12

最后更新:2018年04月12日 - 18:04

原始链接:https://jianwenjun.xyz/2017/12/31/Java并发编程基础(二)/

许可协议: 转载请保留原文链接及作者。

显示 Gitment 评论