两个基本概念-》
并发:有资源竞争
并行:没有资源竞争
线程上下文切换
使用多线程的目的是为了充分利用CPU,但要认识到,每个CPU同一时刻只能被一个线程使用。
cpu通过时间片分配算法来循环执行任务,任务从保存到再加载的过程就是一次上下文切换。这种切换是有时间开销的。因此只有在cpu资源浪费的情况下使用多线程能提高效率,否则多个线程抢一个cpu,线程创建和上下文切换的开销比较高。
这里可以使用Lmbench3来测量上下文切换时长。
使用vmstat来测量上下文切换次数。
顺便提一下java自带的一些工具:
jps:虚拟机进程状况工具,可以列出虚拟机进程
jstat:虚拟机统计信息监视工具 显示本地或者远程虚拟机进程中的类加载、内存、垃圾收集、即时编译等运行时数据
jinfo:java配置信息工具 实时查看和调整虚拟机各项参数
jmap:java内存映像工具 生成堆转储快照
jhat:堆转储快照分析工具(也可以用EMA啊,可视化多方便)
jstack:堆栈跟踪工具 用于生成虚拟机当前时刻的线程快照(可用于分析死锁等问题)
减小上下文切换的四种方式
- 无锁并发编程:避免使用锁(避免多个同时访问共享数据,可以使用hash算法来分配每个线程处理的数据区域)
- CAS算法 无需加锁实现多线程安全
- 使用最少线程 不创建不必要的线程
- 协程 在单线程内实现多任务的调度并维持任务间切换
死锁问题
产生死锁会导致系统功能不可用
下面是一个死锁的例子
class DeadLockDemo {
private static String A="A";
private static String B="B";
public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
private void deadLock(){
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
synchronized (A){
try {
Thread.currentThread().sleep(2050);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
System.out.println("1");
}
}
}
});
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
synchronized (B){
synchronized (A){
System.out.println("2");
}
}
}
});
t1.start();
t2.start();
System.out.println(2);
}
}
线程1获取了A的锁,然后休眠,此时线程2获取了B锁,A线程恢复运行后尝试获取B锁,但线程2此时等待A锁释放,由此造成了死锁。
出现死锁后,将不能继续服务,只能通过dump线程查看
到底是哪个线程出现了问题。
避免死锁的方法:
避免一个线程同时获取多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
尝试使用定时锁lock.tryLock(timeout)来代替内部锁
对于数据库锁,加锁和解锁需要在一个数据库连接,否则可能产生解锁失败。