重学Java(044)——Java基础知识(线程安全、线程的生命周期)

一、线程安全概述

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。但在多线程的情况中往往会出现线程不安全的问题。我们通过一个电影院卖票的案例来模拟线程安全的情况。

案例描述:电影院卖100张票,三个售票口同时卖。

先定义一个类来模拟票:

public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int ticket = 100;

    // 重写run方法,设置线程任务:卖票
    @Override
    public void run() {
        // 每个窗口卖票的操作,窗口一直开启。使用循环,让卖票操作重复执行
        while (true) {
            // 先判断票是否存在
            if (ticket > 0) {
                // 提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 票存在,卖票 ticket--
                System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                ticket--; }
        }

    }
}

再定义测试类:

public class Demo01Ticket {

    public static void main(String[] args) {
        // 创建Runnable接口的实现类对象
        Runnable run = new RunnableImpl();
        // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t0 = new Thread(run,"1号窗口");
        Thread t1 = new Thread(run,"2号窗口");
        Thread t2 = new Thread(run,"3号窗口");
        // 调用start方法开启多线程,开始卖票
        t0.start();
        t1.start();
        t2.start();
    }
}

部分运行结果如下图:
在这里插入图片描述
从该运行结果中可以看出两个问题:

  1. 第4张票被卖了三次,每个窗口一次
  2. 出现了不存在的票——第0张票

上面的这种问题就成为线程不安全。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

二、线程同步

1.概述

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。

Java中解决多线程并发访问一个资源的安全性问题的办法是:同步机制(synchronized)。

以售票案例为例:窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

2.实现同步机制的三种方法

  • 1) 同步代码块
  • 2) 同步方法
  • 3) 锁机制

三、同步代码块

1.格式

synchronized(锁对象){
 	可能会出现线程安全问题的代码(访问了共享数据的代码)
}

2.使用

对模拟票的类进行改造:

public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int ticket = 100;

    // 创建一个锁对象   !*****!
    Object obj = new Object();


    // 设置线程任务:卖票
    @Override
    public void run() {
        // 使用循环,让卖票操作重复执行
        while (true) {
            // 创建一个同步代码块   !*****!
            synchronized (obj) {
                // 先判断票是否存在
                if (ticket > 0) {
                    // 提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
                }
            }

        }
    }
}

测试类不变。使用了同步代码块后,售票案例中的线程安全问题解决了。

四、同步方法

1.格式

修饰符 synchronized 返回值类型 方法名(参数列表){
        可能会出现线程安全问题的代码(访问了共享数据的代码)
        }

2.使用步骤

  • 1)把访问了共享数据的代码块抽取出来,放到一个方法中
  • 2)在方法上添加synchronized修饰符

3.使用

对模拟票的类进行改造:

public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int ticket = 100;

    // 创建一个锁对象
    Object obj = new Object();


    // 设置线程任务:卖票
    @Override
    public void run() {
        // 使用循环,让卖票操作重复执行
        while (true) {
            sellTicket();
        }
    }
    /*
        定义一个同步方法
        同步方法也会把方法内部的代码锁住
        只让一个线程执行
        同步方法的锁对象是谁?
        就是实现类对象new RunnableImpl()
     */
    public synchronized void sellTicket(){
        // 先判断票是否存在
        if (ticket > 0) {
            // 提高安全问题出现的概率,让程序睡眠
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 票存在,卖票 ticket--
            System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
            ticket--;
        }
    }

}

测试类不变。使用了同步方法后,售票案例中的线程安全问题解决了。

五、锁机制

1.概述
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock() :加同步锁。
  • public void unlock() :释放同步锁。

2.使用步骤

  • 1)在成员位置创建一个ReentrantLock对象
  • 2)在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
  • 3)在可能会出现安全问题的代码后调用Lock接口中的方法unlock获取锁

java.util.concurrent.locks.ReentrantLock implements Lock

ReentrantLock一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

3.使用

对模拟票的类进行改造:

public class RunnableImpl implements Runnable {
    // 定义一个多个线程共享的票源
    private int ticket = 100;

    // 1. 在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    // 设置线程任务:卖票
    @Override
    public void run() {
        // 使用循环,让卖票操作重复执行
        while (true) {

            // 2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            l.lock();

            // 先判断票是否存在
            if (ticket > 0) {
                // 提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                    // 票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    // 3. 在可能会出现安全问题的代码后调用Lock接口中的方法unlock获取锁
                    l.unlock(); // 无论程序是否异常,都会把锁释放
                }
            }
        }
    }
}

测试类不变。使用了锁机制后,售票案例中的线程安全问题解决了。

六、线程的生命周期

生命周期是指组件从创建到销亡的过程中,经历不同的阶段和状态
线程对象的生命周期一共有六种状态,当对线程对象调用不同的方法时,会引起线程状态间的转换

1. 新建状态—New

使用new关键字创建线程对象时的状态(已创建未启动)

2. 可运行状态—Runnable

调用start()方法启动线程后的状态,该状态对应操作系统中的就绪和运行两种状态

(1)就绪状态—start()方法内部调用本地方法start0(),操作系统会为该线程分配除CPU以外的所有资源
(2)运行状态—处于就绪状态的线程被线程调度器选中获取到了CPU执行权,正在执行run()方法

3. 阻塞状态—Blocked

线程由于未持有对象的锁定,而被阻挡在synchronized同步代码块外或同步方法外时的状态
一旦线程获取了对象的锁定,就会自动变为可运行状态

线程同步时,只有获取了锁的线程,才能进入同步代码块或同步方法中,操作共享资源数据

4. 等待状态—Waiting

线程执行了无参的join()方法或wait()方法后,会进入等待状态
直到所连接的线程执行结束或其它线程执行notify()或notifyAll()方法,线程会自动变为可运行状态

5. 计时等待状态—Timed_Waiting

线程执行了sleep()方法,有参的join()方法或wait()方法后,会进入计时等待状态
直到休眠时间结束,或所连接的线程执行结束,或其它线程执行notify()|notifyAll()方法,或达到了超时时间,线程会自动变为可运行状态

6. 终止状态—Terminated

线程run()方法中的代码正常执行完毕,或执行过程中抛出未捕获的异常退出后的状态


线程生命周期的状态转换图:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值