Java多线程实践—Java自己携带的同步类

接着昨天的例子,我们利用显示锁Lock实现了生产/消费者例子,今天介绍一些线程安全的类,首先用一个BlockingQueue来实现缓冲区的读/写整数。

BlockingQueue是一个接口,其有三个实现类(如果你愿意也可以实现一个自己的阻塞队列),ArrayBlockingQueue、LinkedListQueue、PriorityBlockingQueue。


我们先用LinkedListQueue实现缓冲区的例子,接着试着自己写一个阻塞队列(顺便阅读一下源码)。下面是代码:

package zy.thread.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class ConProWithBlockingQueue {

	private static LinkedBlockingQueue<Integer> queue = 
			new LinkedBlockingQueue<>(1);
	public static void main(String[] args) {
		
		ExecutorService executor = Executors.newFixedThreadPool(2);
		executor.execute(new ProducerTask());
		executor.execute(new ConsumerTask());
		executor.shutdown();
	}
	
	private static class ConsumerTask implements Runnable {
		public void run() {
			try {
				while (true) {
					System.out.println("\t\t\t\tConsumer reads " + queue.take());
					TimeUnit.SECONDS.sleep(1);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}		
	}
	private static class ProducerTask implements Runnable {
		public void run() {
			try {
				int element = 1;
				while (true) {
					queue.put(element++);
					System.out.println("Producer writes " + element);
					TimeUnit.SECONDS.sleep(3);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}		
	}
}
注意:使用 put()、make()函数很重要!

下面自己实现了一个简单的阻塞队列:

实现:用数组实现,Integer类型(有心的读者可以自己实现泛型),synchronized形式实现同步(读者也可以自己实现显示加锁Lock,可以参考前一篇)。在数组尾部添加数据表示插入队列,获取并删除数组第一个数据表示获取队列数据(需要一次循环更新数值)。

使用:只需将上述代码的第10行做出修改即可,即用自己的队列替代LinkedBlockingQueue。

变量:size变量表示当前队列里有多少个数,capacity表示队列容量。每次操作之前都要对size、capacity做出判断!

package zy.thread.demo;

public class MyBlockingQueue {
	private int[] a;
	private int capacity;
	private int size = 0;
	
	public MyBlockingQueue() {
		a = new int[10];
		capacity = 10;
	}	
	public MyBlockingQueue(int capacity) {
		a = new int[capacity];
		this.capacity = capacity;
	}	
	public synchronized void put (int element) {
		try {
			if (size == capacity)
				wait();
			a[size++] = element;
			notify();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}	
	@SuppressWarnings("finally")
	public synchronized int take () {
		int value = Integer.MIN_VALUE;
		try {
			if (size == 0)
				wait();
			value = a[0];
			for (int i = 0; i < size - 1; i++)
				a[i] = a[i + 1];
			--size;
			notify();
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			return value;
		}		
	}	
}
代码已经测试过,本人觉得没什么问题,运行结果也是可以推敲的!如果有大神指出错误,那是非常感谢的!

之前同步篇介绍了2中同步的方法,今天在补充一种“奇招”,semaphore信号量(操作系统里头有这个概念)。它主要用于限制访问共享资源的显成熟,在访问资源之前,需要获取许可,在访问之后,释放许可!下面还是用那个存取款示例来演示:

package multithreading;

import java.util.concurrent.*;

public class AccountWithSync {
	
	private static Account account = new Account();
	
	public static void main(String[] args) {
		ExecutorService executorService = Executors.newCachedThreadPool();
		
		for (int i = 0; i < 50; i++) {
			executorService.execute(new AddPennyTask());
		}
		executorService.shutdown();
		while (!executorService.isTerminated()) {
		}		
		System.out.println("What is balance?" + account.getBalance());
	}
	
	private static class AddPennyTask implements Runnable {
		public void run() {
			account.deposit(1);
		}
	}	
	private static class Account {
		
		private static Semaphore semaphore = new Semaphore(1);
		private int balance = 0;
		public int getBalance() {
			return balance;
		}	
		public void deposit(int amount) {			
			try {
				semaphore.acquire();
				int newBalance = balance + amount;
				Thread.sleep(5);
				balance = newBalance;
			} catch (InterruptedException e) {
			} finally {
				semaphore.release();
			}
		}
	}
}

PipedReader、PipedWriter(管道)

PipedReader:允许线程向管道写数据;

PipedWriter:允许不同线程从同一管道取数据;

PipedReader的建立必须在构造器中与一个PipedWriter相关联

同步集合

Java集合框架中的类不是线程安全的。Collections类提供了6个静态方法来讲集合转成同步版本:

Collections.synchronizedCollection(Collection<T>);
Collections.synchronizedList(List<T>);
Collections.synchronizedMap(Map<K, V>);
Collections.synchronizedSet(Set<T>);
Collections.synchronizedSortedMap(SortedMap<K, V>);
Collections.synchronizedSortedSet(SortedSet<T>);
但是当使用迭代器时,必须注意, 迭代器具有快速失败的特性,如果当前的集合被另一个线程修改,而集合在使用迭代器,那么迭代器会通过抛出异常来结束。如果要在集合上遍历,则必须同步,示例如下:

Set<Integer> hashSet = Collections.synchronizedSet(new HashSet<Integer>());
<pre name="code" class="java">synchronized (hashSet) {
	Iterator<Integer> iterator = hashSet.iterator();
	while (iterator.hasNext()) {
	System.out.print(iterator.next());
}
		}

 

CountDownLatch

用途:用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作完成,来看看它是如何工作的:

1. 首先你得在你的程序中创建一个CountDownLatch对象,假定有两个工作组(领导组10人和员工组100人)要去吃饭,现在为了展示领导T恤下属,所有员工吃完后饭后才允许领导吃(好像不现实,就是例子而已)。假定每个工作组内部人员的吃饭这个动作相互不影响(因此不需要进行同步控制)。当某一个领导想要去吃饭的时候,发现有员工(计数大于0)还没吃好饭,那么他就必须等待。每当一个员工吃完饭,计数减1。下面是示例:

package multithreading;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class CountDownLatchDemo {

	private static final int SIZE = 100;
	public static void main(String[] args) {
		ExecutorService executor = Executors.newCachedThreadPool();
		CountDownLatch latch = new CountDownLatch(SIZE);
		for (int i = 0; i < 10; i++)
			executor.execute(new WaitingTask(latch));
		for (int i = 0; i < SIZE; ++i)
			executor.execute(new TaskPartion(latch));
		System.out.println("Launched all tasks");
		executor.shutdown();
	}
}

class WaitingTask implements Runnable {
	private static int counter = 0;
	private final int id = counter++;
	private CountDownLatch latch;
	public WaitingTask(CountDownLatch latch) {
		this.latch = latch;
	}
	public void run() {
		try {
			latch.await();
			System.out.println("Latch barrier passed for " + this);
		} catch (InterruptedException e) {
			System.out.println(this + " interrupted");
		}
	}
	public String toString() {
		return String.format("WaitingTask %1$-3d", id);
	}
}

class TaskPartion implements Runnable {
	private static int counter = 0;
	private final int id = counter++;
	private static Random rand = new Random(47);
	private final CountDownLatch latch;
	public TaskPartion(CountDownLatch latch) {
		this.latch = latch;
	}
	public void run() {
		try {
			doWork();
			latch.countDown();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
	}
	public void doWork () throws InterruptedException {
		TimeUnit.MILLISECONDS.sleep(rand.nextInt(2000));
		System.out.println(this + " completed");
	}
	public String toString () {
		return String.format("%1$-3d", id);
	}
}

CyclicBarrier(书本翻译:循环栅栏)可以参考资料:

Java并发编程:CountDownLatch、CyclicBarrier和Semaphore

主要作用是:在某一个barrier(栅栏)等待一组任务,所有任务执行好了之后,在一起开始执行接下去的任务,循环进行!CountDownLatch则只能执行一次!

免锁容器:

免锁容器的策略是:对容器的修改可以与读取操作同时进行,只要读取者只能看到完成修改后的结果即可!

修改是在容器数据结构的某一个部分的副本中进行的,这个副本在修改过程中时不可视的。

只有修改完成后,被修改的结构才会主动与数据结构进行交换,之后读取者就可以看到这个修改了!

主要的类有:CopyOnWriteArrayList CopyOnWriteArraySet、ConcurrentHashMap、ConcurrentLinkedQueue

CopyOnWriteArraySet使用CopyOnWriteArrayList 来实现,在这两者上进行写操作,将导致创建整个底层数组的副本,源数组不变!

ConcurrentHashMap、ConcurrentLinkedQueue使用了类似的技术,允许并发的读取和写入,但是容器中只有部分内容而不是整个容器可以被复制和修改!

示例:CopyOnWriteArrayList读写操作(转载)

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class CopyOnWriteArrayListDemo {
    /**
     * 读线程
     * @author wangjie
     *
     */
    private static class ReadTask implements Runnable {
        List<String> list;
 
        public ReadTask(List<String> list) {
            this.list = list;
        }
 
        public void run() {
            for (String str : list) {
                System.out.println(str);
            }
        }
    }
    /**
     * 写线程
     * @author wangjie
     *
     */
    private static class WriteTask implements Runnable {
        List<String> list;
        int index;
 
        public WriteTask(List<String> list, int index) {
            this.list = list;
            this.index = index;
        }
 
        public void run() {
            list.remove(index);
            list.add(index, "write_" + index);
        }
    }
 
    public void run() {
        final int NUM = 10;
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < NUM; i++) {
            list.add("main_" + i);
        }
        ExecutorService executorService = Executors.newFixedThreadPool(NUM);
        for (int i = 0; i < NUM; i++) {
            executorService.execute(new ReadTask(list));
            executorService.execute(new WriteTask(list, i));
        }
        executorService.shutdown();
    }
 
    public static void main(String[] args) {
        new CopyOnWriteArrayListDemo().run();
    }
}

CopyOnWriteArrayList详解

其实我有一个问题:当一个线程在读取数据时,另一个线程修改完数据想要更新容器时,这时候是怎么处理的?首先想到的就是等待读取操作的结束,然后进行更新,这也是可以合理的!因为是可以进行并发操作的,所以可能会有其他线程也在遍历容器,这时候怎么处理?问题在复杂一点,如果有多个写入操作在等待,这又如何处理(FIFO队列解决?)!
synchronizedHashMap和ConcurrentHashMap:ConcurrentHashMap写操作的影响更小,性能更高!

总结:在以读取操作为主的程序中,使用这些线程安全的类库性能会更好!如果有频繁的写入操作,那么synchronized来的适合!

乐观加锁:Atomic类允许执行所谓的“乐观加锁”,意思是:当执行某项计算时,实际上并没有使用互斥。但是当计算完成,准备更新这个Atomic对象时,你需要使用一个叫compareAndSet()方法将新值和旧值进行比较,不一致则表示操作失败——表明已经有对象对其进行了修改!

优点:没有了互斥开销(获得锁和释放锁),运算速度更快!

缺点:的处理好当某一个操作失败之后应高执行的动作!

ReadWriteLock

ReadWriteLock允许同时又多个读取线程,只要它们不试图写入即可。如果写锁已经被其他线程持有,那么读取线程都不能访问,直到写锁被释放!

Java的并发集合允许读写同时进行,如果不是特殊需要,尽量用并发集合实现!

最后介绍一种多线程模型的替换方式:活动对象(Active Object,《Think in Java》中的术语)

主要的工作模式是:每个对象都维护自己的一个工作器线程和消息队列,并且所有 对这种对象的请求都将进入队列(可以说是无界的),任何时刻都只能运行一个。当向一个活动对象发送消息时,这条消息会转变成为一个任务,该任务会被插入到这个对象的队列中,等待执行。这个时候Future有出场了,它可以返回某一任务执行的结果,下面是《Think in java》里的一个示例:
package multithreading;

import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

public class ActiveObjectDemo {
	private ExecutorService ex =
		    Executors.newSingleThreadExecutor();
	private Random rand = new Random(47);
	// Insert a random delay to produce the effect of a calculation time:
	private void pause(int factor) {
	    try {
	      TimeUnit.MILLISECONDS.sleep(
	        100 + rand.nextInt(factor));
	    } catch(InterruptedException e) {
	      System.out.println("sleep() interrupted");
	    }
	 }
	public Future<Integer>
	  calculateInt(final int x, final int y) {
	    return ex.submit(new Callable<Integer>() {
	      public Integer call() {
	    	  System.out.println("starting " + x + " + " + y);
	        pause(500);
	        return x + y;
	      }
	    });
	  }
	public Future<Float>
	  calculateFloat(final float x, final float y) {
	    return ex.submit(new Callable<Float>() {
	      public Float call() {
	    	System.out.println("starting " + x + " + " + y);
	        pause(2000);
	        return x + y;
	      }
	    });
	  }
	public void shutdown() { ex.shutdown(); }
	public static void main(String[] args) {
	    ActiveObjectDemo d1 = new ActiveObjectDemo();
	    // Prevents ConcurrentModificationException:
	    List<Future<?>> results =
	      new CopyOnWriteArrayList<Future<?>>();
	    for(float f = 0.0f; f < 1.0f; f += 0.2f)
	    	results.add(d1.calculateFloat(f, f));
	    for(int i = 0; i < 5; i++)
	    	results.add(d1.calculateInt(i, i));
	    System.out.println("All asynch calls made");
	    while(results.size() > 0) {
	      for(Future<?> f : results)
	        if(f.isDone()) {
	          try {
	        	  System.out.println(f.get());
	          } catch(Exception e) {
	            throw new RuntimeException(e);
	          }
	          results.remove(f);
	        }
	    }
	    d1.shutdown();
	 }
}

线程篇就此结束了,留着以后再慢慢补充!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值