java-多线程(生命周期以及线程的安全问题)(三)

三-线程的生命周期

线程的声明周期一定要搞懂,可以参考《操作系统概念》,有几种状态的切换要搞懂

JDK中用Thread.State类定义了线程的几种状态。要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

在这里插入图片描述
这里参考《操作系统概念》,说一说各种切换状态的原因:
六种切换模式:一定要搞懂呀

新建–>就绪:

就绪–>运行

运行–>死亡

运行–>阻塞:
java的调用:
(1):sleep()让进程先睡一会,其实就是进入阻塞态。
(2):在线程a中调用b.join(),a会阻塞,让b先去执行完.a才可以可以去进行执行
(3):等待同步锁:一会去讲
(4):
(5):suspend():将线程进行挂起,可能会导致死锁

这里引入一篇很多的
阻塞–>就绪:
(1):sleep()的时间到了
(2):join对应的线程结束。还是上面的例子:a中调用b.join(),b调用结束,a才开始被调用。
(3):获取了同步锁
(4):与wait相对,不再是等待状态。现在是唤醒状态。notify()/notifyAll()。
(5)与susupend线程挂起,相对应是:resume():相当于结束挂起。

运行–>就绪

四.线程的同步(简直太重要了,考研的pv操作就是罪恶行的问题):

这里不单单局限于java的多线程,我还是会引入线程的同步问题。这里参考《操作系统概念》:
线程同步:

讲了这么一大堆理论之后,是不是有一点晕乎乎的呢?没事,我们接下来用最实际的例子去讲一讲为什么进程的同步问题。还是之前的售票厅问题。虽然我们重写Runnale方法,从一定程度上解决了多对象使用相同变量的问题。但是实际的问题问题却依然没有解决:出现了重票和错票的问题。

package java1;

    /**
     * @author 邱顺顺
     * @create 2020-07-18-2:08
     * 例子:创建三个c窗口卖票,总票数为100张
     * 限制:用 Runnable接口的方法
     */
    class WindowSell implements Runnable{
        private int ticket=100;
        @Override
        public void run() {
            while(true){
                if(ticket>0){
                	System.out.println(Thread.currentThread().getName()+"剩余票数为"+ticket);
                    ticket--;
                }
                else break;
            }
        }
    }


public class WindowTest {
    public static void main(String[] args) {
        WindowSell w=new WindowSell();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口一:");
        t2.setName("窗口二:");
        t3.setName("窗口三:");

        t1.start();
        t2.start();
        t3.start();
    }
}

执行的结果如下在这里插入图片描述

看起来这似乎是不能接受的,可以类比一下生活的场景,三个人同时在车站买票,同时去同一个地方,结果三个窗口出来了同一张票。这样是错误的。而正确的做法是:当其中一个人购买票的时候,应该对当前的票进行上锁,下一个人只能买其他的票。

这里就引入了线程的安全问题,同时也是同步问题

要说一说线程的同步为什么会存在问题,多线程一定会存在问题吗?

这里存在了线程的安全问题,就是存在的数据的共享。

这里要提一提设计模式了,之前的单例模式中的懒汉式是存在线程的安全问题的。

package java1;

    /**
     * @author 邱顺顺
     * @create 2020-07-18-2:08
     * 例子:创建三个c窗口卖票,总票数为100张
     * 限制:用 Runnable接口的方法
     */
    class WindowSell implements Runnable{
        private int ticket=100;
        @Override
        public void run() {
            while(true){
                if(ticket>0){

                    //让当前进程先进性休眠
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"剩余票数为"+ticket);
                    ticket--;
                }
                else break;
            }
        }
    }


public class WindowTest {
    public static void main(String[] args) {
        WindowSell w=new WindowSell();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口一:");
        t2.setName("窗口二:");
        t3.setName("窗口三:");

        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述
这里让购票的进程短暂进入了阻塞,但是还是没有解决同步的问题,还明显的出现了错票。

这是我们期待的状态:
在这里插入图片描述
但是需要考虑边界情况:
在这里插入图片描述
这里就需要引入同步的问题:当某一位购票人买票的时候,需要对票进行上锁,就是不允许再对该票去进行访问。

  • 例子:创建三个c窗口卖票,总票数为100张

    • 1.问题:卖票的过程中出现了重票和错票的问题。
    • 2.其实说到底:原因就是:当某个线程操作车票的过程当中,尚未操作完成时其他线程就参与进来,也来操作车票。也就是:存在多个线程对共享数据进行使用
    • 3.如何解决:当一个线程a,在操作ticket的时候,其他线程不能参与进来,只来当前进程操作完,其他线程才可以去访问共享数据。(类似于关门上厕所,别人要等着,厕所用好了,别人才能用)
    • 4.如何用java代码去进行解决:线程的安全问题
    • 使用同步解决线程的安全问题:
      • 方式一和方式二都是jdk5.0之前的
      • 方式一:同步代码块
      • synchronized(同步监视器){
        需要被同步的代码
      • }
        ps:
    •  1.操作共享数据的代码:即为需要被同步的代码(在本例中就是操作ticket的相关代码)
      
    •  2.共享数据:多个线程共同操作的变量,或者数据。比如:ticket
      
    •  3.同步监视器?:俗称就是:锁。
      
    •  4.任何类的对象都可以充当锁
      

    关于obj的补充:同步监视器或者叫锁,有什么具体的要求吗?
    隐藏的要求:要求多个线程必须使用同一把锁(只能有一个锁)

    • 方式二:同步方法:
    • jdk5.0之后:

以下的实现代码是:对是实现的Runnable方法,使用同步方法,解决线程的安全问题

package java1;
    /**
     * @author 邱顺顺
     * @create 2020-07-22-9:21
     * 例子:创建三个c窗口卖票,总票数为100张
     * 限制:用 Runnable接口的方法
     *
     * 1.问题:卖票的过程中出现了重票和错票的问题。
     *
     * 2.其实说到底:原因就是:当某个线程操作车票的过程当中,尚未操作完成时
     * 其他线程就参与进来,也来操作车票。也就是:使用共享数据
     *
     * 3.如何解决:当一个线程a,在操作ticket的时候,其他线程
     * 不能参与进来,只来当前进程操作完,其他线程才可以去访问共享数据
     *
     * 4.如何用java代码去进行解决:线程的安全问题()
     *
     * 使用同步解决线程的安全问题:
     * 方式一和方式二都是jdk5.0之前的
     * 方式一:同步代码块
     *  synchronized(同步监视器){
     *      //需要被同步的代码
     *  }
     *      ps:
     *      1.操作共享数据的代码,即为需要被同步的代码
     *      2.共享数据:多个线程共同操作的变量,或者数据。比如:piao
     *      3.同步监视器?:俗称就是:锁。
     *      4.任何类的对象都可以充当锁
     *
     */
    class WindowSell implements Runnable{
        private int ticket=100;
        //要保证共用同一把锁
        Object obj=new Object();

        @Override
        public void run() {
            while(true){
                //把操作ticket的数据给包起来

                //锁是类的对象,所以不能传入变量,所以要进行转化
                synchronized (obj){
                    if(ticket>0){
                        //让当前进程先进性休眠
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        System.out.println(Thread.currentThread().getName()+"剩余票数为"+ticket);

                        ticket--;
                    }
                    else break;
                }
            }
        }
    }


public class WindowTest {
    public static void main(String[] args) {
        WindowSell w=new WindowSell();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口一:");
        t2.setName("窗口二:");
        t3.setName("窗口三:");

        t1.start();
        t2.start();
        t3.start();
    }
}

用继承Thread类,实现购票的同步,解决现成的安全问题:
代码如下:
ps:继承Thread类有别于实现Runnable方法,线程的创建会单独创建,所以如果还是和实现Runnable方法一样,直接将新建的obj创建synchronized的代码块中会出现三个对象,就无法遵循规格(多个对象只能使用一把锁:就是一个厕所只能被一个人去使用),使用static关键字实现

package java1;

/**
 * @author shkstart
 * @create 2020-07-22-10:03
 *
 *
 * 使用同步代码块,解决继承Thread类的线程安全问题
 *
 *
 *  */
class Window2 extends Thread{
    private static int ticket=100;
    private static Object obj=new Object();

    //注意:这里将obj传入对象是不可以的。
    //因为底下创建了像个Window2对象,所以有三个Object对象
    //obj的锁不唯一
    //解决方法:加上static解决,三个对象共用一个static
    @Override
    public void run() {
        while(true){

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized(obj){
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+": 买票,票号为: "+ticket);
                    ticket--;
                }
                else break;
            }
        }
    }
}
public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1=new Window2();
        Window2 t2=new Window2();
        Window2 t3=new Window2();

        t1.setName("窗口1: ");
        t2.setName("窗口2: ");
        t3.setName("窗口3: ");

        t1.start();
        t2.start();
        t3.start();
    }
}

以上是同步代码块的两种实现方式:(继承Thread类和实现Runnable接口),但是细细去想以下,上面的创建的锁,需要我们每一次去创建一下对象,有没有更快捷的办法,有没有最简便的对象可以提供给我们去进行使用?
of,course:当前对象:可以考虑用this关键字充当同步监视器,代表当前对象,但是需要在继承Thread类的方式中注意,因为main方法中会有多个对象。

那么在继承Thread中也会有比较好的方式:使用当前类去充当当前对象。---->类也是对象
代码如下:

package java1;

/**
 * @author shkstart
 * @create 2020-07-22-10:03
 *
 *
 * 使用同步代码块,解决继承Thread类的线程安全问题
 *
 *
 *  */
class Window2 extends Thread{
    private static int ticket=100;
    //private static Object obj=new Object();

    //注意:这里将obj传入对象是不可以的。
    //因为底下创建了像个Window2对象,所以有三个Object对象
    //obj的锁不唯一
    //解决方法:加上static解决,三个对象共用一个static
    @Override
    public void run() {
        while(true){

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //使用类充当当前对象
            synchronized(Window2.class){
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+": 买票,票号为: "+ticket);
                    ticket--;
                }
                else break;
            }
        }
    }
}
public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1=new Window2();
        Window2 t2=new Window2();
        Window2 t3=new Window2();

        t1.setName("窗口1: ");
        t2.setName("窗口2: ");
        t3.setName("窗口3: ");

        t1.start();
        t2.start();
        t3.start();
    }
}

  • 方式二:同步方法:

注意:虽然将可以对我们的run方法使用Synchronized,但是一旦锁多了,容易造成单个线程一直执行,所以一定要对固定的代码去进行上锁,可以写成一个方法。
代码如下:

package java1;

/**
 * @author shkstart
 * @create 2020-07-22-11:16
 */
class Window4 implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while(true){
            show();
        }
    }
    //synchronizee的同步方法,不用对整个方法包住
    //该同步监视器就是:this
    //类比代码块
    private synchronized void show(){
        if(ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"剩余票数为"+ticket);
            ticket--;
        }
    }
}


public class WindowTest4 {
    public static void main(String[] args) {
        Window4 w=new Window4();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口一:");
        t2.setName("窗口二:");
        t3.setName("窗口三:");

        t1.start();
        t2.start();
        t3.start();
    }
}

用继承Thread类去实现,通过同步方法解决线程的安全问题:
代码如下:

package java1;

/**
 * 使用同步方法解决实现Runnable接口的线程安全问题
 *
 * @author shkstart
 * @create 2020-07-22-11:06
 *
 * 使用同步方法解决实现Runnable接口的线程安全问题
 */
class Window3 extends Thread{
    private static int ticket=100;
    @Override
    //不能包多了,不然就成了一個窗口了
    public void run() {
        while(true){
        //非静态方法可以调用静态方法
        //该同步监视器是:Window3.class
            show();
        }
    }
    private static synchronized void show(){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+": 买票,票号为: "+ticket);
            ticket--;
        }
    }
}
public class WindowTest3 {
    public static void main(String[] args) {
        Window3 t1=new Window3();
        Window3 t2=new Window3();
        Window3 t3=new Window3();

        t1.setName("窗口1: ");
        t2.setName("窗口2: ");
        t3.setName("窗口3: ");

        t1.start();
        t2.start();
        t3.start();
    }
}

需要区别于前两种方法,需要在同步方法里注明static。在实现Runnable接口,通过同步方法来解决线程安全的方式中,同步监视器是this。但是集成Thread类有所相区别,多个线程并不对一个资源进行共享。

关于同步方法的总结:
1.同步方法任然涉及到同步监视器,只是不需要我们显示的声明
2.非静态的同步方法,同步监视器是:this
静态类的同步方法,同步监视器是:当前类本身:类.class

到此,线程的安全问题也算初步解决了:

解决线程安全问题的方法同步代码块,同步方法
实现Runnable接口同步代码快和同步方法
集成Thread类同步代码快和同步方法(注意static和两种方法下的同步监视器)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值