一:线程的生命周期
1.Thread.State类
Thread.State类定义了线程的几种状态,在一个完整的生命周期中通常要经历五种状态:
- 新建: 当一个Thread类或其子类的对象被声明并创建时
- 就绪: start()方法后,将进入线程队列等待CPU时间片
- 运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态
- 阻塞: 被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
- 死亡: 线程完成全部工作或被提前强制性终止或出现异常导致结束
2.线程状态转换图
二:线程的同步
1.多线程的安全问题
- 问题原有: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,另一个线程就参与进来执行,导致共享数据的错误.
- 解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行.
2.三种解决方法
2.1.同步代码块
synchronized(对象){
// 需要被同步的代码;
}
说明:
- 操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
- 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
- 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:
- 在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
- 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
2.2.同步方法
public synchronized void add (String id){
......
}
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
- 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
- 非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身
同步的方式,解决了线程的安全问题。—好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 —局限性
2.3.示例
/**
* 示例:创建三个窗口卖票,总票数为100张.采用继承方式
* 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
* 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
* 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他
* 线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
*/
//方式一:采用继承类的方式
class Window1 extends Thread{
// 共享资源需要加上static
private static int ticket = 100;
@Override
public void run() {
while (true){
//同步方法
// handleTicket();
// 同步代码块
// 不能使用this,this代表w1,w2,w3
synchronized (Window1.class) { //Class clazz = Window1.class,Window1.class只会加载一次
if (ticket > 0){
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":"+ticket);
ticket--;
}else {
break;
}
}
}
}
// 方式一
/*private void handleTicket() {
// 同步代码块
synchronized (Window.class) { //Class clazz = Window.class
if (ticket > 0){
System.out.println(getName()+":"+ticket);
ticket--;
}
}
}*/
// 方式二
// 同步方法: 需要加上static,保证是同一个对象锁
// 方法不加static时,同步监视器:w1,w2,w3
private static synchronized void handleTicket() { // 同步监视器:Window.class
if (ticket > 0){
try {
//sleep()方法属于静态方法,与对象无关
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// static方法内容不能使用this,super关键字
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}
}
}
public class ExtendsTest {
public static void main(String[] args) {
Window1 w1 = new Window1();
Window1 w2 = new Window1();
Window1 w3 = new Window1();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
//方式二:采用实现接口的方式
class Window2 implements Runnable{
// 不需要加static
private int ticket = 100;
@Override
public void run() {
while (true){
//同步方法
// handleTicket();
//同步代码块
synchronized (this) { //此时的this:唯一的Window2的对象
if(ticket > 0){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
// getName()属于Thread的方法
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}else {
break;
}
}
}
}
//方式一
/*private void handleTicket() {
// 同步代码块
synchronized (this) { //同步监视器:this
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}
}
}*/
//方式二
//同步方法: 不需要加static
private synchronized void handleTicket() { //同步监视器:this
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+":"+ticket);
ticket--;
}
}
}
public class ImplementTest {
public static void main(String[] args) {
Window2 w1 = new Window2();
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
2.4.lock
-
synchronized 与 Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock()) -
优先使用顺序:
Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)
-
代码:
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2.调用锁定方法lock()
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁方法:unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
三:线程的通信
1.涉及到的三个方法
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
2.说明
- wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
- wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常.
- wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
3.sleep() 和 wait()的异同?
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 不同点:
- 两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
4.示例
class Number implements Runnable{
private int number = 1;
private Object obj = new Object();
@Override
public void run() {
while(true){
synchronized (obj) {
obj.notify();
if(number <= 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}