临界资源
操作系统课程中我们学到过可能有多个任务同时进入的资源叫临界资源,在实际中这些资源实体一般就会由类及其实例来表示,
其状态由变量来决定。
PS:这里开始牵扯到写复杂多线程的套路,多线程概述中已经提出了任务实体和线程实体,在这里开始引入资源实体,要想共享资源,要把实体传给线程持有。
线程写可能改变共享资源
在只读不写的情况下,多个线程同时只读一个资源只需要简单的创建新线程即可,由于多个线程的并发执行时,字节码在CPU上的执行顺序在未同步的情况下是不能预先知道,而且某些操作并不具备原子性(比如变量自增i++),而当持有该资源的线程所执行的任务可能改变资源的状态时,可能发生脏读的问题,比如一个线程做变量自增,另一个线程读变量,由于自增操作并不是原子性的,在进入了自增操作但是还没结束时CPU就调度了读线程的操作,则会读出错误的数值。
解决方法:使用synchronized关键字修饰方法或者修饰代码块
synchronized包含的区域叫做临界区,在临界区内,可以保证当前只有一个对象能够执行该方法或代码块,底层原理是对象自身可以作为一把锁,只有拿到锁的线程才能进入临界区,即临界区内的操作是原子性的。
例子
在以下的例子中,有一个偶数生成器,单线程时,可以用来生成偶数,但是由于缺少线程同步机制,在多线程都会调用原来的没有进行线程同步的next()方法情况下,两次自增在执行的时候可能被其他调用该方法的线程打断,最终会出现技术。
public abstract class IntGenerator {
private volatile boolean canceled = false; // 原子性的,读写在发生时没有中断的可能
public abstract int next();
// Allow this to be canceled:
public void cancel() {
canceled = true;
}
public boolean isCanceled() {
return canceled;
}
}
// 多个线程检查偶数是一个任务
public class EvenChecker implements Runnable{
private IntGenerator generator;
private final int id;
public EvenChecker(IntGenerator generator, int id) {
super();
this.generator = generator;
this.id = id;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (!generator.isCanceled()) {
int val = generator.next();
if (val % 2 != 0) {
System.out.println(val + " not even!!");
generator.cancel();
}
}
}
// Test any Type of IntGenerator
public static void test(IntGenerator gp, int count) {
System.out.println("Press Control-C to exit");
ExecutorService exec = Executors.newCachedThreadPool();
for (int i=0; i<count; i++) {
exec.execute(new EvenChecker(gp, i));
}
exec.shutdown();
}
// Default value for count:
public static void test(IntGenerator gp) {
test(gp, 10);
}
}
// 临界资源
public class EvenGenerator extends IntGenerator{
private int currentEvenValue = 0; // 在这里用volatile也没用,保证得了自增是原子操作,但是两次自增之间不是原子操作的
public int next() {
// 最理想情况下是产出一个奇数
currentEvenValue++; // danger point
currentEvenValue++;
return currentEvenValue;
}
public static void main(String[] args) {
EvenChecker.test(new EvenGenerator());
}
}
我们使用synchronized来修饰next方法,可以保证该方法执行的原子性来达到线程安全
public class SynchronizedEvenGenerator extends IntGenerator{
private int currentEvenValue = 0;
// synchronized处理的是线程争用的问题,用来标记当不同线程访问到这里时需要竞争来获得同步锁
public synchronized int next() {
// 最理想情况下是产出一个奇数
currentEvenValue++; // danger point
currentEvenValue++;
return currentEvenValue;
}
public static void main(String[] args) {
EvenChecker.test(new SynchronizedEvenGenerator());
}
}