生命周期
在Java中,线程的生命周期从新建状态开始,到运行、阻塞、等待、计时等待、终止等各个状态。
Java里JDK 中用 Thread.State 枚举表示了线程的几种状态:
- 新建状态(New):使用new关键字创建线程对象后,线程就进入新建状态,此时线程尚未启动。
- 就绪状态(Runnable):线程调用start()方法后,线程进入就绪状态。在就绪状态下,表示线程已经获得除了CPU之外的其它必要资源,可以运行。此时并不是线程一定会立即运行,只是代表可以运行。
- 运行状态(Running):线程调度程序将就绪状态的线程置为运行状态,此时线程才真正开始执行run()方法中的代码。
- 阻塞状态(Blocked):当线程处于运行状态时,可能因为某些原因(如等待输入输出、线程调度程序调用等)暂时放弃 CPU,进入阻塞状态。
- 等待状态(Waiting):线程调用wait()方法后进入等待状态。线程会一直等待,直到其它线程调用相同对象的notify()或notifyAll()方法唤醒它。
- 计时等待状态(Timed Waiting):线程调用sleep()或join()方法后进入计时等待状态,也可以通过wait(long timeout)方法进入计时等待状态。
- 终止状态(Terminated):线程运行结束后进入终止状态。
线程的生命周期是动态的,在不同的时间点可能处于不同的状态,但线程的状态转换必须按照上述规定进行。了解线程的生命周期,有助于我们更好地控制和管理线程,保证多线程程序的正确性和高效性。
控制线程状态的方法
- start()方法:启动线程,使线程进入就绪状态。
- sleep()方法:使当前线程休眠指定时间,进入阻塞状态。
- wait()方法:使线程等待唤醒,进入等待状态。
- notify()
tifyAll()方法:唤醒等待中的线程。
- join()方法:等待其他线程执行完毕,使当前线程进入等待状态。
注意
- 多线程并不一定就能提高程序性能,过多的线程反而可能会拖慢程序速度。
- 线程同步是很重要的,否则会导致数据不一致、死锁等问题。
- 不要在run()方法中直接调用stop()方法来结束线程,应该使用其他方式来安全地终止线程。
- 尽量避免使用suspend()和resume()方法,因为它们容易导致死锁。
线程同步
Java中的线程同步是指多个线程对共享数据的访问按照一定的顺序进行协调,以避免出现不可预期的结果。线程同步的主要作用是保证线程安全,避免多个线程同时访问共享数据时可能出现的数据冲突和不一致。在Java中,常见的线程同步方法有synchronized关键字和Lock接口。
synchronized关键字
synchronized关键字是Java语言内置的一种锁机制,它可以确保同一时刻只有一个线程可以执行某个方法或代码块。它有两种用法:
(1)同步方法:在方法前加上synchronized关键字,表示该方法是同步方法,同一时刻只能被一个线程访问。
public synchronized void method() {
// 访问共享资源的代码
}
(2)同步代码块:使用synchronized关键字来包围一段代码,表示这段代码是同步的,同一时刻只能被一个线程执行。
synchronized (lock) {
// 访问共享资源的代码
}
需要注意的是,当线程执行完synchronized代码块或同步方法时,会自动释放锁,其他线程才能获得锁并执行代码。
Lock接口
Lock接口也是一种线程同步机制,相比于synchronized关键字,它更加灵活和强大。它提供了与关键字synchronized不同的锁定和解锁机制,更加灵活。
常用方法
(1)lock():获得锁,如果锁已经被其他线程占用,则当前线程会被阻塞。
(2)unlock():释放锁。
(3)tryLock():尝试获得锁,如果锁被其他线程占用,则直接返回false。
例如,使用ReentrantLock类实现线程同步:
private ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock(); // 获得锁
try {
// 访问共享资源的代码
} finally {
lock.unlock(); // 释放锁
}
}
在Java中,线程同步还有其他相关知识点,例如wait()、notify()和notifyAll()等,可以实现线程之间的等待和通知,进一步提高多线程程序的效率和正确性。
线程同步的特点
- 保证了多线程的正确性和一致性,防止数据竞争和线程之间的干扰。
- 线程同步通常会带来一定的性能开销,因为需要保证同步代码块的互斥性,使得多个线程无法同时进入同步块。
- 如果同步代码块的粒度过大或过小,都会影响程序的性能。因此需要合理地选择同步代码块的粒度。
- 线程同步还可能导致死锁和饥饿等问题,需要特别注意。
线程同步的优点
- 保证了多线程之间的数据访问的安全性和正确性,确保程序能够正确运行。
- 可以使多线程之间的协作更加有效和有序,避免出现不可预期的结果。
线程同步的缺点
- 同步代码块的开销比较大,会影响程序的性能。
- 锁的粒度过大或过小,都可能影响程序的性能和并发性。
- 同步代码的编写和调试比较复杂,容易出现死锁和饥饿等问题。
- 如果同步粒度过大,会导致多个线程之间的竞争过于激烈,降低了并发性能。
注意
- 尽量减少同步代码块的粒度,避免锁竞争过于激烈。
- 避免出现死锁和饥饿等问题,尽量使用公平锁或非公平锁。
- 在同步代码块中,只保护共享数据的访问,尽量减少同步代码块的执行时间。
- 尽量使用局部变量代替共享变量,减少同步代码块的粒度。
- 尽量避免使用双重检查锁定等复杂的同步方式,容易出现竞态条件和线程安全问题。