多线程----线程同步

552 阅读3分钟

1: 线程概述

在了解线程之前我们先要知道什么是进程。

进程:执行中的程序。

线程:进程的执行单元,线程依靠进程运行,并且只能使用分配给线程的资源

由于JAVA虚拟机采用抢占式调度模式,指根据线程优先级选择线程占用cpu,如优先级相同,则随机选择一个线程,使其占用cpu。如果是单线程,我们自然不用考虑这个问题。但是如果是多线程时,就会出现不同的线程之间抢夺cpu的情况。为了应付这种情况,我们引入线程同步。

线程同步:线程同步,通俗来说就是线程同步就是一种等待机制,多个需要同时访问此对象的[线程池]进入这个对象的等待池形成队列,等待前面的线程使用完成,下一个线程再使用。

2:线程同步的具体使用

public class selltricket implements Runnable {
    private int tricket = 100;
   
    @Override
    public void run() {
//        相同的票出现多次
//        休眠时其他线程抢夺cpu的执行权
        while (true) {
//            同步代码块
            synchronized (this){
            if (tricket > 0) {
//                模拟出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tricket + "票");
                tricket--;
            }
            }
            }
        }
    }
public class 卖票 {
    public static void main(String[] args) {
        selltricket s1=new selltricket();

        Thread t1=new Thread(s1,"窗口一");
        Thread t2=new Thread(s1,"窗口二");
        Thread t3=new Thread(s1,"窗口三");

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

我们先创建sellstricket类,然后在卖票类中创建sellstricket对象,执行线程。

3: 具体分析

为了达到线程同步的目的,我们在sellstricket类中使用sleep()方法模拟出票时间,这样每个线程执行后都会出现一定时间的停滞,这时下一线程就会抢夺cpu,完成规律的线程执行。

    private int tricket = 100;

//                模拟出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

4: 同步代码块解决线程安全问题

事实上,如果按照上面的代码运行,结果会出现一些不和逻辑的错误。 到的结果会是,三条语句的ticket都相同,然后ticket突然减三,接着又输出三条ticket相同的输出语句。

那么,该如何解决这种情况呢? 这种延迟卖票的问题被称为线程安全问题,要发生线程安全问题需要满足三个条件(任何一共条件不满足都不会造成线程安全问题):

是否存在多线程环境

是否存在共享数据/共享变量

是否有多条语句操作着共享数据/共享变量

由于前两个条件是多线程的必备条件,所以为了解决问题,就必须破坏条件三。

解决办法:

  1. synchronized —— 自动锁
  2. lock —— 手动锁

5: 自动锁

public class selltricket implements Runnable {
    private int tricket = 100;
    private Object OBJ=new Object();

    @Override
    public void run() {
//        相同的票出现多次
//        休眠时其他线程抢夺cpu的执行权
        while (true) {
//            同步代码块
            ;synchronized (this){
            if (tricket > 0) {
//                模拟出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tricket + "票");
                tricket--;
            }
            }
            }
        }
    }

synchronized(任意对象键):相当于给代码加锁了,任意对象就可以看成是一把锁 这样就能把多条操作共享数据的代码锁起来,让任意时刻只能有一个线程执行。

6:手动锁

public class selltricket implements Runnable {
    private int tricket = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
//        相同的票出现多次
//        休眠时其他线程抢夺cpu的执行权
        while (true) {
            try {
                lock.lock();
                if (tricket > 0) {
//                模拟出票时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tricket + "票");
                    tricket--;
                }
            }finally {
                lock.unlock();
            }
//
        }
    }

}

Lock锁可以更加清晰的表达如何加锁和释放锁,当Lock是接口不能直接实例化,所以我们要采用它 实现类Reentrantlock来实例化

7:总结

以上就是线程同步方法与解决同步代码块解决线程安全问题的方法。