多线程——同步问题

2021-10-08 14:17:34 浏览数 (1)

我们先看个错误示例。代码功能:声明一个数字并赋值10000.然后让1w个线程去减少1,1w个线程去增加1。理论上说,加一万减一万,最后数字的结果并不会改变。

代码:

代码语言:javascript复制
class ErrorDemo{
    private static int Num=10000;
    public static void main(String[] args) throws InterruptedException {
        int n=10000;
        //声明线程数组,在后面使用join让主线程等待所有线程执行完。
        // 不然主线程跑完了,其他线程没执行完就输出结果会不对的。
        Thread[] add=new Thread[n];
        Thread[] reduce=new Thread[n];
        //创建1w个减少线程
        for(int i=0;i<n;i  ){
            Thread t1=new Thread(){
              public void run(){
                    Num-=1;
              }
            };
            t1.start();
            reduce[i]=t1;
        }
        //创建1w个增加线程
        for(int i=0;i<n;i  ){
            Thread t2=new Thread(){
                public void run(){
                    Num =1;
                }
            };
            t2.start();
            add[i]=t2;
        }

        //让主线程等待所有线程执行完毕
        for(Thread t:add)
        {
            t.join();
        }
        for(Thread t:reduce)
        {
            t.join();
        }

        //输出结果
        System.out.println(Num);
    }
}

最后输出的结果是9999。这个结果不固定,有多有少。

造成这种错误的原因是:

假设增加线程获取到数字是10000,进行了加一操作,结果是10001。

但是减少线程在返回结果之前也获取到了数字10000。

减少线程最后返回9999。

所以我们原本期望得到的10000变成了9999。

为了解决这种问题,可以使用synchronized

使用方法:

代码语言:javascript复制
Object object=new Object();//object就是你当前线程操作的对象,比如上面的int数字
synchronized (object){
   //当前线程独占了object,其他线程访问object就会等待当前线程释放object
}

释放object的方法:

代码语言:javascript复制
synchronized代码块结束或者异常抛出。

使用synchronized后的代码
代码语言:javascript复制
public class Thread_synchronization {
}

class ErrorDemo{
    public static void main(String[] args) throws InterruptedException {
        //因为synchronized里面要求的是对象,所以需要用Integer声明
        Integer Num = 10000;
        int n=10000;
        //声明线程数组,在后面使用join让主线程等待所有线程执行完。
        // 不然主线程跑完了,其他线程没执行完就输出结果会不对的。
        Thread[] add=new Thread[n];
        Thread[] reduce=new Thread[n];
        //创建1w个减少线程
        for(int i=0;i<n;i  ){
            Thread t1=new Thread(){
              public void run(){
                  synchronized (Num) {
                      addNum(Num);
                  }
              }
            };
            t1.start();
            reduce[i]=t1;
        }
        //创建1w个增加线程
        for(int i=0;i<n;i  ){
            Thread t2=new Thread(){
                public void run(){
                    synchronized (Num) {
                        reduceNum(Num);
                    }
                }
            };
            t2.start();
            add[i]=t2;
        }

        //让主线程等待所有线程执行完毕
        for(Thread t:add)
        {
            t.join();
        }
        for(Thread t:reduce)
        {
            t.join();
        }

        //输出结果
        System.out.println(Num);
    }

    public static int addNum(int Num){
        return Num 1;
    }
    public static int reduceNum(int Num){
        return Num-1;
    }
}

最后的结果是10000

除了上面的方法,还可以直接在操作数据的方法前加上synchronized

代码语言:javascript复制
public class Thread_synchronization {
}

class ErrorDemo{
    public static void main(String[] args) throws InterruptedException {
        //因为synchronized里面要求的是对象,所以需要用Integer声明
        Integer Num = 10000;
        int n=10000;
        //声明线程数组,在后面使用join让主线程等待所有线程执行完。
        // 不然主线程跑完了,其他线程没执行完就输出结果会不对的。
        Thread[] add=new Thread[n];
        Thread[] reduce=new Thread[n];
        //创建1w个减少线程
        for(int i=0;i<n;i  ){
            Thread t1=new Thread(){
              public void run(){
                      addNum(Num);
              }
            };
            t1.start();
            reduce[i]=t1;
        }
        //创建1w个增加线程
        for(int i=0;i<n;i  ){
            Thread t2=new Thread(){
                public void run(){
                        reduceNum(Num);
                }
            };
            t2.start();
            add[i]=t2;
        }

        //让主线程等待所有线程执行完毕
        for(Thread t:add)
        {
            t.join();
        }
        for(Thread t:reduce)
        {
            t.join();
        }

        //输出结果
        System.out.println(Num);
    }

    public synchronized static int addNum(int Num){
        return Num 1;
    }
    public synchronized static int reduceNum(int Num){
        return Num-1;
    }
}

最后的结果是10000

如果一个类里面的所有方法都被synchronized修饰,那么这个类就是线程安全的类。

同一时间,只有一个线程可以进入这个类的一个实例去修改数据,以免多个线程同时修改数据,而产生脏数据。

0 人点赞