Java 多线程
Java 多线程介绍
创建新线程
Java 用Thread对象表示一个线程,通过调用start()启动一个新线程;
一个线程对象只能调用一次start()方法;
线程的执行代码写在run()方法中;
线程调度由操作系统决定,程序本身无法决定调度顺序;
Thread.sleep()可以把当前线程暂停一段时间。
线程状态
Java 线程对象Thread的状态包括:New、Runnable、Blocked、Waiting、Timed Waiting和Terminated;
通过对另一个线程对象调用join()方法可以等待其执行结束;
可以指定等待时间,超过等待时间线程仍然没有结束就不再等待;
对已经运行结束的线程调用join()方法会立刻返回。
中断线程
对目标线程调用interrupt()方法可以请求中断一个线程,目标线程通过检测isInterrupted()标志获取自身是否已中断。如果目标线程处于等待状态,该线程会捕获到InterruptedException;
目标线程检测到isInterrupted()为true或者捕获了InterruptedException都应该立刻结束自身线程;
通过标志位判断需要正确使用volatile关键字;
volatile关键字解决了共享变量在线程间的可见性问题。
守护线程
守护线程是为其他线程服务的线程;
所有非守护线程都执行完毕后,虚拟机退出;
守护线程不能持有需要关闭的资源(如打开文件等)。
线程同步
多线程同时读写共享变量时,会造成逻辑错误,因此需要通过synchronized同步;
同步的本质就是给指定对象加锁,加锁后才能继续执行后续代码;
注意加锁对象必须是同一个实例;
对 JVM 定义的单个原子操作不需要同步。
同步方法
用synchronized修饰方法可以把整个方法变为同步代码块,synchronized方法加锁对象是this;
通过合理的设计和数据封装可以让一个类变为“线程安全”;
一个类没有特殊说明,默认不是 thread-safe;
多线程能否安全访问某个非线程安全的实例,需要具体问题具体分析。
死锁
Java 的synchronized锁是可重入锁;
死锁产生的条件是多线程各自持有不同的锁,并互相试图获取对方已持有的锁,导致无限等待;
避免死锁的方法是多线程获取锁的顺序要一致。
wait 和 notify
wait和notify用于多线程协调运行:
- 在
synchronized内部可以调用wait()使线程进入等待状态; - 必须在已获得的锁对象上调用
wait()方法; - 在
synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程; - 必须在已获得的锁对象上调用
notify()或notifyAll()方法; - 已唤醒的线程还需要重新获得锁后才能继续执行。
ReentrantLock
ReentrantLock可以替代synchronized进行同步;
ReentrantLock获取锁更安全;
必须先获取到锁,再进入try {...}代码块,最后使用finally保证释放锁;
可以使用tryLock()尝试获取锁。
Condition
Condition可以替代wait和notify;
Condition对象必须从Lock对象获取。