Java 多线程之线程安全-2

在这里插入图片描述

1线程安全问题

如果有多个线程同时运行,而这些线程可能会同时运行这段代码,程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值
和预期的是一样的,就是线程安全的

!!!举个卖票的例子!!!

在这里插入图片描述

代码

public class RunnableImpl implements Runnable {

    // 定义一个多个线程共享票源
    private int ticket = 100;

    /**
     * 卖票
     */
    @Override
    public void run() {
        while (true) {
            // 先判断票是否存在
            if (ticket > 0) {
                // 提高出现问题的概率
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 存在
                System.out.println(Thread.currentThread().getName() + "--->正在卖票" + ticket + "张票");
                ticket--;
            }
        }
    }
}
public class DemoTicket {

    public static void main(String[] args) {

        RunnableImpl run = new RunnableImpl();

        // 创建三个线程共同卖票
        new Thread(run,"窗口-1").start();
        new Thread(run,"窗口-2").start();
        new Thread(run,"窗口-3").start();

    }
}

!!!出现了2类线程安全问题!!!
在这里插入图片描述
在这里插入图片描述

问题1——分析出现负票原因

  • Thread-2抢到cpu时间片,进入到run方法执行if之后,进入程序睡眠,放弃CPU执行权,Thread-1和Thread-0均是如此
  • Thread-2线程醒来,执行ticket– 此时ticket=0,再一次循环 ticket不在大于0,线程Thread-2结束
  • Thread-1线程醒来,此时ticket为0,进行ticket–,ticket为-1,再一次循环 ticket不大于0,线程Thread-1结束
  • Thread-0线程醒来,此时ticket为-1,进行ticket–,ticket为-2,再一次循环 ticket不大于0,线程Thread-1结束

问题2——分析出现相同票原因

  • 三个线程同时执行到了打印那句话,而此时ticket并没有开始自减,所以打印了重复的票

解决线程安全问题

  • 保证让一个线程在访问共享资源时候,无论是否失去了CPU时间片,其他线程只能等待
  • 等待当前线程拍完票,其他线程在进行卖票

2.线程同步

  • Java种提供同步机制(Synchronized)来解决
  • 线程1操作时,线程2和3等待,线程1操作结束,线程1和2和3在去竞争CPU时间片来执行线程

三种方式同步操作:

1.同步代码块

synchronized关键字可以用于方法种某个区块中,表示只对这个区块的资源实行互斥访问
对象同步锁只是一个概念,想象在对象上标记一个锁,锁对象可以是任意类型,多个线程对象要使用同一把锁

synchronized(同步锁){
	需要同步的操作
}

!!!上代码!!!

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

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

    /**
     * 卖票
     */
    @Override
    public void run() {
        while (true) {
            // 创建同步代码块
            synchronized (obj) {
                // 先判断票是否存在
                if (ticket > 0) {
                    // 存在
                    System.out.println(Thread.currentThread().getName() + "--->正在卖票" + ticket + "张票");
                    ticket--;
                }
            }
        }
    }
}
public class DemoTicket {

    public static void main(String[] args) {

        RunnableImpl run = new RunnableImpl();

        // 创建三个线程共同卖票
        new Thread(run, "窗口-1").start();
        new Thread(run, "窗口-2").start();
        new Thread(run, "窗口-3").start();

    }
}

!!!执行结果!!!
在这里插入图片描述

同步技术原理:

使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
三个线程一起抢夺CPU执行权,谁抢到了谁执行run方法进行减票
窗口-1抢到了执行权,执行run方法,遇到synchronized代码块,并检查代码块是否有锁对象发现有,获取锁对象并进入到同步中执行
窗口-2抢到了执行权,执行run方法,遇到synchronized代码块,并检查代码块是否有锁对象发现没有,线程进入阻塞会一直等待窗口-1执行完同步中的代码并归还对象锁时,窗口-2才能获取对象锁并进入同步中执行

总结: 同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步方法,程序中频繁判断锁,获取锁,释放锁,效率降低

2.同步方法

使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法时,其他线程在方法外等待

public synchronized void method(){
	// 可能会产生线程安全问题的代码
}

同步锁是谁?
对于非static方法,同步锁就是this
对于static方法,我们使用当前方法所在的类字节码对象(类名.class)

public class RunnableImpl implements Runnable {

    // 定义一个多个线程共享票源
    private int ticket = 100;

    /**
     * 卖票
     */
    @Override
    public void run() {
        while (true) {
            updateTicket();
        }
    }
    // 同步方法
    public synchronized void updateTicket() {
        // 先判断票是否存在
        if (ticket > 0) {
            // 存在
            System.out.println(Thread.currentThread().getName() + "--->正在卖票" + ticket + "张票");
            ticket--;
        }
    }
}
public class DemoTicket {

    public static void main(String[] args) {

        RunnableImpl run = new RunnableImpl();

        // 创建三个线程共同卖票
        new Thread(run, "窗口-1").start();
        new Thread(run, "窗口-2").start();
        new Thread(run, "窗口-3").start();

    }
}

!!!执行结果!!!
执行结果
结果: 结果依旧不变

3.锁机制(Lock锁)

java.util.concurrent.locks.Lock
Java提供了比同步代码块和同步方法更广泛的锁定操作
Lock锁称为同步锁,加锁与释放锁方法如下

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

Lock接口提供了多个实现
锁对象实现

!!!上代码!!!

public class RunnableImpl implements Runnable {

    // 定义一个多个线程共享票源
    private int ticket = 100;
	
	// Lock接口获取锁对象  多态
    Lock lock = new ReentrantLock();

    /**
     * 卖票
     */
    @Override
    public void run() {
        while (true) {
            // 获取锁
            lock.lock();
            try {
                updateTicket();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                // 无论业务方法是否发生异常 都释放锁
                lock.unlock();
            }
        }
    }

    /**
     * 使用Lock 调用业务方法
     */
    public void updateTicket() throws Exception {
        // 先判断票是否存在
        if (ticket > 0) {
            // 存在
            System.out.println(Thread.currentThread().getName() + "--->正在卖票" + ticket + "张票");
            ticket--;
        }
    }
}

!!!查看结果!!!

在这里插入图片描述
!!!结果依然正确!!!

发布了9 篇原创文章 · 获赞 9 · 访问量 567

猜你喜欢

转载自blog.csdn.net/weixin_41241629/article/details/104179113