共享的问题
import lombok.extern.slf4j.Slf4j;
/**
* @Author blackcat
* @version: 1.0
* @description: 共享资源例子
* <p>
* 结果可能是正数、负数、零。
* <p>
* i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:
* getstatic i // 获取静态变量i的值
* iconst_1 // 准备常量1
* iadd // 自增
* putstatic i // 将修改后的值存入静态变量i
* <p>
* i-- 也类似
* getstatic i // 获取静态变量i的值
* iconst_1 // 准备常量1
* isub // 自减
* putstatic i // 将修改后的值存入静态变量i
* <p>
*/
@Slf4j
public class Counter {
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter++;
}
}, "add");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
counter++;
}
}, "sub");
t1.start();
t2.start();
t1.join();
t2.join();
log.info("result:{}", counter);
}
}
临界区
一个程序运行多个线程本身是没有问题
问题出现在多个线程访问共享资源
多个线程读共享资源其实也没有问题
在多个线程对共享资源读写操作发生指令交错,就会出现问题
一段代码块如果存在对共享资源的多线程读写操作,称这段代码块为临界区
竞态条件
当两个线程竞争同一个资源,如果对资源的访问顺序敏感,就称存在竞态条件。
计算的正确性取决于多线程的交替执行的时许时,就会发生竞态条件。
解决
为了避免临界区的竞态条件发生,有多种手段可以达到目的。 阻塞式的解决方案:synchronized,Lock 非阻塞式的解决方案:原子变量
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author blackcat
* @version: 1.0
* @description:避免临界区的竞态条件的方案
*/
@Slf4j
public class CounterSafe {
static int counter = 0;
static int lockCounter = 0;
static final Object lock = new Object();
static AtomicInteger atomicCounter = new AtomicInteger(0);
static final ReentrantLock reentrantLock = new ReentrantLock();
static volatile int volatileCounter = 0;
public static void main(String[] args) throws InterruptedException {
sync();
lock();
atomicCounter();
volatileCounter();
}
private static void volatileCounter() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
volatileCounter++;
}
}, "add");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
volatileCounter--;
}
}, "sub");
t1.start();
t2.start();
t1.join();
t2.join();
log.info("volatile result:{}", atomicCounter.get());
}
private static void atomicCounter() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
atomicCounter.incrementAndGet();
}
}, "add");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
atomicCounter.decrementAndGet();
}
}, "sub");
t1.start();
t2.start();
t1.join();
t2.join();
log.info("atomic result:{}", atomicCounter.get());
}
private static void lock() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
reentrantLock.lock();
try {
lockCounter++;
} finally {
reentrantLock.unlock();
}
}
}, "add");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
reentrantLock.lock();
try {
lockCounter--;
} finally {
reentrantLock.unlock();
}
}
}, "sub");
t1.start();
t2.start();
t1.join();
t2.join();
log.info("lock result:{}", lockCounter);
}
private static void sync() throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (lock) {
counter++;
}
}
}, "add");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (lock) {
counter--;
}
}
}, "sub");
t1.start();
t2.start();
t1.join();
t2.join();
log.info("sync result:{}", counter);
}
}
变量的线程安全分析
成员变量和静态变量是否线程安全
如果它们没有共享,则线程安全
如果它们被共享了,如果只有读操作,则线程安全
如果有读写操作,则这段代码是临界区,需要考虑线程安全.
局部变量是否线程安全
局部变量是线程安全的
但局部变量的引用对象则未必
如果该对象没有逃离方法的作用访问,它是线程安全的
如果该对象逃离方法的作用范围,需要考虑线程安全
import java.util.ArrayList;
import java.util.List;
/**
* @Author blackcat
* @version: 1.0
* @description:共享list对象
*/
public class ThreadUnsafe {
static List<String> list = new ArrayList<>();
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadUnsafe unsafe = new ThreadUnsafe();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
unsafe.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
}
public void method1(int loopNumber) {
for (int i = 0; i < loopNumber; i++) {
// { 临界区, 会产生竞态条件
method2();
method3();
// } 临界区
}
}
private void method2() {
list.add("1");
}
private void method3() {
list.remove(0);
}
}
//Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, //Size: 0
import java.util.ArrayList;
import java.util.List;
/**
* @Author blackcat
* @version: 1.0
* @description: 将list改成局部变量
*/
public class ThreadSafe {
public final void method1(int loopNumber) {
List<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {
method2(list);
method3(list);
}
}
private void method2(List<String> list) {
list.add("1");
}
private void method3(List<String> list) {
list.remove(0);
}
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
ThreadSafe safe = new ThreadSafe();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
safe.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
}
}
import java.util.ArrayList;
import java.util.List;
/**
* @Author blackcat
* @version: 1.0
* @description: 把method2,3 从private 改成public
*/
public class PublicThreadSafe {
public final void method1(int loopNumber) {
List<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {
method2(list);
method3(list);
}
}
public void method2(List<String> list) {
list.add("1");
}
public void method3(List<String> list) {
list.remove(0);
}
}
import java.util.List;
/**
* @Author blackcat
* @version: 1.0
* @description: 重写method2 导致list溢出
*/
public class PublicThreadUnsafe extends PublicThreadSafe {
@Override
public void method2(List<String> list) {
new Thread(() -> {
list.add("1");
}).start();
}
static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 100;
public static void main(String[] args) {
PublicThreadSafe safe = new PublicThreadUnsafe();
for (int i = 0; i < THREAD_NUMBER; i++) {
new Thread(() -> {
safe.method1(LOOP_NUMBER);
}, "Thread" + i).start();
}
System.out.println("111");
}
}
常见线程安全的类
String
Integer
StringBuffer
Random
Vector
Hashtable
这里的线程安全始值调用实例单个方法是线程安全的,每个方法是原子,多个方法组合不是原子
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
/**
* @Author blackcat
* @version: 1.0
* @description:单个方法是原子,组合不是原子的
*/
@Slf4j
public class VectorThread {
static Vector vector = new Vector();
public static void main(String[] args) throws InterruptedException {
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(() -> {
// if(vector.isEmpty()) {
vector.add("key");
// }
}, ""+i);
list.add(thread);
}
list.stream().forEach(t -> t.start());
list.stream().forEach(t -> {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
log.info("size:{}", vector.size());
}
}
不可变类线程安全性
内部属性不能改变,只存在读取。
public class Immutable {
private int value = 0;
public Immutable(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
如果你需要对Immutable类的实例进行操作,如添加一个类似于加法的操作,我们不能对这个实例直接进行操作,只能创建一个新的实例来实现,下面是一个对value变量进行加法操作的示例:
public class Immutable{
private int value = 0;
public Immutable(int value){
this.value = value;
}
public int getValue(){
return this.value;
}
public Immutable add(int valueToAdd){
return new Immutable(this.value + valueToAdd);
}
}
引用不是线程安全的
即使一个对象是线程安全的不可变对象,指向这个对象的引用也可能不是线程安全的。
public void Calculator{
private Immutable currentValue = null;
public Immutable getValue(){
return currentValue;
}
public void setValue(Immutable newValue){
this.currentValue = newValue;
}
public void add(int newValue){
this.currentValue = this.currentValue.add(newValue);
}
}
Calculator类持有一个指向Immutable实例的引用。注意,通过setValue()方法和add()方法可能会改变这个引用,因此,即使Calculator类内部使用了一个不可变对象,但Calculator类本身还是可变的,多个线程访问Calculator实例时仍可通过setValue()和add()方法改变它的状态,因此Calculator类不是线程安全的。