原创

Java多线程基础(三)


前言

上一篇文章《Java多线程基础(二)》整理了线程常用API,本篇继续整理一下线程安全问题及常用线程锁实现。


线程安全问题

Java允许多线程并发控制,当多个线程同时对一个可共享的资源变量做读写操作时,可能会发生数据不一致的情况,这时,线程就是不安全的。举个例子,火车站的售票窗口,两个窗口同时对余票进行查询,查询到剩余车票一张,然后同时出票并减少余票数量,此时余票数量就变成了-1,这明显是不合理的。代码示例:

class myThread implements Runnable{ //售票窗口类
    //剩余车票数量
    private int tickets = 10;
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            try { //线程暂停1毫秒,模拟耗时操作,增加车票超售几率
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sellTicket();
        }
    }
    //封装售票方法
    private  void sellTicket() {
        if(tickets>0) {
            System.out.println(Thread.currentThread().getName() + "售出车票,剩余车票" + (--tickets) + "张!");
        }
    }
}

public class ThreadUnsafetyDemo {
    public static void main(String[] args) {
        Runnable mr = new myThread();
        Thread t1 = new Thread(mr,"售票窗口1");
        Thread t2 = new Thread(mr,"售票窗口2");
        Thread t3 = new Thread(mr,"售票窗口3");
        t1.start();
        t2.start();
        t3.start();
    }

}
输出结果:
售票窗口1售出车票,剩余车票8张!
售票窗口2售出车票,剩余车票7张!
售票窗口3售出车票,剩余车票9张!
售票窗口2售出车票,剩余车票6张!
售票窗口1售出车票,剩余车票5张!
售票窗口3售出车票,剩余车票4张!
售票窗口3售出车票,剩余车票3张!
售票窗口2售出车票,剩余车票2张!
售票窗口1售出车票,剩余车票1张!
售票窗口2售出车票,剩余车票0张!
售票窗口3售出车票,剩余车票-1张!
售票窗口1售出车票,剩余车票-2张! //余票出现负数,车票超售

如上所示,在多线程环境下,当涉及到全局资源共享时,经常会出现数据不一致的问题;此时,我们可以通过Java的锁机制来解决问题,例如在方法上增加synchronized关键字,同时只允许一个线程访问,示例:

//在售票方法上加上 synchronized 关键字
private synchronized void sellTicket() {
        if(tickets>0) {
            System.out.println(Thread.currentThread().getName() + "售出车票,剩余车票" + (--tickets) + "张!");
        }
    }
输出结果:
售票窗口2售出车票,剩余车票9张!
售票窗口3售出车票,剩余车票8张!
售票窗口1售出车票,剩余车票7张!
售票窗口2售出车票,剩余车票6张!
售票窗口1售出车票,剩余车票5张!
售票窗口3售出车票,剩余车票4张!
售票窗口1售出车票,剩余车票3张!
售票窗口2售出车票,剩余车票2张!
售票窗口3售出车票,剩余车票1张!
售票窗口1售出车票,剩余车票0张!//车票正常出售

Java提供了多种多线程锁机制的实现方式,常见的有synchronized、ReentrantLock等,每种机制都有优缺点与各自的适用场景,必须熟练掌握他们的特点才能在Java多线程应用开发时得心应手。

......未完待续

正文到此结束