アウトライン
並行プログラミング、同期間内に、すなわち複数のスレッド、「同時に」実行します。
マルチプロセッサシステム、すでに人気のある今日では、マルチスレッドのようなその利点、プレイアウトすることができます唯一のシングルスレッドは、それが7つのプロセッサがアイドル状態である必要があります場合は、8コアCPUサーバを、サーバはわずか8を再生することができます(他のリソースの消費を無視して)能力の一つ。
同時に、マルチスレッドを使用することは、我々はビジネスモデルの複雑さを軽減するために、処理ロジックの複雑なタスクを簡素化することができます。
そのため、並行プログラミングは、システムのスループットを向上させる難しさをコーディング削減、サーバリソースの利用率を向上させる上で重要な役割を果たしています。
これらは、並行プログラミングの利点であるが、それはまた、非常に重要な質問紹介:スレッドの安全性を。
スレッドの安全性の問題とは何ですか
場合は、同時実行スレッドは、CPUスケジューリングやその他の理由から、交互に実行するスレッド。図の例を以下に示します。
public class SelfIncremental {
private static int count;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i< 10000; i++) {
count++;
System.out.println(count);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i< 10000; i++) {
count++;
System.out.println(count);
}
});
thread1.start();
thread2.start();
}
}
スレッド1とスレッド2を交互に実行することができるので、それは常に20,000等しくないカウントの値を実装した後、表示されます20,000未満です。
図示のように:
- 時間t1:数= 100を読んでスレッド1
- 時間t2:スレッド2読ん数= 100
- T3時間:カウントするスレッド1 + 1
- T4時間:カウントするスレッド2 + 1
- T5時間:101を数える書きますスレッド1
- T5時間:101を数える書きますスレッド2
カウントが++アトミック操作ではありませんので、それは実際には3つのステップを実行します。
- 1、countの値を取得します
- 2、数プラス1
- 3、計算結果が書かれている数
したがって、2つのスレッドの同時実行が同時に読んだときに、期待を満たしていない結果につながると同じ値、1を加えた同じ値を読むことができる、このような状況はスレッドセーフではありません。
スレッドの安全性:複数のスレッドがどんなにいわゆるスケジューリングランタイム環境の種類使用するか、またはこれらのスレッドが実行を交互になり、追加の同期動作を使用する必要はありませんどのようにクラスを、アクセスすると、クラスが適切な挙動を示すことができます、その後、このクラスはスレッドセーフであると呼びます。
トリガ
スレッドの安全性の問題の原因は、主に、共有メモリは、スレッド内の古いデータを読み、その結果、不確実性を読み取って修正する時間があるため、複数のスレッドによって読み取られ、そして共有に基づいてダーティデータ後処理をライトバックすることができるされています誤った結果が得られたメモリ、。
レース条件
在并发编程中,因为不恰当的执行时序而出现不正确的结果的情况被称为竟态条件。
常见的静态条件类型:
- 先检查后执行:首先观察到某个条件为真。根据这个观察结果采用相应的动作,但实际上在你观察到这个结果和采用相应动作之间,观察的结果可能发生改变变得无效,导致后续的所有操作都变得不可预期。(比如延迟初始化)
- 读取-修改-写入:基于对象之前的状态来定义对象状态的转换。但在读取到结果和修改之间,对象可能已被更改。这样就会基于错误的数据修改得出错误的结果并被写入。(比如递增操作)
发布与逸出
发布:使对象能够在当前作用域之外的代码中使用。如将该对象的引用保存到其它代码可以访问的地方、在一个非私有的方法中返回该引用,将引用传递到其它类的方法中。如:
public static Student student;
public void init() {
student = new Student;
}
这里 student对象就被发布了。
逸出:当不该被发布的对象被发布了,就称为逸出。如
private String name = "xxx";
public String getString() {
return name;
}
这里name原为private类型但是却被getString方法发布了,就可以被视为逸出。
如何避免
线程封闭
线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只有这个对象能修改。
线程封闭即不共享数据,仅在单线程内访问数据,这是实现线程安全最简单的方式之一。
实现线程封闭可以通过:
- Ad-hoc线程封闭:即维护线程封闭性的职责完全由成熟实现承担。
- 栈封闭:通过局部变量才能访问对象,该局部变量被保存在执行线程的栈中,其他线程无法访问。
- ThreadLocal类:将共享的全局变量转换为ThreadLocal对象,当线程终止后,这些值会被垃圾回收。
只读共享
在没有额外同步的情况下,共享的对象可以由多个线程并发访问,但是任何线程都不能修改。共享的对象包括不可变对象和事实不可变对象。
不可变对象:如果某个对象在被创建后就不能修改,那么这个对象就是不可变对象。不可变对象一定是线程安全的。
线程安全共享
线程安全的对象在其内部实现同步,因此多线程可以通过对象的公有接口来进行访问而不需要自己做同步。
保护对象
被保护的对象只能通过持有特定的锁来访问。即通过加锁机制,确保对象的可见性及原子性。
- 内置锁:即通过synchronized关键字同步代码块。线程在进入同步代码块之前会自动获得锁,并在退出同步代码块时自动释放锁。内置锁是一种互斥锁。
- 重入锁:当线程视图获取一个已经持有的锁时,就会给锁的计数器加一,释放锁时计数器会减一。当计数器为0时,释放锁
- volatile:访问volatile变量时,不会加锁,也不会阻塞线程执行。他只确保变量的可见性,是一种比synchronized更轻量级的同步机制。
总结
本文主要是记录了学习《Java并发编程实站》前几章中,并发编程相关的一些概念。简单介绍了线程安全、锁机制等,接下来 我们会深入JUC源码,来深刻学习并发编程相关知识。
备注:本文主要源自对《Java并发编程实战》的学习笔记。