1.多线程调试的方法
如下代码存在安全问题,在多个线程操作时会出现数据越界异常。
package com.thread.chapter09;
import java.util.ArrayList;
/**
* t1,t2同时对 arrayList进行操作,会出现脏读,脏写
* 数组越界异常是因为 size在被扩容之前,比如 t1,t2都
* 读到了size = 9;然后都认为可以继续做 add 操作,t1把
* size++ 到长度为10,t2仍然以为size = 9,可以add,结果
* size++ 为11,就发生了越界访问。
* Created by chenbin on 2019\8\24 0024.
*/
public class UnSafeArrayList {
static ArrayList al = new ArrayList();
static class AddTask implements Runnable {
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
for (int i = 0;i < 1000000;i++) {
al.add(new Object());
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new AddTask(),"t1");
Thread t2 = new Thread(new AddTask(),"t2");
t1.start();
t2.start();
}
}
2.线程dump及分析
以一个死锁案例来介绍查看堆栈信息,寻找死锁原因:
package com.thread.chapter09;
import java.util.concurrent.locks.ReentrantLock;
/**
* 死锁案例,以及死锁检测解决(中断死锁线程)
* Created by chenbin on 2019\8\16 0016.
*/
public class DeadLockDemo2 implements Runnable {
public int lock;
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
public DeadLockDemo2(int lock) {
this.lock = lock;
}
public void run() {
try {
if (lock == 1) {
lock1.lockInterruptibly();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if (lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.out.print(Thread.currentThread().getId() +
":线程退出");
}
}
public static void main(String[] args) throws InterruptedException {
DeadLockDemo2 r1 = new DeadLockDemo2(1);
DeadLockDemo2 r2 = new DeadLockDemo2(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(1000);
}
}
这段代码运行,线程一直不会结束。用cmd打开命令窗口,jps命令查看进程:
使用 jstack 3620 命令查看该进程下所有的线程状态:
3.JDK8对并发的新支持
3.1LongAdder
– 和AtomicInteger类似的使用方式
– 在AtomicInteger上进行了热点分离
– public void add(long x)
– public void increment()
– public void decrement()
– public long sum()
– public long longValue()
– public int intValue()
3.2CompletableFuture
– 实现CompletionStage接口(40余个方法)
– Java 8中对Future的增强版,支持函数式编程。不需要自己写Callable任务。
– 支持流式调用
代码案例:
package com.thread.chapter09.jdk8;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* jdk8对并发的新支持
* future模式
* Created by chenbin on 2019\8\24 0024.
*/
public class CompletableFutureDemo {
public static Integer calc(int para) {
try {
//模拟一个长时间执行
Thread.sleep(5000);
} catch (InterruptedException e) {
}
return para * para;
}
public static void main(String[] args) {
//1.耗时操作,单独起一个异步线程来计算
final CompletableFuture<Integer> future =
CompletableFuture.supplyAsync(()-> calc(50));
//2.其它操作在主线程中继续
System.out.println("step 2.");
System.out.println("step 3.");
System.out.println("step 4.");
/**
* 3.最后获取第一步耗时操作的计算结果,若2完成后1仍未完成,则会等到1完成后
* 返回结果
*/
try {
System.out.println("获取future的计算结果:" + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);
static CompletableFuture<Void> runAsync(Runnable runnable);
static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);
3.3StampedLock
– 读写锁的改进
– 读不阻塞写
StampedLock的实现思想
– CLH自旋锁
– 锁维护一个等待线程队列,所有申请锁,但是没有成功的线程都记录在这个队列中。每一个节点(一个
节点代表一个线程),保存一个标记位(locked),用于判断当前线程是否已经释放锁。
– 当一个线程试图获得锁时,取得当前等待队列的尾部节点作为其前序节点。并使用类似如下代码判断前
序节点是否已经成功释放锁:
while (pred.locked) {
}
– 不会进行无休止的自旋,会在在若干次自旋后挂起线程
package com.thread.chapter09.jdk8;
import java.util.concurrent.locks.StampedLock;
/**
* StampedLock
* 乐观读
*/
public class StampLockDemo3 {
private int balance;
private StampedLock lock = new StampedLock();
public StampLockDemo3() {
balance=10;
}
/**
* 读的时候不阻塞写,乐观读
* 通过判断 时间戳 来判断是否需要重读(其它线程对变量做了写操作的情况下,时间戳会发生改变)
*/
public void optimisticRead() {
long stamp = lock.tryOptimisticRead();
int c = balance;
// 这里可能会出现了写操作,因此要进行判断
if(!lock.validate(stamp)) {
// 要重新读取
System.out.println("要重新读取");
stamp = lock.readLock();
try{
c = balance;
}
finally{
lock.unlockRead(stamp);
}
}
System.out.println("读取的值为:"+c);
}
/**
* 写操作会获取写锁
* @param value
*/
public void write(int value) {
long stamp = lock.writeLock();
balance += value;
lock.unlockWrite(stamp);
}
public static void main(String[] args) {
StampLockDemo3 demo=new StampLockDemo3();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
demo.optimisticRead();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true){
demo.write(2);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
源代码可在github上下载:https://github.com/chenbin911029/mutiThread
类文件在 com.thread.chapter09 包下。
备注:本文为JAVA高并发程序设计(葛一鸣著)读书笔记,以及自身的整理和实践。