使用多线程的目的是更好的利用cpu资源,大部分多线程代码都可以用单线程来实现,但也有无法用单线程实现的,如:生产者消费者模型
下面对一些常用的概念进行区分:
多线程:指的是这个程序(一个进程)运行时产生了不止一个线程。
并行与并发:
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。
线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。若线程不安全则意味着线程调度顺序影响最终结果。如转账操作。
同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如在转账中加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。
一、线程的状态
线程共有五种状态,其状态转换如下图所示:

- 新建态(New):新创建的线程对象。
- 就绪态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。 即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。
- 运行态(Running):就绪状态的线程获取了CPU,执行程序代码。
- 死亡态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
- 阻塞态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
- 等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒。
- 同步阻塞:运行的线程在获取对象的同步锁时(Synchronized),同步锁被释放进入可运行状态(Runnable),若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
注:在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。
二、控制线程的基本方法
首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。
1、sleep()
sleep()方法属于Thread类,是一个静态方法,主要的作用是让当前线程停止执行,把cpu让给其他线程执行,但不会释放对象锁和监控的状态,到了指定时间后线程又会自动恢复运行状态
Java有两种sleep方法,一个只有一个毫秒参数,另一个有毫秒和纳秒两个参数
sleep(long millis)
sleep(long millis, int nanos)
注意:线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
2、wait()与notify()
wait()属于Object类,与sleep()的区别是当前线程会释放锁,进入等待此对象的等待锁定池。若线程A调用Obj.wait(),线程A就会停止运行,而转为等待状态。至于等待时间,看其他线程是否调用Obj.notify(),成为多个线程之间进行通讯的有手段。
注意:无论是wait()还是notify()都需要首先获得目标的对象的一个监视器。wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。当某代码并不持有监视器的使用权时去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。
"Synchronzied"是一种同步锁。作用是实现线程间同步,对同步的代码加锁,使得每一次,只能有一线程进入同步块,从而保证线程间的安全性。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的部分,进入同步代码前要获得给定对象的锁
- 修饰一个实例方法,进入同步代码前要获得当前实例的锁
- 修饰一个静态方法,进入同步代码前要获得当前类的锁
public class Test {
final static Object object=new Object();
public static class Thread1 extends Thread{
@Override
public void run() {
synchronized (object) {
System.out.println("T1 开始");
try {
System.out.println("T1 等待");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1 结束");
}
}
}
public static class Thread2 extends Thread{
@Override
public void run() {
synchronized (object) {
System.out.println("T2 开始");
System.out.println("释放一个线程");
object.notify();
System.out.println("T2 结束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread1();
Thread t2=new Thread2();
t1.start();
t2.start();
t1.join();
t2.join();
}
}
运行结果:
T1 开始
T1 等待
T2 开始
释放一个线程
T2 结束
T1 结束
如下为生产者消费者模型:
public class Test {
private final int MAX_SIZE = 100;
private LinkedList