线程的同步

问题的提出

  • 多个线程执行的不确定性引起执行结果的不稳定

  • 多个线程对数据的共享,会造成操作的不完整性,破坏数据

 

         如上图,媳妇和同时从银行取3000元,银行业务逻辑中肯定会判断当前存款额和要取款额大小,因为媳妇与我两个线程是同时进入判断条件,因此都满足存款额 > 取款额。然后都成功取到了3000元,银行便亏了1000元。

        这种情况当然是不合理的,正常情况下是媳妇或者我先取3000元,然后存款还剩下2000元,此时另外一个人再次去取3000元时,便不满足存款额 > 取款额,取款失败。但是在高并发情况下,这种情况发生的概率就大大增加了。因此,线程的同步是非常重要的。

        若对共享数据(此处为存款¥5000)不加以保护,那么多个线程对其进行操作的时候就容易发生错误。

 解决线程同步问题

引入售票案例:

        总票数为100,有三个线程共享总票数,同时进行卖票,若线程没有进行同步,则可能会出现重票、错票的现象。

测试代码:

public class TicketWindowBySyn {
    public static void main(String[] args) {
        Window1 window1 = new Window1();
        Thread t1 = new Thread(window1);
        Thread t2 = new Thread(window1);
        Thread t3 = new Thread(window1);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

}

class Window1 implements Runnable{
    private int ticketNum = 100; //总票数

    /**
     * 重写run方法,改为售票业务
     */
    @Override
    public void run() {
        while(true) {
            //让线程sleep一下,增加重票、错票的概率
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if(ticketNum > 0) {
                System.out.println(Thread.currentThread().getName() + ": 售票,票号:" + ticketNum);
                ticketNum--;
            }else {
                break;
            }
        }
    }
}

    我们现在看看未进行线程同步的运行结果:

     那么,我们如何进行线程的同步呢?

方式一:同步代码块


//同步监视器(即锁)
synchronized(同步监视器) {
  //需要被同步的代码
}

说明

  • 同步监视器:俗称为锁,任何一个类的对象都可以充当锁,但是多个线程必须要共同一把锁。

  • 在实现Runnable接口创建多线程的方式中,我们可以考虑使用 this 充当同步监视器

  • 在继承Tread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器

    总而言之,要看清楚,充当同步监视器的类必须是不变的。

利用同步代码块,对上述run()方法进行重写

 @Override
    public void run() {
        while(true) {
            synchronized (this) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if (ticketNum > 0) {
                    System.out.println(Thread.currentThread().getName() + ": 售票,票号:" + ticketNum);
                    ticketNum--;
                } else {
                    break;
                }
            }
        }
    }

方式二:同步方法

        如果操作共享数据的代码完整地放在了一个方法中,我们不妨将此方法声明为同步的。

  • 同步方法仍然涉及到同步监视器,只是不需要我们显式地声明

    • 非静态的同步方法,同步监视器是:this

    • 静态的同步方法,同步监视器是:当前类本身

@Override
    public void run() {
        while(true) {
                sellTicket();
        }
    }

    //同步方法
    private synchronized void sellTicket() {

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (ticketNum > 0) {
            System.out.println(Thread.currentThread().getName() + ": 售票,票号:" + ticketNum);
            ticketNum--;
        }
    }

方式三:Lock锁(jdk5.0新增)


class Window implements Runnable{
    private int ticketNum = 100;
    //实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    /**
     * 重写run方法,改为售票业务
     */
    @Override
    public void run() {
        while(true) {

            try {

                lock.lock();

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                if(ticketNum > 0) {
                    System.out.println(Thread.currentThread().getName() + ": 售票,票号:" + ticketNum);
                    ticketNum--;
                }else {
                    break;
                }

            } finally {
                lock.unlock();
            }

        }
    }
}

        线程同步后,代码运行效果:

 

     总结:同步的方式:解决了线程安全问题,操作同步代码时,只能有一个线程参与,相当于是一个单线程的过程,效率低。

线程的单例模式之懒汉式


/**
 * 线程安全的懒汉式单例模式
 */
class Bank{
    private Bank(){
    }

    private static Bank instance = null;

    public static synchronized Bank getInstance() {
        if(instance == null){
            instance = new Bank();
        }
        return instance;
    }
}

线程的死锁问题

  • 死锁

    • 不同的线程分别占用对方需要的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

    • 出现死锁后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续。

  • 解决方法

    • 专门的算法、原则

    • 尽量减少同步资源的定义

    • 尽量避免嵌套同步

面试题:synchronized与Lock的异同?

  1. Lock属于显式锁(手动开启和关闭锁),是隐式锁,出了作用域自动释放

  2. Lock只有代码块锁,synchronized有代码块锁和方法锁

  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的拓展性(提供更多子类)

优先使用顺序:

Lock > 同步代码块 > 同步方法

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值