第六章 Concurrent Collections (并发集合类) 【下】

本章涉及内容:
  • 使用非阻塞、线程安全lists集合
  • 使用阻塞、线程安全lists集合
  • 通过优先级确定顺序来使用阻塞式线程安全lists集合
  • 通过延迟元素来使用线程安全lists集合
  • 使用线程安全导航maps集合
  • 生成一个并发随机数
  • 使用原子变量
  • 使用原子数组

1、使用线程安全导航maps集合

ConcurrentSkipListMap 实现了ConcurrentNavigableMap接口,它使用了Skip List 数据结构(基于平行list集合)来存储数据,使得存储更加高效。

例子:利用ConcurrentSkipListMap实现联系人

package com.jack;

public class Contact {
	private String name;
	private String phone;
	public Contact(String name, String phone) {
		super();
		this.name = name;
		this.phone = phone;
	}
	public String getName() {
		return name;
	}
	public String getPhone() {
		return phone;
	}

	
}

总结:创建一个联系人实体类,姓名和电话号码

package com.jack;

import java.util.concurrent.ConcurrentSkipListMap;

public class Task implements Runnable {

	private String id;
	private ConcurrentSkipListMap<String, Contact> map;
	
	public Task(String id, ConcurrentSkipListMap<String, Contact> map) {
		super();
		this.id = id;
		this.map = map;
	}

	@Override
	public void run() {
		for (int i=0; i<1000; i++){
			Contact contact =new Contact(id, String.valueOf(i+1000));
			map.put(id+contact.getPhone(), contact);
		}
	}
	
	
	
	

}

总结:创建一个ConcurrentSkipListMap<String,Contact>来保存联系人,键为姓名+电话号码

package com.jack;

import java.util.Map;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

public class Main {
	public static void main(String[] args) throws Exception{
		ConcurrentSkipListMap<String, Contact> map;
		map = new ConcurrentSkipListMap<>();
		Thread threads[] = new Thread[25];
		int counter =0;
		for (char i='A'; i<'Z'; i++){
			Task task = new Task(String.valueOf(i),map);
			threads[counter] = new Thread(task);
			threads[counter].start();
			counter++;
		}
		for (int i=0; i<25; i++){
			try{
				threads[i].join();
			} catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		System.out.printf("Main: map的大小:%d\n", map.size());
		Map.Entry<String, Contact> element;
		Contact contact;
		
		element = map.firstEntry();
		contact = element.getValue();
		System.out.printf("Main: 第一个Entry : %s: %s\n", contact.getName(), contact.getPhone());
		element = map.lastEntry();
		contact = element.getValue();
		System.out.printf("Main: 最后的Entry: %s: %s\n", contact.getName(), contact.getPhone());
		System.out.printf("Main: 子map从A1996 到B1002: \n");
		ConcurrentNavigableMap<String, Contact> submap = map.subMap("A1996", "B1002");
		do{
			element=submap.pollFirstEntry();
			if(element!=null){
				contact = element.getValue();
				System.out.printf("%s: %s\n", contact.getName(), contact.getPhone());
			}
			
		}while (element !=null);
	}
}

总结:

  • 1、创建一个ConcurrentSkipListMap 实例
  • 2、创建25个线程来添加联系人
  • 3、threads[i].join()方法防止main提前退出
  • 4、map.firstEntry() 得到第一个键值对,map.lastEntry()得到最后键值对
  • 5、map.subMap得到一个子map,两个参数是键的范围。主要A1000 到A1999 一直到Y1000到Y1999
  • 6、pollFirstEntry() 得到第一个键值对,同时移除。
  • 7、这里亮点就是它会按照A1000 到Y1999顺序排列

 扩展:

  • 1、headMap(K toKey) 返回该toKey前一个元素。
  • 2、tailMap(K fromKey): 返回该fromKey后一个元素
  • 3、putIfAbsent(K key, V value) ,如果不存在就插入该键值
  • 4、pollLastEntry() 移除最后的键值对(Map.Entry)
  • 5、replace(k key, V value) 如果存在该key进行替换。

2、生成一个并发随机数

通过ThreadLocalRandom类生成随机数 (其实就是线程内部一个随机生成器,不会与其他线程共用生成器)

package com.jack;

import java.util.concurrent.ThreadLocalRandom;

public class TaskLocalRandom implements Runnable {

	
	public TaskLocalRandom() {
		ThreadLocalRandom.current();
	}

	@Override
	public void run() {
		String name = Thread.currentThread().getName();
		for(int i=0; i<10; i++){
			System.out.printf("%s: %d\n", name, ThreadLocalRandom.current().nextInt());
		}
		
	}

}

package com.jack;

public class Main {
	public static void main(String[] args) throws Exception{
		Thread threads[] = new Thread[3];
		for (int i=0; i<3; i++){
			TaskLocalRandom task = new TaskLocalRandom();
			threads[i] = new Thread(task);
			threads[i].start();
		}
	}
}


总结:基本用法和Math.random类似
日志:
Thread-0: -1183906547
Thread-0: 577959842
Thread-0: -105441313
Thread-0: -425609536
Thread-0: 1551546221
Thread-0: 191950995
Thread-0: -915467104
Thread-0: 1952188497
Thread-0: 845863336
Thread-0: 888400078
Thread-1: 1682302533
Thread-1: 1007563307
Thread-1: -1911050677

3、使用原子变量

原子变量基于同步思想Compare and Set(比较和设置),简单理解就是判断旧的值是否不变,那么就可以进行设置操作,如果旧的值改变就进行下次相同的操作,直到完成测试。(大概不恰当的比喻,早晨上厕所,人很多需要等,当然你可以不用等,先玩一会,等5分钟然后在看一次有没有占住厕所,如果没有你可以去,如果有你会宿舍玩一会再来看。。。)

例子:利用AtomicLong来模拟取存款过程

package com.jack;

import java.util.concurrent.atomic.AtomicLong;

public class Account {

	private AtomicLong balance;

	public Account() {
		super();
		balance = new AtomicLong();
	}

	public AtomicLong getBalance() {
		return balance;
	}

	public void setBalance(AtomicLong balance) {
		this.balance = balance;
	}
	
	public void addAmount(long amount){
		this.balance.getAndAdd(amount);
	}
	
	public void substractAmount(long amount){
		this.balance.getAndAdd(-amount);
	}
	
}

总结:

  • 1、创建一个AtomicLong();
  • 2、增加余额balance.getAndAdd(amount)
  • 3、减少余额balance.getAndAdd(-amount) 

package com.jack;

import java.util.concurrent.atomic.AtomicLong;

public class Account {

	private AtomicLong balance;

	public Account() {
		super();
		balance = new AtomicLong();
	}

	public AtomicLong getBalance() {
		return balance;
	}

	public void setBalance(AtomicLong balance) {
		this.balance = balance;
	}
	
	public void addAmount(long amount){
		this.balance.getAndAdd(amount);
	}
	
	public void substractAmount(long amount){
		this.balance.getAndAdd(-amount);
	}
	
}
总结:存钱

package com.jack;

public class Bank implements Runnable {

	private Account account;
	
	
	public Bank(Account account) {
		super();
		this.account = account;
	}


	@Override
	public void run() {
		for (int i=0; i<100; i++){
			account.substractAmount(1000);
		}
		
	}
	
}
总结:取钱

package com.jack;

import java.util.concurrent.atomic.AtomicLong;

public class Main {
	public static void main(String[] args) throws Exception{
		Account account = new Account();
		
		account.setBalance(new AtomicLong(1000));
		Company company = new Company(account);
		Thread companyThread = new Thread(company);
		Bank bank = new Bank(account);
		Thread bankThread = new Thread(bank);
		System.out.printf("Account: 初始化账户余额 %s\n", account.getBalance().toString());
		companyThread.start();
		bankThread.start();
		try{
			companyThread.join();
			bankThread.join();
			System.out.printf("Account: 最后的余额为: %s\n", account.getBalance().toString());
		}catch (InterruptedException e){
			e.printStackTrace();
		}
	}
}


总结:

  • 1、创建两个线程跑存钱和取钱

扩展:除了AtomicLong, 还有AtomicBoolean, AtomicInteger 和 AtomicReference


4、使用原子数组

当一个对象在多个线程之间共享会可能会导致死锁情况。那么如何避免这个种情况,它提供compare-and-swap operation(比较和交换操作),它的性能更高,它在修改一个变量值通过如下三步:

  • 1、获取旧的值
  • 2、把新的值赋值给临时变量
  • 3、如果旧的值等于实际变量的值,就可以将新的值进行替换了,反之,说明有其他线程替换值,不能执行替换操作。

例子:使用AtomicIntegerArray类实现

package com.jack;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class Incrementer implements Runnable {

	private AtomicIntegerArray vector;
	
	public Incrementer(AtomicIntegerArray vector) {
		super();
		this.vector = vector;
	}

	@Override
	public void run() {
		for(int i=0; i<vector.length(); i++){
			//在索引i的值自加1
			vector.getAndIncrement(i);
		}
	}

}
package com.jack;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class Decrementer implements Runnable {

	private AtomicIntegerArray vector;
	
	public Decrementer(AtomicIntegerArray vector) {
		super();
		this.vector = vector;
	}

	@Override
	public void run() {
		for(int i=0; i<vector.length(); i++){
			//在索引i的值自减1
			vector.getAndDecrement(i);
		}
		
	}

}

package com.jack;

import java.util.concurrent.atomic.AtomicIntegerArray;

public class Main {
	public static void main(String[] args) throws Exception{
		final int THREADS = 100;
		AtomicIntegerArray vector = new AtomicIntegerArray(1000);
		Incrementer incrementer = new Incrementer(vector);
		Decrementer decrementer = new Decrementer(vector);
		Thread threadIncrementer[] = new Thread[THREADS];
		Thread threadDecrementer[] = new Thread[THREADS];
		for (int i=0; i<THREADS; i++){
			threadIncrementer[i] = new Thread(incrementer);
			threadDecrementer[i] = new Thread(decrementer);
			threadIncrementer[i].start();
			threadDecrementer[i].start();
		}
		for(int i=0; i<100; i++){
			try {
				threadIncrementer[i].join();
				threadDecrementer[i].join();
			} catch (InterruptedException e){
				e.printStackTrace();
			}
		}
		for (int i = 0; i < vector.length(); i++) {
			if(vector.get(i) !=0){
				System.out.printf("Main: Vector[ %d ] : %d\n", i, vector.get(i));
			}
		}
		System.out.println("Main:执行结束");
	}
}

总结:

  • 1、创建容量为1000的数组AtomicIntegerArray.
  • 2、创建两个增加和减少任务 (每种任务总共跑100个线程)
  • 3、join()方法防止main提前退出
  • 4、判断数组的值是否为0,正确的应该是0(如果你想看到不为零效果可以注释减任务获取加任务)

扩展:

  • 1、get(int i) 返回索引为i的数据
  • 2、set(int I, int newValue) 设置索引i的值

日志:

Main:执行结束

第六章完。。。。。。。。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值