并发编程的问题
并发编程的目的是让程序运行更快,但并不是线程越多越好,要考虑以下几个方面
上下文切换
- CPU通过分配时间片的方式,来执行多线程任务,当前任务执行一个时间片后会切换到下一个任务,任务在切换时保存当前状态,再次获取到时间片后,加载保存的状态继续执行。任务从保存到加载的过程就是一次上下文切换。
- 过多的上下文切换会影响效率,所以多线程不一定快。在并发编程过程中,要考虑到上下文切换的影响,减少上下文切换
死锁
线程t1和线程t2互相等待对方释放锁
public class LockTest {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args) {
LockA la = new LockA();
new Thread(la).start();
LockB lb = new LockB();
new Thread(lb).start();
}
}
class LockA implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + " LockA 开始执行");
while(true){
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + " LockA 锁住 obj1");
Thread.sleep(3000); // 此处等待是给B能锁住机会
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + " LockA 锁住 obj2");
Thread.sleep(60 * 1000); // 为测试,占用了就不放
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class LockB implements Runnable{
public void run() {
try {
System.out.println(new Date().toString() + " LockB 开始执行");
while(true){
synchronized (LockTest.obj2) {
System.out.println(new Date().toString() + " LockB 锁住 obj2");
Thread.sleep(3000); // 此处等待是给A能锁住机会
synchronized (LockTest.obj1) {
System.out.println(new Date().toString() + " LockB 锁住 obj1");
Thread.sleep(60 * 1000); // 为测试,占用了就不放
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
避免死锁的常见方法
- 避免一个线程同时获取多个锁
- 避免一个线程在锁内同时占有多个资源,尽量保证每个锁只占用一个资源
- 使用定时锁lock.tryLock(timeout)
资源限制
硬件资源
- 带宽
- 硬盘读写速度
- CPU处理速度
软件资源
- 数据库连接数
- socket连接数
volatile
- 用于声明共享变量,当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。比synchronized的使用和执行成本低,不会引起上下文的切换
- Java线程模型确保所有线程看到的volatile变量的值是一致的
synchronized
Java中每一个对象都可以作为锁
- 对于普通同步方法,锁的是当前实例对象
- 对于静态同步方法,锁的是当前类的Class对象
- 对于同步方法块,锁是synchronized括号里配准的对象
当一个线程试图访问同步代码块时,他首先必须得到锁,退出或抛出异常时必须释放锁。
原子操作
原子操作是指不可被中断的一个或一系列操作,Java通过锁和循环CAS的方式实现原子操作。
- AtomicBoolean
- AtomicInteger
CAS的问题
- ABA问题,如果一个值由A,变成B,然后再变成A,CAS检查会认为值没有变化
- 循环时间长,开销大
- 只能保证单个共享变量的原子操作
ThreadLocal
- 线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构
- 一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值
- 通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到值
public class ThreadLocalTest {
private static final ThreadLocal<Long> TIME_THREADLOCAL =
new ThreadLocal<Long>() {
@Override
protected Long initialValue() {
return System.currentTimeMillis();
}
};
public static final void begin() {
TIME_THREADLOCAL.set(System.currentTimeMillis());
}
public static final long end() {
return System.currentTimeMillis() - TIME_THREADLOCAL.get();
}
public static void main(String[] args) throws InterruptedException {
ThreadLocalTest.begin();
TimeUnit.SECONDS.sleep(1);
System.out.println("Cost: " + ThreadLocalTest.end() + " mills");
}
}