多线程认知系列(二)之线程的生命周期及安全问题

多线程认知系列(二)之线程的生命周期及安全问题

多线程系列:
多线程认知系列(一)之认识线程、简单实现线程

上一篇文章已经初步的认识了线程,那么我们继续研究一下,那使用线程肯定会遇到一些问题啊,我们来看看

线程的生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态

  1. 新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值

  2. 就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行

  3. 运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态

  4. 阻塞状态:当处于运行状态的线程失去所占用资源之后,便进入阻塞状态

  5. 死亡:线程处理完任务,结束运行

多线程认知系列(二)之线程的生命周期及安全问题_第1张图片

线程的安全问题

由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题。
举个售票员卖100张票的栗子:

public class ThreadTicket {


    public static void main(String[] args) {
        ThreadDemo t1 = new ThreadDemo();
        t1.setName("售票员111");
        ThreadDemo t2 = new ThreadDemo();
        t2.setName("售票员222");
        ThreadDemo t3 = new ThreadDemo();
        t3.setName("售票员333");
        t1.start();
        t2.start();
        t3.start();


    }

}

class ThreadDemo extends Thread {

    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
                if (ticket > 0) {
                    try {
                        sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "售卖了:" + ticket);
                    ticket--;
                } else {
                    break;
                }
        }
    }
}

那么,我们运行main方法,会得到以下的结果
多线程认知系列(二)之线程的生命周期及安全问题_第2张图片
纳尼???竟然卖出了100票号两次,这什么鬼啊,我加了static了啊
那肯定是有问题的了,怎么解决呢?

解决线程安全问题

必须让一个线程操作共享数据完毕以后,其它线程才有机会参与共享数据的操作。

java实现线程安全:线程的同步机制

方式一:同步代码块

synchronized(同步监视器){
//需要被同步的代码块(即为操作共享数据的代码)
}
1、共享数据:多个线程共同操作的同一个数据(变量)
2、同步监视器:由任何一个类的对象来充当。哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁
注:在实现的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this

代码示例:

继承Thread方式同步代码块

class ThreadDemo extends Thread {

    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (ThreadDemo.class) {
                if (ticket > 0) {
                    System.out.println(getName() + "售卖了:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

实现方式

class RunnableTestDemo implements Runnable {

    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (RunnableTestDemo.class) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "售卖了:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

方式二:同步方法

将操作共享数据的方法声明为synchronized. 即此方法为同步方法,能够保证当其中一个线程执行此方法时,其他线程在外等待直至此线程执行完此方法。
同步方法的锁:this(不用显式的写) ,就是在方法上加个synchronized,相当于进入这个方法是单线程的。

class RunnableFunctionDemo implements Runnable {

    private static int ticket = 100;

    @Override
    public void run() {
        while (true) {
            checkTicket();
            if (ticket<=0){
                break;
            }
        }
    }

    private static synchronized void checkTicket(){
        if(ticket > 0) {
            try {
                sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket + "张票");
            ticket--;
        }
    }
}

方式三:Lock锁

主要是lock.lock() 跟 lock.unlock() 结合使用

public class Window {
    public static void main(String[] args) {
        WindowDemo demo = new WindowDemo();
        Thread t1 = new Thread(demo);
        t1.setName("t1++++");
        Thread t2 = new Thread(demo);
        t2.setName("t2++++");
        t1.start();
        t2.start();
    }
}


class WindowDemo implements Runnable {

    private static int ticket = 100;

    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (ticket > 0) {
                    if (ticket % 2 == 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName() + ",售票员出手了:" + ticket);
                } else {
                    break;
                }
                ticket--;
            } finally {
                lock.unlock();
            }
        }
    }
}

多线程认知系列(二)之线程的生命周期及安全问题_第3张图片

注意点:ReentrantLock() 参数默认false,如果为true,卖票窗口会交替卖票

总结:
synchronized 跟 lock的区别

相同点:都能解决线程的安全问题
不同点:synchronized 机制在执行完相应的同步代码以后,会自动的释放同步监视器
lock要手动开启同步,在结束的时候也要手动的关闭 unlock()

在实际的工作中,我使用的更多的是实现Runable接口实现自己的业务逻辑,当需要运行里面的一些数据的时候,使用callable,
使用synchronized的概率更大,比如生成流水号等等

那么,这篇文章就到这了,下一篇讲一下死锁问题,以及如何解决,系列文章还有3-4篇啦~

你可能感兴趣的:(多线程,多线程,并发编程,java,thread)