前言并发编程的目的是为了提高程序的执行速度.但是并不意味着启动更多的线程会达到更好的并发效果,并发编程还会引起死锁 , 上下文频繁切换 , 线程不安全等问题.该问题主要介绍几种并发引来的问题及解决方案
一.上下文切换
1.1什么是上下文切换?
无论是单核还是多核cpu都是支持多线程执行代码的,cpu通过给每一个线程分配时间片,只有拿到时间片的线程才可以执行,通常时间片很短只有几十ms,让我们感觉是多个线程在并行操作.
1.2多线程一定快吗?
我们通过以下代码对比两者的执行速度
public class ConcurrentTest {
private final static long times = 100000000;
public static void main(String[] args) throws InterruptedException {
concurrent();
serial();
}
private static void concurrent() throws InterruptedException {
long start = System.currentTimeMillis();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
long a = 0;
for (int i = 0 ; i < times;i++) {
a++;
}
}
});
thread.start();
long b = 0;
for (int j = 0 ; j < times;j++ ){
b++;
}
thread.join();
long end = System.currentTimeMillis();
System.out.println("concurrent cost:" +(end - start));
}
private static void serial() {
long start = System.currentTimeMillis();
long a = 0;
for (int i = 0 ; i < times;i++) {
a++;
}
long b = 0;
for (int j = 0 ; j < times;j++ ){
b++;
}
long end = System.currentTimeMillis();
System.out.println("serial cost:" +(end - start));
}
}
循环次数 | 串行时间/ms | 并行时间 |
---|---|---|
1万 | 0 | 2 |
10万 | 3 | 7 |
100万 | 6 | 13 |
1000万 | 14 | 11 |
1亿 | 145 | 76 |
从数据上发现,循环次数少时串行速度快,多时并行快,这是为什么呢?因为线程的创建与销毁和上下文切换带来的额外开销
1.3如何查看上下文切换?
可通过vmstat 查看上下文切换
1.4如何减少上下文切换?
- 无锁编程 : 将处理的数据hash处理或取余等
- CAS算法: juc 下的atomic包
- 减少使用线程
- 协程:单线程多任务调度(原生的java默认不支持,可引入其他框架)
1.5如何线上排查处理上下文切换
- 使用jstack获取线程信息
- 统计所有线程分别处于什么状态
- 打开dump文件查看处于WAITING的线程在做什么
- 减少JBOSS的工作线程数
- 重启JBOSS
二.死锁
当多线程对共享资源同时抢夺时,可能会因为相互等待资源释放,造成死锁
public class DeadLock {
static class Q {}
static class D {}
private static Q q = new Q();
private static D d = new D();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (q) {
System.out.println(Thread.currentThread().getName()+"拿到了Q");
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
System.out.println(Thread.currentThread().getName()+"拿到了D");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (d) {
System.out.println(Thread.currentThread().getName()+"拿到了D");
try {
Thread.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (q) {
System.out.println(Thread.currentThread().getName()+"拿到了Q");
}
}
}
}).start();
}
}