美文网首页
Synchronized的使用

Synchronized的使用

作者: 小和尚恋红尘 | 来源:发表于2018-08-17 09:52 被阅读0次
锁的类型
  • 类锁:只有synchronized修饰静态方法或者修饰一个类的class对象时,才是类锁。
  • 对象锁:除了类锁,所有其他的上锁方式都认为是对象锁。比如synchronized修饰普通方法或者synchronized(this)给代码块上锁等

我们先来看下面这段代码:

private static void count(String name, int i) {
        x = i;
        y = i;
        if (x != y) {
            System.out.println(name + "-->CurrThread:" + Thread.currentThread().getName() + " x=" + x + ",y="+ y);
        }
    }

阅读上面的代码,我们可能会觉得不会有什么输出值。是的如果上面的代码是在一个线程中运行,那么确实不会有输出值;那么我们在两个线程,或者更多线程中运行,会是什么结果呢?我们来验证一下:

//生成一个Runnable对象
private static Runnable getRunnable1(final String name, final int i) {
        return new Runnable() {
            @Override
            public void run() {
                count("Count", i);
            }
        };
    }

//建立线程池,并生成两个线程
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < Integer.MAX_VALUE/10; i++) {
       executorService.execute(getRunnable1(name, i));
       executorService.execute(getRunnable1(name, i));
   }
 executorService.shutdown();

运行程序,并查看输出结果:

Count-->CurrThread:pool-4-thread-69 x=1350
Count-->CurrThread:pool-4-thread-69 y=1322
Count-->CurrThread:pool-4-thread-5 x=1427
Count-->CurrThread:pool-4-thread-5 y=1432

为什么会有结果输出呢?两个线程中都调用的是count方法,里面就是实现了简单的赋值操作;一个线程正在进行赋值操作,这时另一个线程也调用方法进行赋值操作,这样就会造成这个问题,因为多线程中,共同调用一个方法进行赋值,并不是一个线程赋值完成后,另一个线程在进行调用。那么怎么解决这个问题呢?这就是我们这篇要讲的内容。
互斥锁,也就是:当多个线程共同访问共享数据时,需要保证同一时刻有且只有一个线程进行数据操作,其他线程等待该线程执行完后在执行。关键字Synchronized就可满足。下面来看它的具体使用。

  • 修饰非静态实例方法
    建立一个线程实现类,里面调用Count类中的方法
public class TempRunnable implements Runnable {
    private Count count;

    public TempRunnable(Count count) {
        this.count = count;
    }

    @Override
    public void run() {
              count.count("TempRunnable_Count");
    }
}

Count类中的方法

public void count(String name) {
        x++;
        System.out.println(name + "-->CurrThread:" + Thread.currentThread().getName() + " x=" + x);
    }

建立两个线程

private static void outPutString() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count = new Count();
        for (int i = 0; i < 1000; i++) {
            executorService.execute(new TempRunnable(count));
            executorService.execute(new TempRunnable(count));
        }
        executorService.shutdown();
    }

我们期望的结果是2000,来看下代码输出结果:

TempRunnable_Count-->CurrThread:pool-4-thread-276 x=1366
TempRunnable_Count-->CurrThread:pool-4-thread-59 x=1364

这个结果明显不是我们想要的,我们在来看synchronized修饰的方法:

public synchronized void improve(String name) {
        x++;
        System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " x=" + x);
    }

运行程序,结果为:

TempRunnable_improve(Improve)-->CurrThread:pool-4-thread-4 x=1999
TempRunnable_improve(Improve)-->CurrThread:pool-4-thread-60 x=2000

这个明显是我们需要的结果。在看下面例子:
首先,一个类中建立三个方法,两个synchronized方法和一个普通方法,如下:

public class Count {
    public synchronized void countImprove3(String name) {
        System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " end...");
    }

    public synchronized void countImprove4(String name) {
        System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " end...");
    }

    public void countImprove(String name) {
        System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " end...");
    }
}

其次:建立一个Runnable实现类,如下:

public class MyRunnable implements Runnable {
    private Count count;

    public MyRunnable(Count count) {
        this.count = count;
    }

    @Override
    public void run() {
        count.countImprove3("countImprove3:synchronized");
        count.countImprove("normal");
        count.countImprove4("countImprove4:synchronized");
    }
}

最后,在代码中的使用,如下:

        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count = new Count();
        executorService.execute(new MyRunnable(count));
        executorService.execute(new MyRunnable(count));
        executorService.shutdown();

看输出结果:

countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
normal(Improve)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
normal(Improve)-->CurrThread:pool-4-thread-1 end...
normal(Improve)-->CurrThread:pool-4-thread-2 start...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
normal(Improve)-->CurrThread:pool-4-thread-2 end...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...

从输出结果我们可以看到:

  • 在同一个实例对象中,调用synchronized方法的同时,不影响非synchronized方法的调用;
  • 在同一个实例对象中,一个线程调用了其中的synchronized方法后,其他线程就不能再继续调用其中的其他synchronized方法;只有当先调用的线程释放锁后其他线程才能调用。
  • 给方法添加synchronized修饰,本质上就是给这个类的对象加锁,也就是对象锁。
    那我们传入不同的实例对象,有是什么情况呢,看代码:
        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count = new Count();
        executorService.execute(new MyRunnable(new Count(new Count("Count Instance 1:"))));
        executorService.execute(new MyRunnable(new Count(new Count("Count Instance 2:"))));
        executorService.shutdown();

其输出结果为:

Count Instance 1:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...
Count Instance 2:countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove(Improve)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove(Improve)-->CurrThread:pool-4-thread-2 start...
Count Instance 2:countImprove(Improve)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove(Improve)-->CurrThread:pool-4-thread-1 end...
Count Instance 2:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove4:synchronized(synImprove)-->CurrThread:pool-4-thread-1 end...

从上面的输出结果可以看出,他们之间的调用互不影响。因为传入了各自的Count对象,运行后他们进入各自的对象锁。这样就会造成最开始例子中值不一样的问题了。
如果我就想传入不同的对象,但是要实现的效果和传入同一对象一样,这该怎么实现呢?这就需要将synchronized作用于静态方法中,看例子:
新建Count类中有一个static修饰的synchronized方法,如下:

public class Count {
    public static synchronized void add(String name) {
        m++;
        System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " m=" + m);
    }

    public synchronized void count(String name) {
        y++;
        System.out.println(name + "-->CurrThread:" + Thread.currentThread().getName() + " y=" + y);
    }
}

先调用count方法,运行,结果为:

Count Instance 1:-->CurrThread:pool-4-thread-53 y=98
Count Instance 1:-->CurrThread:pool-4-thread-46 y=97

调用add方法,运行,结果为:

Count Instance 2:(Improve)-->CurrThread:pool-4-thread-35 m=199
Count Instance 1:(Improve)-->CurrThread:pool-4-thread-7 m=200

通过上面两个例子可以看出,传入不同的对象,调用synchronized方法,并没有起到互斥锁的作用,因为他们进入了各自的对象锁,因此输出的值并不是依次叠加后的值;调用静态synchronized方法,结果输出正确,所以静态的synchronized方法相当于给整个类加锁,也就是类锁。线程调用其中的静态的synchronized方法,只有一个线程执行完释放锁后其余线程才能进行调用。但是不影响调用其他非静态的方法。

新建类Count中有下面几个方法:

public class Count {
    public void countImprove2(String name) {
        synchronized (this) {
            for (int i = 0; i < 3; i++) {
                System.out.println(name + "(countImprove2)-->CurrThread:" + Thread.currentThread().getName() + " start...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + "(countImprove2)-->CurrThread:" + Thread.currentThread().getName() + " end...");
            }
        }
    }

    public synchronized void countImprove4(String name) {
        for (int i = 0; i < 3; i++) {
            System.out.println(name + "(countImprove4)-->CurrThread:" + Thread.currentThread().getName() + " start...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "(countImprove4)-->CurrThread:" + Thread.currentThread().getName() + " end...");
        }
    }

    public void countImprove(String name) {
        for (int i = 0; i < 3; i++) {
            System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " start...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "(Improve)-->CurrThread:" + Thread.currentThread().getName() + " end...");
        }
    }
}

该类中提供了一个synchronized方法,一个方法中含synchronized代码块和一个普通方法。接下来在建立两个线程类ThreadAThreadB,代码如下:

public class ThreadA implements Runnable {
    private Count count;

    public TempRunnable(Count count) {
        this.count = count;
    }

    @Override
    public void run() {
        count.countImprove2(count.getName());
    }
}

public class ThreadB implements Runnable {
    private Count count;

    public TempRunnable(Count count) {
        this.count = count;
    }

    @Override
    public void run() {
        count.countImprove4(count.getName());
    }
}

在代码中使用:

        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count1 = new Count("Count Instance 1:");
        executorService.execute(new TempRunnable(count1));
        executorService.execute(new MyRunnable(count1));
        executorService.shutdown();

传入了相同的Count实例对象,运行结果为:

Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...

从结果看它是一个执行完成后,另一个才开始执行的。在代码中我们的countImprove2方法是上的对象锁,而countImprove4synchronized代码段传入的是this,因此也就相当于上的对象锁;而调用时传入的也是同一对象,所以:当一个线程调用countImprove2方法,在没有结束前其他的线程并不能调用countImprove4方法,但是可以调用非synchronized修饰过的方法。
更改上面的使用代码,如下:

        ExecutorService executorService = Executors.newCachedThreadPool();
        Count count1 = new Count("Count Instance 1:");
        Count count2 = new Count("Count Instance 2:");
        executorService.execute(new TempRunnable(count1));
        executorService.execute(new MyRunnable(count2));
        executorService.shutdown();

传入了两个不同的实例对象,运行结果为:

Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 2:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...

从结果看,他们是互不影响的。因为传入的是两个不同的,所以他们加的也是不同的对象锁,因此互不影响。
我们来在Count类中加入下面两个方法:

......
    public void countImprove5(String name) {
        synchronized (Count.class) {
            for (int i = 0; i < 2; i++) {
                System.out.println(name + "(countImprove5)-->CurrThread:" + Thread.currentThread().getName() + " start...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(name + "(countImprove5)-->CurrThread:" + Thread.currentThread().getName() + " end...");
            }
        }
    }

    public static synchronized void countImprove3(String name) {
        for (int i = 0; i < 2; i++) {
            System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " start...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + "(synImprove)-->CurrThread:" + Thread.currentThread().getName() + " end...");
        }
    }
......

ThreadAThreadB中分别调用countImprove5countImprove3方法,在传入相同的对象,运行结果为:

Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...

从结果看它是一个执行完成后,另一个才开始执行的。在代码中我们的countImprove5方法中是synchronized代码段,传入的是Count.class因此上的是类锁;而countImprove3为静态synchronized方法,也就相当于上的类锁;而调用时传入的也是同一对象,所以:当一个线程调用countImprove5方法,在没有结束前其他的线程并不能调用countImprove3方法,但是可以调用非synchronized修饰过的方法。

更改代码在ThreadB中调用countImprove4方法,传入相同的对象,运行结果为:

Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 start...
Count Instance 1:countImprove4:synchronized(countImprove4)-->CurrThread:pool-4-thread-2 end...
Count Instance 1:(countImprove5)-->CurrThread:pool-4-thread-1 end...

从结果看互不干扰,因为countImprove4方法加的是对象锁,而countImprove5加的是类锁,他们不是同一类锁,因此互不干扰。

更改代码,在ThreadAThreadB中分别调用countImprove2countImprove3方法,在传入相同的对象,运行结果为:

Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 start...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 start...
Count Instance 1:(countImprove2)-->CurrThread:pool-4-thread-1 end...
countImprove3:synchronized(synImprove)-->CurrThread:pool-4-thread-2 end...

从结果看也是互不干扰,因为countImprove2方法加的是对象锁,而countImprove3加的是类锁,他们不是同一类型的锁,因此互不干扰。

总的来说:
  • 加了相同的锁,不管是对象锁(同一个类的实例对象)还是类锁(同一个类),只有先获取到锁的线程执行完成后其它线程才能继续执行。也就是说:为对象锁时,获取到锁的线程执行完方法后,下次执行的加锁的方法可能是另一个线程了;为类锁时,先获取到锁的线程将改线程内的加了同步锁的方法执行完成后,其它线程才能继续执行。
  • 加了不同的锁,不同的线程之间调用是互不影响的。
  • 不管加了什么锁,对于普通方法的调用不影响。

下一篇:Lock的使用

参考:

相关文章

网友评论

      本文标题:Synchronized的使用

      本文链接:https://www.haomeiwen.com/subject/lkiybftx.html