【Java多线程 33】
一、线程的几中状态
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充当同步监视器
|-- 实现Runable接口实现多线程
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、同步的方式,解决了线程的安全问题 ---好处
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。--局限性
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的
关于同步方法的总结:
1、同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
2、非静态的同步方法,同步监视器:this
静态的同步方法,同步监视器是:当前类本身
|-- 实现Runable接口实现多线程的线程安全问题
package com.csii.day02; /** * @Author wufq * @Date 2020/12/21 16:59 * * 采用同步方法来解决继承Runable接口的线程安全问题 */ public class WindowTest03 { public static void main(String[] args){ Window3 w3 = new Window3(); for(int i=0;i<3;i++){ Thread tt = new Thread(w3); tt.start(); } } } class Window3 implements Runnable{ private int ticket = 100; @Override public void run() { while (true) { show(); } } //同步方法,在方法前面加synchronized private synchronized void show(){//同步监视器:this,这时候调用this的就是main方法中for循环声明的三个Thread对象 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if(ticket>0){ System.out.println(Thread.currentThread().getName()+"票号:"+ticket); ticket--; } } }
|-- 继承Thread类实现多线程的线程安全问题
package com.csii.day02; /** * @Author wufq * @Date 2020/12/21 17:33 * 采用继承Thread类解决线程的安全问题 */ public class WindowTest04 { public static void main(String[] args){ for(int i=0;i<3;i++) { Window4 w4 = new Window4(); w4.start(); } } } class Window4 extends Thread{ private static int ticket=100; @Override public void run() { while (true) { show_1(); } } private static synchronized void show_1(){ //同步监视器:Window4.class // private synchronized void show_1(){ //同步监视器:tt,此种解决方式是错误的 if(ticket>0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "票号为:" + ticket); ticket--; } } }
备注:需要被同步的代码必须是共享的