本章涉及内容:
总结:创建一个联系人实体类,姓名和电话号码
总结:创建一个ConcurrentSkipListMap<String,Contact>来保存联系人,键为姓名+电话号码
总结:
日志:
总结:
总结:
- 使用非阻塞、线程安全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();
}
}
}
日志:
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:执行结束
第六章完。。。。。。。。。。。。。