zoukankan      html  css  js  c++  java
  • 解决线程不安全问题

    更多精彩文章欢迎关注公众号“Java之康庄大道”

    当多个线程并发访问同一个资源对象时,可能会出现线程不安全的问题,比如现有100个高铁座位,现在有请三个窗口(A,B,C)同时售票.,此时使用多线程技术来实现这个案例.

    package com.yunqing.ssm.test;
    
    /**
     * 存在线程安全问题
     */
    public class TestRunnable {
    
        public static void main(String[] args) {
            Ticket tick = new Ticket();
    
            Thread t1 = new Thread(tick,"A窗口");
            Thread t2 = new Thread(tick,"B窗口");
            Thread t3 = new Thread(tick,"C窗口");
    
            t1.start();
            t2.start();
            t3.start();
        }
    
    
    
    }
    
    class Ticket implements Runnable{
    
        int ticket = 100;
    
        @Override
        public void run() {
    
            while (true){
                
                try {
                    //模拟网络延迟
                    Thread.currentThread().sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    if (ticket>0)
                        System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
    
            }
    
        }
    
    }

    以上代码运行结果:

    为什么编号为84的座位号被3个窗口售出了?

    当A窗口打印84座位号,还没打印完的时候,其他两个线程就也进入到了84号座位票的分配操作中,所以导致线程安全问题。

    要解决上述多线程并发访问多一个资源的安全性问题,就必须得保证打印座位号和座位号总数减1操作,必须同步完成.即是说,A线程进入操作的时候,BC线程只能在外等着,A操作结束,ABC才有机会进入代码去执行.

    解决多线程并发访问资源的安全问题,有三种方式:

    方式1:同步代码块

    方式2:同步方法

    方式3:锁机制(Lock)

     

     

    方式1:同步代码块

    语法:

    synchronized(同步锁)

    {

         需要同步操作的代码

    }

     

    同步锁:

    为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制.也称为同步监听对象/同步锁/同步监听器/互斥锁。

    实际上,对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁,谁拿到锁,谁就可以进入代码块,其他线程只能在代码块外面等着,而且注意,在任何时候,最多允许一个线程拥有同步锁.

    Java程序运行可以使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象.

    package com.yunqing.ssm.test;
    
    /**
     * 存在线程安全问题
     */
    public class TestRunnable {
    
        public static void main(String[] args) {
            Ticket tick = new Ticket();
    
            Thread t1 = new Thread(tick,"A窗口");
            Thread t2 = new Thread(tick,"B窗口");
            Thread t3 = new Thread(tick,"C窗口");
    
            t1.start();
            t2.start();
            t3.start();
        }
    
    
    
    }
    
    class Ticket implements Runnable{
    
        int ticket = 100;
    
        @Override
        public void run() {
    
            while (true){
    
                try {
                    //模拟网络延迟
                    Thread.currentThread().sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //this代表Ticket对象,Ticket对象是多线程共享资源
                synchronized (this){
                    if (ticket>0)
                        System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--);
                }
    
    
            }
    
        }
    
    }
    

      方式2:同步方法:

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

    Synchronized public void doWork(){

         ///TODO

    }

    同步锁是谁:

          对于非static方法,同步锁就是this.  

          对于static方法,我们使用当前方法所在类的字节码对象(Ticket.class).

    package com.yunqing.ssm.test;

    /**
    * 存在线程安全问题
    */
    public class TestRunnable {

    public static void main(String[] args) {
    Runnable a = new Ticket();
    new Thread(a,"A窗口").start();
    new Thread(a,"B窗口").start();
    new Thread(a,"C窗口").start();

    }



    }

    class Ticket implements Runnable{

    int ticket = 100;

    synchronized private void doWork() throws InterruptedException {

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

    }

    @Override
    public void run() {

    for (int i=1;i<=100;i++){
    try {
    doWork();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }



    }

    }

    注意:

    不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能好比是多个线程出现串行.

     

    解决方案:把需要同步操作的代码定义在一个新的方法中,并且该方法使用synchronized修饰,再在run方法中调用该新的方法即可.

     

    实际上,同步代码块和同步方法差不了多少,在本质上是一样的,两者都用了一个关键字synchronizedsynchronized保证了多线程并发访问时的同步操作,避免线程的安全性问题,但是有一个弊端,就是使用synchronized的方法/代码块的性能比不用要低一些,因此如果要用synchronized,建议尽量减小synchronized的作用域。

     

    方式3:同步锁(锁机制)

     

    Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象.

    
    
    package com.yunqing.ssm.test;

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;

    /**
    * 存在线程安全问题
    */
    public class TestRunnable {

    public static void main(String[] args) {
    Runnable a = new Ticket();
    new Thread(a,"A窗口").start();
    new Thread(a,"B窗口").start();
    new Thread(a,"C窗口").start();

    }



    }

    class Ticket implements Runnable{

    int ticket = 100;

    //创建锁对象
    private final Lock lock = new ReentrantLock();

    private void doWork(){
    //进入方法,立马加锁
    lock.lock();


    try {

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


    } catch (InterruptedException e) {
    e.printStackTrace();
    }finally {
    lock.unlock();
    }


    }

    @Override
    public void run() {

    for (int i=1;i<=100;i++){
    doWork();
    }



    }

    }
    
    
  • 相关阅读:
    Java自学-数字与字符串 字符串
    Java自学-数字与字符串 格式化输出
    Java自学-数字与字符串 数学方法
    Java自学-数字与字符串 字符串转换
    Java自学-数字与字符串 装箱和拆箱
    Java自学-接口与继承 UML图
    Mysql优化系列之查询优化干货1
    Mysql优化系列之查询性能优化前篇3(必须知道的几个事实)
    Mysql优化系列之查询性能优化前篇2
    第二十二篇:Spring简单定时任务
  • 原文地址:https://www.cnblogs.com/yunqing/p/9277494.html
Copyright © 2011-2022 走看看