• 多线程学习(七)


    共享受限资源

    关于线程的基本特性差不多介绍了,接下来是一些关于线程安全的问题了。

    不正确的访问资源

    /**
     * 定义一个抽象的生成器 用于生成 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的那些属性来获取 锁,和获取锁失败

  • 相关阅读:
    json对象与字符串的相互转换,数组和字符串的转换
    angularjs ng-csv 异步下载
    angular2 localStorage的使用
    ng-csv 异步数据下载
    微信小程序AES加密解密
    微信小程序Md5加密(utf-8汉字无影响)
    angular-file-upload封装为指令+图片尺寸限制
    angular+require前端项目架构搭建
    Inspinia_admin-V2.3原版(英文)
    hplus--H+ V2.3 (中文版)
  • 原文地址:https://www.cnblogs.com/joeCqupt/p/6820477.html
Copyright © 2020-2023  润新知