线程同步

线程安全

要保证线程安全有两个前提:

  1. 程序调用了多线程。
  2. 多个线程操作共同的变量

以上两个条件满足后,程序就有可能触犯线程不安全的问题

什么是线程不安全?

举例说明:假如一场演唱会需要售卖门票,有三个售票口,A,B,C。它们会同时售票,假如一共只有100张票,那么当100张票售卖完后,售票口就需要停止工作

以下是是实现代码

public class SaleTicket extends Thread {
    //统计剩余票数
    private int countticket = 100;

    @Override
    public void run() {
        while (true) {
            if (countticket <= 0) {
                break;
            } else {
                countticket--;
                System.out.println(getName() + "售票成功,剩余票数:" + countticket);
            }
        }
    }
}

class Test {
    public static void main(String[] args) {
        SaleTicket s1 = new SaleTicket();
        SaleTicket s2 = new SaleTicket();
        SaleTicket s3 = new SaleTicket();

        s1.setName("窗口A");
        s2.setName("窗口B");
        s3.setName("窗口C");
        //启用三条线程
        s1.start();
        s2.start();
        s3.start();
    }
}

运行结果如下:

image-20230321200027031

可以发现结果出现了重复的数据,这是由于这三个线程一直在反复抢占CPU的执行权导致的,解决办法有三个:

1. synchronized()

同步代码块

synchronized(任意对象) { 
	多条语句操作共享数据的代码 
}

此代码块的功能是锁住要执行的代码块,代码锁住后,其他线程即使抢到CPU的执行权,也不能进入锁定代码块进行执行,也就是说,同一时间锁里面只能有一条线程在操作,操作完后解锁。

修改后代码:

public class SaleTicket implements Runnable {
    //统计剩余票数
    private int countTicket = 100;
    //给代码加锁,同步代码块的锁对象可以是任何对象,但必须唯一
    private Object obj= new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (obj){
                if (countTicket <= 0) {
                    break;
                } else {
                    countTicket--;
                    System.out.println(Thread.currentThread().getName() + "售票成功,剩余票数:" + countTicket);
                }
            }
        }
    }
}

class Test {
    public static void main(String[] args) {
        SaleTicket s1 = new SaleTicket();
        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        Thread t3 = new Thread(s1);
        
        t1.setName("窗口A");
        t2.setName("窗口B");
        t3.setName("窗口C");
        //启用三条线程
        t1.start();
        t2.start();
        t3.start();
    }
}

结果如下:

image-20230321204026669

2. 同步方法
修饰符 synchronized 返回值类型 方法名(方法参数) { 
	方法体;
}

将要锁住的方法提取到方法中,并用synchronized修饰

public class SaleTicket implements Runnable {
    //统计剩余票数
    private int countTicket = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            boolean result = syMethod();
            if (result) {
                break;
            }
        }
    }
	//同步方法
    private synchronized boolean syMethod() {
        if (countTicket <= 0) {
            return true;
        } else {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            countTicket--;
            System.out.println(Thread.currentThread().getName() + "售票成功,剩余票数:" + countTicket);
            return false;
        }
    }
}

class Test {
    public static void main(String[] args) {
        SaleTicket s1 = new SaleTicket();
        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        Thread t3 = new Thread(s1);

        t1.setName("窗口A");
        t2.setName("窗口B");
        t3.setName("窗口C");
        //启用三条线程
        t1.start();
        t2.start();
        t3.start();
    }
}
3.Lock

在JDK5以后新增了一个锁对象Lock, Lock是接口不能实例化,所有调用它的实现类ReentrantLock来实例化

public class SaleTicket implements Runnable {
    //统计剩余票数
    private int countTicket = 100;
    //实例化一个ReentrantLock对象
    private ReentrantLock rtl = new ReentrantLock();

    @Override
    public void run() {
        while (true) {

            try {
                rtl.lock();
                if (countTicket <= 0) {
                    break;
                } else {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    countTicket--;
                    System.out.println(Thread.currentThread().getName() + "售票成功,剩余票数:" + countTicket);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                rtl.unlock();
            }

        }
    }
}

class Test {
    public static void main(String[] args) {
        SaleTicket s1 = new SaleTicket();
        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        Thread t3 = new Thread(s1);

        t1.setName("窗口A");
        t2.setName("窗口B");
        t3.setName("窗口C");
        //启用三条线程
        t1.start();
        t2.start();
        t3.start();
    }
}