Java并发理论基础

并发的优点

更好的利用单个CPU

如果cpu是单核,多线程执行也会提供性能,比如:GUI界面和网咯下载文件,必须使用多线程,否则就会卡死界面。

更好的利用多核CPU

现在的cpu都是多核的。 如果一个程序是单线程的,那么它只能使用一个核心,再多的CPU核心也不能提高机器的性能。 如果一个程序是多线程的,它可以使用CPU的多个核来执行操作。 CPU的内核越多,运行速度就越快。

异步事件的简化处理

当每个连接都分配有自己的线程并允许使用同步 I/O 时,接受来自多个远程客户端的套接字连接的服务器应用程序可能更容易开发。

并发的缺点

频繁的上下文切换

上下文切换原理请见这篇文章:

罗政:CPU上下文切换原理

线程安全性

线程安全问题的主要来源是多个线程同时访问操作 共享变量/资源。 举个例子

public class BasicObservableClass {
	public interface Observer {
		void onObservableChanged();
	}

	private Set<Observer> mObservers;

	public void registerObserver(Observer observer) {
		if (mObservers == null) {
			mObservers = new HashSet<>(1);
		}
		mObservers.add(observer);
	}

	public void unregisterObserver(Observer observer) {
		if (mObservers != null && observer != null) {
			mObservers.remove(observer);
		}
	}

	private void notifyObservers() {
		if (mObservers == null) {
			return;
		}
		for (Observer observer : mObservers) {
			observer.onObservableChanged();
		}
	}
}

当多线程同时调用registerObserver 方法注册观察者时,

  • 线程 A 调用 registerObserver(Observer)
  • 线程 A 执行 mObservers == null 检查并继续实例化新集合
  • 在线程 A 有机会创建新集合之前,操作系统暂停它并恢复线程 B 的执行
  • 线程 B 执行上面的步骤 1 和 2
  • 由于线程 A 还没有实例化集合,线程 B 实例化一个新集合并将对它的引用存储在 mObservers
  • 线程 B 向新创建的集合添加一个观察者
  • 在某个时候操作系统恢复线程 A 的执行(在新集合的实例化之前被挂起)
  • 线程 A 实例化一个新集合并覆盖中的引用 mObservers
  • 线程 A 在新创建的集合中添加一个观察者

尽管两个调用都 registerObserver(Observer) 成功完成,但最终结果是 notifyObservers() 调用时只会通知一个观察者。


线程安全

编写线程安全代码原则

  • 不要共享变量(例如无状态 servlet)
  • 使变量 不可变
  • 使用 (在 Java 中同步)


Java 线程安全的要点

1) 不可变对象默认是线程安全的,因为它们的状态一旦创建就无法修改。由于 String 在 Java 中是不可变的,因此它本质上是线程安全的。

2) Java 中的只读或 最终变量在 Java 中也是线程安全的。

3)锁定是Java中实现线程安全的一种方式。

4)如果没有正确同步, 静态变量会 成为线程安全问题的主要原因。

5)Java线程安全类示例:Vector、Hashtable、ConcurrentHashMap、String等。

6) Java 中的原子操作是线程安全的,就像从内存中读取 32 位 int 一样,因为它是一个原子操作,它不能与其他线程交错。

7) 局部变量也是线程安全的,因为每个线程都有自己的副本,使用局部变量是在 Java 中编写线程安全代码的好方法。

8) 为了避免线程安全问题,尽量减少多线程间的对象共享。

9) Java中的Volatile关键字 也可以用来指示线程不要缓存变量和从主内存读取,也可以从线程的角度指示JVM不要重新排序或优化代码。


使用锁的原则

使用锁也会导致性能问题 不正确/过度的锁使用会导致很多问题。使用锁应该遵循下面原则:

  • 缩小锁 范围 ,例如:使用同步代码块而不是锁整个方法
  • 避免锁定 冗长的 操作 (等待用户响应、竞争资源、远程 Web 服务)
  • 不要 嵌套
  • 分裂 /减少锁粒度:为每个对象使用不同的锁,而不是在长代码中使用一个锁。
  • 使用锁定的 替代方案 :限制/ 不共享 对象(例如,使用局部变量、深度复制为每个线程创建本地副本、使用线程局部)、 不可变 对象(最终)、 易失性 对象。
  • 提供 死锁解决方案

一些概念

并行与并发的区别

并发是指同时执行通常不相关的各种任务,并发程序并不需要多核处理器。比如:windows系统边听歌边玩游戏,在单核cu系统中是靠cpu来回切换执行。

并行是依靠多核的优势,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。

简单理解: 一个人(单核)同时做多件事情就是并发,多个人(多核)同时做多件事情就是并行。

临界区

临界区用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。

阻塞和非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响,比如一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起,这种情况就是阻塞,而非阻塞就恰好相反,它强调没有一个线程可以阻塞其他线程,所有的线程都会尝试地往前运行。

同步VS异步

同步和异步通常用来形容一次方法调用。同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。而异步调用,指的是,调用者不用管被调用方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。

支付宝打赏 微信打赏

如果文章对您有帮助,您可以鼓励一下作者