前言

在上一篇博客里我提到了线程安全性的问题,谈到线程的安全性问题,不得不提的就是锁了,下面对锁进行介绍。

什么是线程锁呢,上一讲我提到了锁这个概念,通俗来讲就是保证多个线程对同一共享资源的操作是串行执行的,即同一时刻只有一个线程操作共享资源,提到锁,我们在java中最常用的锁就是Synchronized同步锁了,我们先看一个不加锁的例子。

public class Demo {

public void a() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("a....");
}

public void b() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("b....");
}

public static void main(String[] args) {

    Demo demo=new Demo();

    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo.a();
        }
    }).start();

    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo.b();
        }
    }).start();
}
}

运行之后,我们会看到两个线程基本同时执行结束,即两个线程是并行执行的

a….

b….

但是当我们加了synchronized同步锁后会发现明显的等待,即一个线程等待另一个线程执行完后再继续执行,即两个线程是串行执行的

public synchronized void a() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("a....");
}

public synchronized void b() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("b....");
}

这就是锁的作用,线程执行时首先要获取锁,然后执行,另一个线程执行任务必须等待前面的线程释放锁,才能获取锁继续执行,从而保证线程的安全性

这里的synchornized 既可以用于修饰方法,又可以用作synchronized代码块当synchronized修饰静态方法时,此时的锁为当前类的Class对象,又叫类锁,当修饰成员方法时,默认的锁为this对象,又叫对象锁,相信大家看到这里会有疑问,这有区别吗,不都是锁吗,这里可以很认真的告诉大家有区别,不妨像一下两个线程在执行任务时,如果拿的是不同的锁,那肯定是锁不住的,所以我们要记住,如果我们希望两个线程是串行执行的,必须使用相同的锁

大家可以思考下,下面的代码是怎么执行的,是两个线程同时执行结束,还是一个线程等待另一个线程执行完后继续执行

实例一

public class Demo {

public synchronized void a() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("a....");
}

public synchronized void b() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("b....");
}

public static void main(String[] args) {

    Demo demo1=new Demo();
    Demo demo2=new Demo();
    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo1.a();
        }
    }).start();

    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo2.b();
        }
    }).start();
}
}

实例二

public class Demo {

public synchronized void a() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("a....");
}

public static synchronized void b() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("b....");
}

public static void main(String[] args) {

    final Demo demo1=new Demo();
    final Demo demo2=new Demo();
    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo1.a();
        }
    }).start();

    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo1.b();
        }
    }).start();
}
}

实例三

public class Demo {

public static synchronized void a() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("a....");
}

public static synchronized void b() {

    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("b....");
}

public static void main(String[] args) {

    final Demo demo1=new Demo();
    final Demo demo2=new Demo();
    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo1.a();
        }
    }).start();

    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo2.b();
        }
    }).start();
}
}

答案

实例一 同时执行结束

实例二 一个线程执行结束后另一个线程才执行结束

实例三 一个线程执行结束后另一个线程才执行结束

简单分析下

实例一中 synchornized修饰的都是成员方法,所以锁是this对象(即当前执行方法的对象),demo1和demo2显然不是同一个对象,因此同时执行结束

实例二中 由于采用的同一个demo对象,因此锁是同一个对象,因此一个线程执行结束后另一个线程才执行结束

实例三中 有于synchronized修饰的是静态方法,所以锁定的是Class对象,即使行方法的demo1和demo2不是同一个对象,但是还是拿到的是同一把锁,所以一个线程执行结束后另一个线程才执行结束。

上面说完了synchronized修饰方法的情况,下面说下synchronized代码块的情况,这种就更加灵活的指定锁的对象了,而且相比前面的,代码块这种形式只是锁定方法里面的一部分代码,而不是锁定整个方法,这样就意味着代码块外的代码不用等待线程锁的释放(特别在那种一个方法里面很多代码的那种更为适用),可以提高程序的运行效率,因此在实际中代码块用的更多。

实例

public class T {

int count = 0;

synchronized void m1() {
    //do sth need not sync
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
    count ++;

    //do sth need not sync
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

void m2() {
    //do sth need not sync
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
    //采用细粒度的锁,可以使线程争用时间变短,从而提高效率
    synchronized(this) {
        count ++;
    }
    //do sth need not sync
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

}

很显然方法m2执行效率比m1高,因此同步代码块更加常用。

实例

public class Demo {


private Object obj1 = new Object();
private Object obj2 = new Object();

public void a() {

    synchronized (obj1) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("a....");
    }
}

public void b() {

    synchronized (obj2) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("b....");
    }
}

public static void main(String[] args) {

    final Demo demo1=new Demo();
    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo1.a();
        }
    }).start();

    new Thread(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            demo1.b();
        }
    }).start();
}
}

这里很显然可以看到 obj1与obj2不是同一个对象,因此是同时执行结束

同步和非同步方法是否可以同时调用?

public class T {

public synchronized void m1() { 
    System.out.println(Thread.currentThread().getName() + " m1 start...");
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + " m1 end");
}

public void m2() {
    System.out.println(Thread.currentThread().getName() + " m2 ");
}

public static void main(String[] args) {
    T t = new T();

    new Thread(new Runnable() {

        @Override
        public void run() {
            t.m1();
        }

    }).start();

    //为了使前面的线程先执行,这里睡几秒
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    new Thread(new Runnable() {

        @Override
        public void run() {
            t.m2();
        }

    }).start();

}

}

打印结果

Thread-0 m1 start…

Thread-1 m2

Thread-0 m1 end

可以看出后面启动的线程反而先执行结束,不需要等待前面的线程释放锁,所以同步和非同步方法可以同时调用。

总结

这篇博客主要讲了类锁和对象锁的概念以及synchorized修饰方法或者synchorized代码块的区别,这是面试中经常被问到的,希望引起大家重视!!!