线程同步


线程同步

线程安全的问题

问题的提出

  • 多个线程执行的不确定性引起结果的不稳定

  • 多个线程对账本的共享,会造成操作的不完整性,会破坏数据

    image-20220730100751510

线程安全举例

多窗口卖票问题

public class WindowTest {
    public static void main(String[] args) {
        Window window = new Window();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);
        Thread thread3 = new Thread(window);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Window implements Runnable{

    private int ticket = 100;

    @Override
    public void run() {
        while (true){
            if (ticket > 0 ){
                System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

出现重票和错票

image-20220730102333439

原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也操作车票。

如何解决:当线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket时,其他线程才可以继续开始操作ticket。这种情况即使a出现阻塞,也不能被改变。在Java中,我们通过同步机制解决线程安全问题。

同步代码块

synchronized(同步监视器){
    //需要被同步的代码
}

说明

  1. 操作共享数据的代码,即为需要被同步的代码。

  2. 共享数据:多个线程共同操作的变量

  3. 同步监视器:俗称:。任何一个类的对象都可以充当锁。

    要求:多个线程必须共用同一把锁

同步代码块处理实现Runnable的线程安全问题

对象object作为锁,或者使用this关键字表示当前对象作为锁

class Window implements Runnable{

    private int ticket = 1000;
    Object object = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (this){//synchronized (object){
                if (ticket > 0 ){
                    System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}

同步代码处理继承Thread类的线程安全问题

因为创建了三个Window2()对象,所以要将ticket和object用static修饰保证三个线程共用一个锁和ticket。

更简单的可以使用类锁 Window2.class,保证三个线程共用一个锁

public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

    }
}

class Window2 extends Thread {

    private static int ticket = 100;

    private static Object object = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized(Window2.class){//synchronized (object) {
                try {
                    sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明成同步的

实现Runnable

public class WindowTest3 {
    public static void main(String[] args) {
        Window3 window3 = new Window3();
        Thread thread1 = new Thread(window3);
        Thread thread2 = new Thread(window3);
        Thread thread3 = new Thread(window3);
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class Window3 implements Runnable{

    private int ticket = 100;

    @Override
    public void run() {
        do {
            show();
        } while (ticket != 0);
    }

    private synchronized void show(){//同步监视器this
        if (ticket > 0 ){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":卖票,票号为:"+ticket);
            ticket--;
        }
    }
}

继承Thread

把同步方法声明成静态的,静态方法随着类的加载而加载

public class WindowTest4 {
    public static void main(String[] args) {

        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window4 extends Thread {

    private static int ticket = 100;

    @Override
    public void run() {
        do {
            show();
        } while (ticket != 0);
    }

    private static synchronized void show(){
        try {
            sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
    }
}

类锁和对象锁的区别

类锁是一个class其中的静态方法和静态变量在内存中只会加载和初始化一份,所以,一旦一个静态的方法被申明为synchronized,此类的所有的实例化对象在调用该方法时,共用同一把锁,称之为类锁。

类锁是加载类上的,而类信息是存在 JVM 方法区的,并且整个 JVM 只有一份,方法区又是所有线程共享的,所以类锁是所有线程共享的。

类锁属于一个类,类似静态变量;多个实例对象对应同一个锁

对象锁属于对象实例,类似类属性变量;每一个实例对象,对应一个锁


Author: qwq小小舒
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source qwq小小舒 !
  TOC