*阿炮的Java笔记013号*-多线程会引发安全问题,我们该如何解决?(包含火车票问题的代码)

196 阅读4分钟

老生常谈…对于大佬们根本都不用看的博客!

对于像我这种菜鸡还是得一步一步的来!如果有错误请大佬们指点指点!

线程安全

原因

多线程环境下操作同一个资源(成员变量),就会引发安全问题。

简单的解决思路

对于多条操作共享数据的语句,只能让一个线程都执行完,在执行的过程中,其他线程不参与

JDK如何解决线程安全?

JDK5之前

JAVA提供了专业的解决方法:同步机制

  • 同步代码块
synchronized (对象){ 
    // 需要被同步的代码
}
  • 同步方法
权限修饰符 synchronized 返回值类型 方法名(形参列表){
    // 需要被同步的代码
}

同步机制:在并发工作时,通过加上一个锁防止两个线程同时争夺相同的资源。这个资源被A线程访问,则加上一把锁,让B线程处于等待状态,当A线程出来的后,B才能进行处理。

synchronized 的锁是什么?

  • 任意对象都可以作为同步锁。所有对象都自动含有单一的锁
  • 同步方法和同步代码的锁:静态方法( 类名.class )、非静态方法( this )

注意:确保使用同一个资源的多个线程共用一把锁,否则会出现安全问题。

JDK5之后

显式定义同步锁对象来实现同步,通过Lock接口控制多个线程对共享资源进行访问。锁对共享资源进行了独占访问,每次只能有一个线程对Lock对象加锁。所以每当线程开始访问共享资源时。应先获得Lock对象。

ReentrantLock 类实现Lock接口,它可以显示的加锁和释放锁。

synchronized解决火车票问题

public class TicketSynchronized implements Runnable {
    private int tickets = 100;
    @Override
    public void run() {
        while (true) {
            //this.consume();
            //同步代码块
            synchronized (this) {
                if (this.tickets > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "出售第" + this.tickets-- + "张");
                } else {
                    break;
                }
            }
        }
    }

    //同步方法
    private synchronized void consume() {
        if (this.tickets > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "出售第" + this.tickets-- + "张");
        }
    }

    public static void main(String[] args) {
        TicketSynchronized ticket = new TicketSynchronized();
        Thread thread1 = new Thread(ticket,"A");
        Thread thread2 = new Thread(ticket,"B");
        Thread thread3 = new Thread(ticket,"C");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

Lock解决火车票问题

public class TicketLock implements Runnable{
    private final Lock lock = new ReentrantLock();
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            //加锁
            this.lock.lock();
            try {
                if (this.tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖第" + this.tickets-- + "张票");
                }
            } finally {
                // 解锁
                this.lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TicketLock ticket = new TicketLock();
        Thread thread1 = new Thread(ticket,"A");
        Thread thread2 = new Thread(ticket,"B");
        Thread thread3 = new Thread(ticket,"C");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

死锁问题与代码实现

死锁:

  • 一个共享资源需要两个不同的锁,但是这个两个锁分别被两个线程锁拥有,但是双发都不肯放弃这把锁,然后形成了僵持状态就是死锁
  • 出现死锁后,程序不会抛异常,不会有提示,所有的线程都处于阻塞状态,无法正常运行。

标准答案如何避免死锁:

  • 互斥条件:一个资源每次只能被一个进程使用
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

注意:破坏其中的一条就可以!

解决方案:

  • 减少同步资源的定义
  • 避免嵌套同步

代码实现如下所示:

public class DeadLock implements Runnable{


    private static final String A = "A";

    private static final String B = "B";

    private final boolean flag;

    public DeadLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        while (true) {
            if (this.flag) {
                synchronized (A) {
                    System.out.println("true线程获取A锁");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (B) {
                        System.out.println("true线程获取B锁");
                    }
                }
            } else {
                synchronized (B) {
                    System.out.println("false线程获取B锁");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    synchronized (A) {
                        System.out.println("false线程获取A锁");
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        DeadLock lock1 = new DeadLock(true);
        DeadLock lock2 = new DeadLock(false);
        Thread thread1 = new Thread(lock1);
        Thread thread2 = new Thread(lock2);
        thread1.start();
        thread2.start();
    }
}

Lock和Synchronized 总结

  • Lock 是显式锁(手动开启和关闭锁,别忘记关闭锁),Synchronized 是隐式锁,出了作用域自动释放。
  • Lock 只有代码块锁,Synchronized有代码块锁和方法锁。
  • 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好,有更好的扩展性(因为是接口可以实现很多类)。