多线程
线程是独立的执行空间,Java语言内置多线程功能,用类Thread来表达。每个Java应用程序会启动一个主线程 – 将main()放在自己的执行空间的最开始处. JVM会负责主线程的启动(以及其他比如GC的系统线程). 程序员负责启动自己的建立的线程.
启动新线程
- 建立
Runnable对象作为线程任务Runnable job = new MyRunnable(),Runnable接口只有一个方法run() - 建立Thread对象并赋值Runnable
Thread thread1 = new Thread(job); Thread thread2 = new Thread(job) - 启动
thread.start();
另一种创建线程的方法是用Thread的子类覆盖掉run(), 构造新线程Thread t = new Thread();. 从OO的角度看待, 此时Thread 与线程任务是不同概念的. 让子类继承Thread的目的通常是需要更特殊的Thread, 需要特殊的行为, 如果没有这种需求, 就没必要继承Thread.
线程调度器
多线程间的切换由调度器scheduler来管理, 线程有多种状态:
- 执行中,
sleep(2000): 睡眠2000ms, 时间到之前不会被执行, 但时间到了并不保证一定会被执行. 可能会抛出InterruptedException, 所以对它的调用要包含在try/catch中.- locked.
线程的run完成执行后, 将无法重新启动.
调度器在不同的JVM有不同的做法. 测试多线程时需要在不同机器上测试.
并发
Concurrency并发环境中, 为了避免冲突, 需要上锁, 使用synchronized来修饰方法使之每次只能被单一线程读写. 同步化是有代价的, 查询钥匙有性能上的损耗, 同步化也会强制线程排队执行, 还可能出现死锁.
死锁
因为两个线程互相持有对方正在等待的东西, 导致没有一方可以脱离等待. 数据库有事务回滚机制来复原死锁的事务, 但Java没有处理死锁的机制.
Volatile
Java为了提高程序运行效率, 编译器自动会优化, 把经常被访问的变量混存起来, 程序在读取这个变量时有可能会直接从缓存(例如寄存器)中读取这个值, 而不会去内存中读取. 但在多线程环境中, 变量的值可能因为别的线程而改变了, 而该缓存的值不会相应改变, 从而造成应用程序读取的值和实际的变量值不一致.
使用volatile修饰被不同线程访问和修改的变量, 使得其每次被用到时, 都是直接从对应的内存中提取, 而不会利用缓存了.
public class MyThread implements Runnable {
private volatile Boolean flag;
public void stop() { flag = false; }
public void run() { while(flag) ; }
}
如果flag没有被声明为volatile, 那么当这个县城的run()判断flag时, 使用的有可能是缓存中的值, 此时就不能及时地获取其他线程对flag所做的操作了.
但volatile不能保证操作的原子性, 因此一般情况下不能代替sychronized. 此外, 使用volatile会阻止编译器对代码的优化, 因此会降低