原创

Java入门---线程安全


1.问题引出

avatar

1个窗口卖100张票没有问题,单线程是不会出现线程安全问题的。

avatar

3个窗口一起卖票,卖的票不同,也不会出现问题,多线程程序,没有访问共享数据,不会产生问题。

avatar

3个窗口卖同样的票,就出现线程安全问题,多线程访问了共享数据,会产生安全问题,比如:出现重复的票和不存在的票。

package Multithreading;

class RunnableImpl1 implements Runnable {
    private int ticket = 10;

    @Override
    public void run() {
        while (true){
            if(ticket > 0){
                // 故意提高安全问题出现的概率
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖票" + ticket + "张票");
                ticket--;
            }
        }
    }
}

public class Demo06 {
    public static void main(String[] args) {
        RunnableImpl1 r = new RunnableImpl1();

        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);

        t1.start();
        t2.start();
        t3.start();
    }
}
Thread-2正在卖票10张票
Thread-1正在卖票10张票
Thread-0正在卖票10张票
Thread-2正在卖票7张票
Thread-1正在卖票6张票
Thread-0正在卖票5张票
Thread-1正在卖票4张票
Thread-2正在卖票3张票
Thread-0正在卖票2张票
Thread-2正在卖票1张票
Thread-1正在卖票0张票
Thread-0正在卖票-1张票

【说明】:10张票出现了3次,有的票没有出现,还出现了不存在的票,由此可见线程安全是不可以忽略的问题。

2.线程安全问题解决思路

【线程安全问题出现的原因】:某个共享资源在同一时刻被不同线程所访问。当多个线程并发访问同一个内存地址并且内存地址保存的值是可变的时候可能会发生线程安全问题。

【解决思路】:我们可以让一个线程在访问共享数据的时候,无论是否是去了CPU的执行权,让其他线程只能等待,等待当前线程完成卖票后,其他线程才可以卖票。

3.线程同步技术01--同步代码块

package Multithreading;
/*
    解决线程安全问题:
        方法一:同步代码块
*/
class RunnableImpl2 implements Runnable {
    private int ticket = 10;

    // 创建一个锁对象
    Object obj = new Object();

    @Override
    public void run() {
        while (true){
            // 同步代码块
            synchronized (obj){
                if(ticket > 0){
                    // 故意提高安全问题出现的概率
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票" + ticket + "张票");
                    ticket--;
                }
            }
        }
    }
}

public class Demo07 {
    public static void main(String[] args) {
        RunnableImpl2 r = new RunnableImpl2();

        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);

        t1.start();
        t2.start();
        t3.start();
    }
}
Thread-0正在卖票10张票
Thread-0正在卖票9张票
Thread-1正在卖票8张票
Thread-1正在卖票7张票
Thread-2正在卖票6张票
Thread-0正在卖票5张票
Thread-2正在卖票4张票
Thread-2正在卖票3张票
Thread-2正在卖票2张票
Thread-1正在卖票1张票

【说明】:(1)代码块中的锁对象,可以使用任意的对象。(2)必须保证多个线程使用的锁对象是同一个。(3)锁对象(同步锁)的作用:把同步代码块锁住,只能让一个线程在同步代码块中执行。

【核心】:同步中的线程没有执行完毕,不会释放锁;同步外的线程没有锁,进不去同步。

【缺点】:程序频繁的判断锁,获取锁,释放锁,程序的效率会降低。

4.线程同步技术02--同步方法

package Multithreading;
/*
    解决线程安全问题:
        方法二:同步方法
*/
class RunnableImpl3 implements Runnable {
    private int ticket = 100;

    @Override
    public void run() {
        while (true){
           payTicket();
        }
    }
    
    // 同步方法
    public synchronized void payTicket(){
        if(ticket > 0){
            // 故意提高安全问题出现的概率
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖票" + ticket + "张票");
            ticket--;
        }
    }
}

public class Demo08 {
    public static void main(String[] args) {
        RunnableImpl3 r = new RunnableImpl3();

        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);

        t1.start();
        t2.start();
        t3.start();
    }
}
Thread-0正在卖票10张票
Thread-0正在卖票9张票
Thread-0正在卖票8张票
Thread-1正在卖票7张票
Thread-1正在卖票6张票
Thread-2正在卖票5张票
Thread-2正在卖票4张票
Thread-0正在卖票3张票
Thread-1正在卖票2张票
Thread-0正在卖票1张票

【说明】:同步方法也有锁,它的锁就是当前调用该方法的对象,也就是this指向的对象。但是,有的时候同步方法是静态方法,静态方法不需要创建对象就可以直接调用,那么它的同步锁是什么?静态方法的锁是该方法所在类的class对象,该对象可以直接用“类名.class”的方式获取。

// 静态方法
    public static  void payTicket(){
        synchronized (RunnableImpl4.class) { //类名.class
            if(ticket > 0){
                // 故意提高安全问题出现的概率
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖票" + ticket + "张票");
                ticket--;
            }
        }
    }

5.线程同步技术03--LOCK锁机制

package Multithreading;

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

class RunnableImpl5 implements Runnable {
    private int ticket = 10;

    // 1.创建ReentrantLock()锁对象
    Lock l = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            // 2.在可能出现安全问题代码前获取锁
            l.lock();

            if(ticket > 0){
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "正在卖票" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 3.在可能出现安全问题代码前获取锁
                    l.unlock();
                }
            }
        }
    }
}

public class Demo10 {
    public static void main(String[] args) {
        RunnableImpl5 r = new RunnableImpl5();

        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        Thread t3 = new Thread(r);

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

Thread-0正在卖票10张票
Thread-0正在卖票9张票
Thread-0正在卖票8张票
Thread-1正在卖票7张票
Thread-1正在卖票6张票
Thread-2正在卖票5张票
Thread-1正在卖票4张票
Thread-0正在卖票3张票
Thread-1正在卖票2张票
Thread-0正在卖票1张票

【说明】:避免出现死锁问题!!!

Java
  • 作者:李延松(联系作者)
  • 发表时间:2020-07-23 15:51
  • 版本声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 公众号转载:请在文末添加作者公众号二维码

评论

留言