一、线程的几中状态
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下五种状态:
- 新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性的终止或出现异常导致结束
二、线程的同步
用线程同步来解决上面买票的存在的线程安全的问题
问题:
1、 取票过程中,出现重票、错票 -->出现了线程的安全问题
2、问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票
如何解决:
当一个线程在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
3、在Java中通过线程同步机制来解决
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:1、操作共享数据的代码,即为需要被同步的代码
2、共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
3、同步监视器:俗称:锁。任何一个类的对象都可以充当锁
要求:多个线程必须要共用同一把锁。
补充:在实现Runable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
package com.csii.day02; /** * @Author wufq * @Date 2020/11/24 15:21 * 多窗口卖票,总票为100张,使用Runerable接口的方式 * 存在线程安全,待解决 */ public class WindowTest01 { public static void main(String[] args){ //只声明了一个对象,放到了三个构造器内, // ticket就不需要前面加static,如果声明了多个对象,在用ticket时就必须加static,主要作用是静态变量公用 Ticket ticket = new Ticket(); /*Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start();*/ for(int i=0;i<3;i++){ Thread t1 = new Thread(ticket); t1.setName("窗口"+i+" "); t1.start(); } } } class Ticket implements Runnable{ private int ticket =100; //同步监视器:锁,类中的任一对象都可作为锁 // 要求:多个线程必须要共用同一把锁 Object obj = new Object(); //此时Ticket类中的dog对象也可以作为锁,原因在于多个线程共用了dog这一个对象 Dog dog = new Dog(); @Override public void run() { /*Object obj = new Object(); * 如果把obj对象声明在run()方法内也是不行的,原因在于多个线程新建时(Thread t1 = new Thread(ticket);) * 每new一次,就会调用一次run()方法,同时也就会创建一个obj对象,这样就不满足多个线程必须共用同一把锁, * 所以就不不能解决线程安全的问题 * * 同样synchronized(new Object){}也是不可以的,原因同上 * */ while (true){ //这时候也可以用this,因为this就是一个对象,谁调用就是谁,这么看的话ticket对象调用就是ticket对象,并且只有这一个共享锁 //所以说是可以的。这样的好处就没必要特意的去声明一个对象了 synchronized (this){//synchronized(obj){ if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket); ticket --; }else { break; } } } } } class Dog{ }
在继承Thread类出啊昂见多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器
package com.csii.day02; /** * @Author wufq * @Date 2020/11/26 17:04 */ public class WindowTest02 { public static void main(String[] args){ for(int i=0;i<3;i++){ Ticket1 ticket = new Ticket1(); ticket.setName("窗口"+i+" "); ticket.start(); } } } class Ticket1 extends Thread{ private static int ticket =100; /* * 运用继承Thread这种方式来创建多线程时, * 采用同步代码块解决线程安全问题时,一定要在变量和同步监视器(锁<-->对象)前面加上static使其变成共享 * */ private static Object obj = new Object(); @Override public void run() { while (true){ synchronized(obj){ if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+ticket); ticket --; }else { break; } } } } }
方式二:同步方法
4、同步的方式,解决了线程的安全问题 ---好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。--局限性