Java的高并发编程系列(一)synchronized锁

时间:2022-01-21 17:58:30
private int count = 10;
public void test(){
    synchronized (this) { //任何线程要执行下面的代码,必须先拿到Demo02对象实例的锁
            count --;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
        }
    }
public synchronized void test(){//等同于synchronized(this),锁定的是Demo03对象的实例
        count --;
        System.out.println(Thread.currentThread().getName() + " count =" + count);
    }

synchronized是对当前对象Object加了一把锁,在当前线程使用过程中其他线程无法调用,只有上一个线程释放锁后,其他线程才可以使用,所以synchronized是互斥锁。在这里需要注意的是,snchronized锁定的是一个对象,而不是一个类或者代码,这个对象是可以自己自定义的,这里是用this锁定了自己。以上两个等同。

public static void test2(){ //考虑一下这里写synchronize(this)是否可以
        synchronized (Demo04.class) {
            count --;
        }
    }

synchronized当锁定静态方法时,那么此时锁定的当前类的class。

public class Demo05 implements Runnable{
    private int count = 10;
    @Override
    public /*synchronized*/ void run(){
        count --;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
    }
    public static void main(String[] args) {
        Demo05 demo05 = new Demo05();
        for (int i = 0; i < 5; i++) {
            new Thread(demo05,"THREAD" + i).start();
        }
    }
}

以上代码Demo05线程,当定义5个线程时,共同调用count,造成的重用,无法进行资源的共享,当添加 synchronized 时,对run方法加了把锁,一次只能一个线程进行调用,不允许其他线程访问,可避免count 的重用,线程的重入。synchronized修饰的代码块是原子操作。

注意:在多线程中,同步方法和非同步方法时可以同时调用的,这两者之间互不影响。

案例:当对业务写方法加锁,而不对读方法加锁时,容易产生脏读问题。

public class Demo08 {
    String name;
    double balance;
    public synchronized void set(String name, double balance){
        this.name = name;
        try {
            Thread.sleep(2 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
    }
    public synchronized double getBalance(String name){
        return this.balance;
    }
    pblic static void main(String[] args) {
        Demo08 demo08 = new Demo08();
        new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(demo08.getBalance("zhangsan")); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(demo08.getBalance("zhangsan")); } }

在某些业务情况下,当对读取的数据要求不高时,允许脏读的情况下,可以不使用synchronized的情况下,提升性能。

一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候 仍然会得到该对象的锁也就是说synchronized获得的锁是可重入的。

public class Demo09 {
    synchronized void test1(){
        System.out.println("test1 start.........");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        test2();
    }
    synchronized void test2(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test2 start.......");
    }
    public static void main(String[] args) {
        Demo09 demo09 = new Demo09();
        demo09.test1();
    }
}

继承中有可能发生的情形,子类调用父类的同步方法

public class Demo10 {
    synchronized void test(){
        System.out.println("test start........");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test end........");
    }

    public static void main(String[] args) {
        new Demo100().test();
    }
}
class Demo100 extends Demo10{
    @Override
    synchronized void test() {
        System.out.println("child test start.......");
        super.test();
        System.out.println("child test end.......");
    }
}

总而言之:synchronized修饰的锁是可重入的,可以被其他synchronized方法调用。

死锁:简而言之,假如有一个线程需要依次锁定A和B,另一个线程需要依次锁定B和A,此时,第一个线程无法锁定B,第二个线程无法锁定A,这样就产生了死锁。

程序在执行过程中,如果出现异常,默认情况锁会被释放,所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。比如,在一个web app处理过程中,多个servlet线程共同访问通一个资源,这是如果异常处理不合适,在第一个线程中抛出异常,其他线程就会进入同步代码去,有可能访问到异常产生是的数据,因此要非常小心的处理同步业务逻辑中的异常。

public class Demo11 {
    int count = 0;
    synchronized void test(){
        System.out.println(Thread.currentThread().getName() + " start......");
        while (true) {
            count ++;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5) {
                int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch处理,然后让循环继续
            }
        }
    }
    public static void main(String[] args) {
        Demo11 demo11 = new Demo11();
        Runnable r = new Runnable() {   
            @Override
            public void run() {
                demo11.test();
            }
        };
        new Thread(r, "t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }   
        new Thread(r, "t2").start();
    }
}