共享受限资源
关于线程的基本特性差不多介绍了,接下来是一些关于线程安全的问题了。
不正确的访问资源
/**
* 定义一个抽象的生成器 用于生成 int 整数
*
*
*/
public abstract class IntGenerator {
private volatile boolean canceled=false;
public abstract int next();
public void cancel(){
this.canceled=true;
}
public boolean isCanceled(){
return this.canceled;
}
}
定义一个检查器,去检查生成器生成的内容
public class EvenChecker implements Runnable {
private IntGenerator intGenerator;
public EvenChecker(IntGenerator intGenerator) {
super();
this.intGenerator = intGenerator;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (!intGenerator.isCanceled()) {
int val = intGenerator.next();
System.out.println(val);
if (val % 2 != 0) {
System.out.println(val + "should not appear,current Thread:"+Thread.currentThread().getName());
intGenerator.cancel();
}
}
}
public static void main(String[] args) {
ExecutorService exec=Executors.newFixedThreadPool(10);
Runnable target=new EvenChecker(new IntGenerator() {
private int val;
@Override
public int next() {
val++;
Thread.yield();
val++;
return val;
}
});
for(int a=0;a<10;a++){//开启10个线程
exec.execute(target );
}
exec.shutdown();//任务执行完之后尽快退出
}
}
输出#
关于上面的输出:解释一下 由于next没有保证原子性导致了线程不安全 同时 canceled 是使用volatile修饰的,它修改之后对所有的线程都是可见的 按道理说 程序只可能有一次输出 这里第一个线程输出之后就不会有其他输出了,但是因为这里没有使用同步 所以可能在线程一输出之后还没来得及改 cancel的标志 又切换其他的线程了,这时候线程看到的cancel标志任然是false,所以线程继续执行进了while 循环就有了第二次第三次。。。输出
上面的实例展示了线程的的一个基本的问题,你永远不知道一个线程什么时候执行、什么时候切换时间片 当你拿着叉子准备吃叉起最后一块食物时候,这块食物消失了,因为你的线程被挂起,其他的线程吃掉了它。这正是并发编程要解决的问题。
解决冲突的方法就是 当一个资源被一个任务所使用时候,为这个资源上锁。
基本上所有的并发模式在解决线程冲突问题的时候,都采用 序列化访问共享资源的方案。这意味着在给定的时刻只允许一个任务访问共享资源。通常在代码前面 加一条上锁语句来实现的,因为锁有一种相互排斥的效果,所以这种机制又被称为 互斥量 (mutex)
java以提供关键字 synchronized 的形式,为防止资源冲突提供支持。当任务要执行synchronized 关键字保护的代码的时候,它将检查锁是否可用。然后获取锁,执行代码,释放锁。
共享资源一般以对象的形式存在的内存片段,但也可以是文件、输入、输出端口,或者是打印机。要想控制对共享资源的访问。得先把它包装进一个对象。然后把所有对这个资源的访问的方法标记为synchronized.
如果一个线程正处于对一个对象的一个标记synchronized的方法访问之中,在方法返回之前。那么其他任何对这个对象的synchronized方法的访问的线程都将阻塞。
同步的规则:
如果你正在写一个变量,它可能接下来将别另一个线程读取,或者正在读取一个上一次以及被另一个线程写过的变量。那么你必须使用同步。并且,读写线程必须使用相同的监视器锁进行同步。
修改以前的线程不安全的代码:
public class EvenChecker implements Runnable {
private IntGenerator intGenerator;
public EvenChecker(IntGenerator intGenerator) {
super();
this.intGenerator = intGenerator;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (!intGenerator.isCanceled()) {
int val = intGenerator.next();
System.out.println(val);
if (val % 2 != 0) {
System.out.println(val + "should not appear,current Thread:"+Thread.currentThread().getName());
intGenerator.cancel();
}
}
}
public static void main(String[] args) {
ExecutorService exec=Executors.newFixedThreadPool(10);
Runnable target=new EvenChecker(new IntGenerator() {
private int val;
@Override
public synchronized int next() {
val++;
Thread.yield();
val++;
return val;
}
});
for(int a=0;a<10;a++){//开启10个线程
exec.execute(target );
}
exec.shutdown();//任务执行完之后尽快退出
}
}
此时这个程序就不会产生奇数了。
也可以使用 Java SE5 中juc类库提供的Lock对象 代码如下:
public class EvenChecker implements Runnable {
private IntGenerator intGenerator;
public EvenChecker(IntGenerator intGenerator) {
super();
this.intGenerator = intGenerator;
}
@Override
public void run() {
// TODO Auto-generated method stub
while (!intGenerator.isCanceled()) {
int val = intGenerator.next();
System.out.println(val);
if (val % 2 != 0) {
System.out.println(val + "should not appear,current Thread:" + Thread.currentThread().getName());
intGenerator.cancel();
}
}
}
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(10);
Runnable target = new EvenChecker(new IntGenerator() {
private Lock lock = new ReentrantLock();
private int val;
@Override
public int next() {
lock.lock();
try {
val++;
Thread.yield();
val++;
return val;
} finally {
lock.unlock();
}
}
});
for (int a = 0; a < 10; a++) {// 开启10个线程
exec.execute(target);
}
exec.shutdown();// 任务执行完之后尽快退出
}
}
尽管这里的代码try-catch比用synchronized要多得多,但是也代表了Lock关键字的优点之一,如果使用synchronized 时候,某些事物失败了,那么会抛出一个异常,但是你没有任何机会去做一些清理工作,以维护系统处于良好的状态。有了显示的Lock对象,你就可以在finally中将系统维护在正确的状态中了。
大致上使用 synchronized 代码量更少,并且出现错误的记录更低、只有在尝试解决一些特殊的问题的时候才使用 Lock.比如:尝试获取锁,最终获取失败、尝试获取锁一段时间,然后放弃它。
Lock的其他特性:
public class AttemptLocking {
private Lock lock=new ReentrantLock();
private void untime(){
boolean flag=lock.tryLock();
try{
System.out.println("try lock:"+flag);
}finally {
if(flag){
lock.unlock();
}
}
}
private void time(){
boolean flag=false;
try {
flag=lock.tryLock(2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try{
System.out.println("try lock(2,TimeUnit.SECONDS):"+flag);
}finally {
if(flag){
lock.unlock();
}
}
}
public static void main(String[] args) {
AttemptLocking al=new AttemptLocking();
al.time();
al.untime();
new Thread(){
{
setDaemon(true);
}
@Override
public void run() {
al.lock.lock();
System.out.println("deamon lock!");
}
}.start();
Thread.yield();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
al.untime();
al.time();
}
}
使用Lock的那些属性来获取 锁,和获取锁失败