synchronized(this)和synchronized(obj)

synchronized(this)和synchronized(obj)

首先synchronized(this)锁住的是当前的类对象实例,如果synchronized(this)出现在的是service层,那么锁的是该service实例对象,spring的IOC中默认单例模式,当有多个线程执行时也是去竞争同一个service实例对象,所以不会有线程同步问题。但是如果是IOC是多例模式,那么synchronized(this)并不能保证线程安全。下面是简单的例子:
synchronized(this)线程同步例子:

public class ThreadTest implements Runnable{
    private volatile static Integer i=0;
    @Override
    public void run() {
        try{
            for (int j=0; j < 50; j++) {
                Thread.sleep(100);
                synchronized (this){
                    i++;
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
     public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        Thread thread1 = new Thread(threadTest);
        Thread thread2 = new Thread(threadTest);
        thread1.start();
        thread2.start();
    }
}

synchronized(this)和synchronized(obj)_第1张图片
synchronized(this)线程不同步例子:

public class ThreadTest implements Runnable{
     public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        ThreadTest threadTest2 = new ThreadTest();
        Thread thread1 = new Thread(threadTest);
        Thread thread2 = new Thread(threadTest2);
        thread1.start();
        thread2.start();
    }
}

当有两个threadTest实例对象时,并不能保证线程同步,因为两个线程synchronized(this)都能获得各自需要的锁。thread1的锁是threadTest对象,thread2的锁是threadTest2对象。所以结果也是不对的。
synchronized(this)和synchronized(obj)_第2张图片
synchronized(this)和synchronized(obj)_第3张图片
但是如果是synchronized(obj)时,无论是多少个threadTest实例对象都能保证线程同步。如下:

public class ThreadTest implements Runnable{
    private volatile static Integer i=0;
    private static Object lock=new Object();
    @Override
    public void run() {
        try{
            for (int j=0; j < 50; j++) {
                Thread.sleep(100);
                synchronized (lock){
                    i++;
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
     public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        ThreadTest threadTest2 = new ThreadTest();
        Thread thread1 = new Thread(threadTest);
        Thread thread2 = new Thread(threadTest2);
        thread1.start();
        thread2.start();
    }
}

synchronized(this)和synchronized(obj)_第4张图片

synchronized锁Integer、String

当然synchronized也并不是所有对象都可以当作锁的,比如Integer对象和String对象。大家可以用上面的例子试试锁住对象i,结果肯定是线程不同步。
因为一方面i是在不停的在做++运算的,本身的值就在改变,不是同一个对象,另一方面

  • Inteager是如果其结果值总是在 [-128, 127] 之间时,这个值会直接从一个缓存数组中取出,这时取出来的都是同一个对象,而不会重新创建一个新的对象。
  • 如果运算后的结果超出了这个范围,就会每次重新创建一个对象出来,新创建的两个对象也肯定是不一样的。

当synchronized锁的是String时,这就涉及到JVM对字符串的处理。保证线程同步核心思想是锁的是同一个对象

public class ThreadTest implements Runnable{
    @Override
    public void run(){
        String threadName = new String("dd");
        synchronized (threadName) {
            //线程进入
            System.out.println(threadName + " thread start");
            try {
                //进入后睡眠
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //线程结束
            System.out.println(threadName + " thread end");
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(new ThreadTest());
            thread.start();
        }
    }
}

运行结果并没有线程同步

dd thread start
dd thread start
dd thread start
dd thread end
dd thread end
dd thread end

可见,虽然threadName的值都是aa,但是threadName并不是同一个对象。
当然也有解决办法,那就是使用intern()方法,取字符串常量池中的对象,这样每次锁的都是同样一个对象

synchronized (threadName.intern()) 

运行结果:

aa thread start
aa thread end
aa thread start
aa thread end
aa thread start
aa thread end

通过上面的结果可以看出,字符串常量和字符串对象的值相等,地址不同。通过new的对象是在堆栈中,字符串常量是存放在常量池中,通过intern()把字符串对象放入常量池中,则地址是同一个。
使用intern()也有一定的缺点,在数据量很大的情况下,将所有字符串都放入常量池是不合理的,常量池大小依赖服务器内存,且只有等待fullGC,极端情况下会导致频繁fullGC。并且在数据量很大的情况下,将字符串放入常量是存在性能问题。
可以用google的guava包的interner类,

public class test{
    private static Interner<String> lock = Interners.newWeakInterner();
    public void test() {
        synchronized (lock.intern(id.toString())){
		//do...
		}
    }
}

Interner是通过MapMaker构造ConcurrentMap来实现弱引用,ConcurrentMap用分段的方式保证安全。这里个人觉得比常量池的优点就在于这里是弱引用的方式,便于map的回收,常量池只能依赖于fullGC, 这里的回收在不使用或内存不够用条件下即可被回收(Minor GC阶段)。

个人小站:什么是快乐

你可能感兴趣的:(Java,JVM,java,jvm)