多线程的安全问题synchronized线程同步

时间:2024-4-3    作者:老大夫    分类: JAVA


synchronized通常作为本地锁,分布式项目下要用分布式锁。

线程的安全问题与线程的同步机制
类似于数据库的脏读幻读的情况

  1. 卖票时出现了重复卖票和错票的情况
  2. 什么原因导致的?在线程处理未结束的情况下其他线程参与进来,同样对票数进行了操作。
  3. 如何解决?需要在线程a操作票数的情况下,其他线程必须等待,直至a操作结束。
  4. Java是如何解决线程安全问题的?使用线程的同步机制

方式一:同步代码块

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

说明:
需要同步的代码,即为操作共享数据代码
共享数据即为多个线程需要操作的数据,例如tiket票数
需要被操作的代码被synchronized包裹后,一个线程操作时其他线程必须等待
同步监视器俗称,哪个线程获得了锁即获得了操作数据的权力
同步监视器,可以用任意一个对象充当,但是注意所有线程共用一个锁
比喻:上厕所锁门要加锁,如果没有锁,后面一个人同时进来,两个人上同一个厕所不安全。

Runnable接口实现使用synchronized

public class sale {
    public static void main(String[] args) {
        salePic saller1=new salePic();
        Thread t1=new Thread(saller1);
        Thread t2=new Thread(saller1);
        Thread t3=new Thread(saller1);
        t1.start();
        t2.start();
        t3.start();
    }
}
class salePic implements Runnable{
    int total=1000;
    @Override
    public void run() {
        while(total>0){
        //this表示调用该方法的对象,这里指saller1确定是唯一的
        synchronized (this){
                if (total>0){
                    System.out.println(Thread.currentThread().getName()+"售出第"+total+"张票");
                    total-=1;
                }
            }
        }
    }
}

Thread继承使用synchronized

public class sale2 {
    public static void main(String[] args) {
        salePic2 s1=new salePic2();
        salePic2 s2=new salePic2();
        salePic2 s3=new salePic2();
        s1.start();
        s2.start();
        s3.start();
    }
}
class salePic2 extends Thread {
    int total=1000;
    @Override
    public void run() {
        while(total>0){
        //在继承Thread类方式中同步监视器慎用,这里使用Window.class
        synchronized (Window.class){
                if (total>0){
                    System.out.println(Thread.currentThread().getName()+"售出第"+total+"张票");
                    total-=1;
                }
            }
        }
    }
}

方式二:同步方法

Runnable接口实现使用synchronized方法

public class sale {
    public static void main(String[] args) {
        salePic saller1=new salePic();
        Thread t1=new Thread(saller1);
        Thread t2=new Thread(saller1);
        Thread t3=new Thread(saller1);
        t1.start();
        t2.start();
        t3.start();
    }
}
class salePic implements Runnable{
    int total=100;
    @Override
    public void run() {
        while(total>0){
                show();
        }
    }
    //方法非静态时,默认监视器为this,此时为this
    public synchronized void show(){
        if (total>0){
            System.out.println(Thread.currentThread().getName()+"售出第"+total+"张票");
            total-=1;
        }
    }
}

可以将需要执行的多线程任务用synchronized标识为特定方法

Thread继承使用synchronized方法

public class sale2 {
    public static void main(String[] args) {
        salePic2 s1=new salePic2();
        salePic2 s2=new salePic2();
        salePic2 s3=new salePic2();
        s1.start();
        s2.start();
        s3.start();
    }
}
class salePic2 extends Thread {
    static int total=10;
    @Override
    public void run() {
        while(total>0){
            show();
        }
    }
    //非静态时监视器为this,在这种情况不可以,因为s1 s2 s3为不同对象
    //静态时监视器为当前类,window.class
    public static synchronized void show(){
        if (total>0){
            System.out.println(Thread.currentThread().getName()+"售出第"+total+"张票");
            total-=1;
        }
    }
}

注意在Thread继承使用时,调用run()的为不同对象,只加上synchronized是没有用的。可以使用static,但是也要尽量避免使用这种方法。

总结

如果共享数据完整声明在一个方法中适合使用synchronized方法
非静态方法,默认监视器为this
静态方法,默认监视器为当前类本身

好处 弊端
解决了线程安全问题 操作共享数据时,实际时串行线程,性能要低一些


扫描二维码,在手机上阅读

推荐阅读: