多线程之 线程安全

一.线程安全出现原因:

  原因:  原本不应该拆开的两个步骤中间,被其他线程插足。

  解决方案:(java中的同步机制 [synchronized] 来解决),具体有下面三种
          a. 同步代码块
          b. 同步方法
          c. Lock接口
              创建:Lock lock = new ReentrantLock();
              霸占锁:lock();
              释放锁:unlock();

  代码演示线程安全问题:

public class Ticket implements Runnable{

    //定义一个成员变量,表示还有多少张票
    int num = 100;

    //要在run方法中定义卖票的任务
    @Override
    public void run() {
        //因为要一直卖票,所以使用死循环
        while (true) {
            //如果还有票,那么就卖票
            if(num > 0) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                }
                System.out.println(Thread.currentThread().getName() + "正在卖票:" + num);
                num--;
            }
        }
    }
}
public class Demo01TicketTest {
    public static void main(String[] args) {
        //创建三个线程,执行这个卖票任务
        Ticket t = new Ticket();
        //等同于创建了3个窗口,每个窗口都执行一样的任务:售票
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();

    }
}

   上面代码Demo01TicketTest,运行后可在控制台查看:

      1.出现了票 0 ,-1

      2.还有售出重复的票

  这就是多个线程会出现的线程安全问题,而在现实中这是不允许的

二.解决线程安全的同步代码块方法

  同步代码块: synchronized关键字可以用于方法中的某个区块中,表示支队这个区块的资源实行互斥访问

  格式: synchronized( 同步锁 ){

       需要同步操作的代码

     }

  同步锁: 对象的同步锁知识一个概念,就是个标记,(就好像狮子占地盘,撒了尿这就是我的地盘)

      1. 锁对象,可以是任意类型

      2. 多个线程对象,要使用同一把锁

  注意:  一个同步锁最多被一个线程锁拥有,谁拿到锁就可以进入代码块,其他的线程只能外面等待,等待同步锁被上一个线程释放后,再进行抢占cpu

  使用此方法解决,上面多线程售票安全问题

public class Ticket implements Runnable{

    int num = 100;

    //定义一个对象,这个对象表示锁对象
    //锁对象没有特殊的含义,只是做一个标记
    //多个线程必须使用同一个锁对象,否则不能保证线程安全。
    Object lock = new Object();

    @Override
    public void run() {
        while (true) {
            //当线程执行到synchronized这句代码的时候,会看一下这个上面还有没有锁
            //如果同步代码块上面还有锁,那么线程就获取到这个锁, 然后进入到同步代码块中。
            //如果同步代码块上面没有锁, 那么这个线程就一直在这个位置等着获取锁, 如果锁一直不来,那么就一直等着.
            synchronized (lock) {
                if(num > 0) {
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票:" + num);
                    num--;
                }
            }
            //当一个线程离开同步代码块,那么他会释放锁。
            //之后其他线程就可以去竞争这个锁,谁抢到了,那么谁就去执行这个同步代码块。
        }
    }
}

 三.解决线程安全的同步方法方法

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

  格式:  public synchronized void method(){

        可能会产生线程安全问题的代码

      }

  注意:  同步方法中也有同步锁

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

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

  使用同步方法代码解决售票的线程安全问题:

package cn.itcast.demo02;

@SuppressWarnings("all")
public class Ticket implements Runnable{
    int num = 100;
    @Override
    public void run() {
        while (true) {
            sell();
        }
    }

    //同步方法,表示对整个方法体都加上了同步代码块
    public synchronized void sell() {
        if(num > 0) {
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread().getName() + "正在卖票:" + num);
            num--;
        }
    }
}

 四.解决线程安全的Lock接口方法

  Lock锁: lock机制提供了比synchronized代码块和synchronized方法更加广泛的操作

       Lock,更加强大,更加体现面向对象

  Lock锁也成为同步锁,将加锁和释放所拆分两个方法,如下:

     public void lock(): 加同步锁

     public void unlock(): 释放同步锁

  使用该方法解决多线程售票安全问题

public class Ticket4 implements Runnable{
    int num = 100;
    //创建Lock对象
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            //调用lock对象的lock()方法,手动的获取锁
            lock.lock();
            if(num > 0) {
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                }
                System.out.println(Thread.currentThread().getName() + "正在卖票:" + num);
                num--;
            }
            //调用lock对象的unlock方法,手动的释放锁
            lock.unlock();
        }
    }
}

  

猜你喜欢

转载自www.cnblogs.com/xiangshaui/p/9582483.html