马士兵高并发编程笔记一之关键字概念

脏读

对写业务加锁,对读业务不加锁。在写的过程中去读取,容易产生脏读问题。

重入锁

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

  • 同一个类中

    synchronized void m1() {
    // ….
    m2();
    }

    synchronized void m2() {
    System.out.println(“m2”);
    }

  • 继承中的情形,子类调用父类的同步方法

public class T {
    synchronized void m() {
        System.out.println("m");
    }
    public static void main(String[] args) {
        new TT().m();
    }
}

class TT extends T {
    @Override
    synchronized void m() {
        System.out.println("child m");
        super.m();
    }
}

异常情况

程序在执行过程中,如果出现异常,默认情况锁会被释放
在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生时的数据。
因此要非常小心的处理同步业务逻辑中的异常

Java 内存模型(JMM) 虚拟机 volatile

volatile 使一个变量在多个线程间可见,保证线程间变量的可见性。使用synchronized效率比较低
每个线程可能有自己的缓存,缓存数据从主内存读取。使用volatile后,主内存的值改变后,会通知各个线程更新数据
A B线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道
使用volatile关键字,会让所有线程都会读到变量的修改值

volatile模型

public class T {
    /*volatile*/ boolean running = true; 
    void m() {
        System.out.println("m start");
        while(running) {
        }
        System.out.println("m end!");
    }

    public static void main(String[] args) {
        T t = new T();
        new Thread(t::m, "t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.running = false;
    }
}

volatile 和 synchrozied

volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
volatile只保证可见性
synchrozied既保证可见性,又保证原子行
简单的同步的地方使用 AtomicXXX 变量,更高效。比如++/–

AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的

    /*volatile*/ //int count = 0;
    AtomicInteger count = new AtomicInteger(0); 
    /*synchronized*/ void m() { 
        for (int i = 0; i < 10000; i++) {
            //if(count.get() < 1000) 
            //比如这里中间就不保证原子性
            count.incrementAndGet(); //count++
        }
    }

synchronized优化

void m2() {
        //do sth need not sync

        //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
        //采用细粒度的锁,可以使线程争用时间变短,从而提高效率
        synchronized(this) {
            count ++;

        //do sth need not sync
    }

不要以字符串常量作为锁定对象

在下面的例子中,m1和m2其实锁定的是同一个对象

    String s1 = "Hello";
    String s2 = "Hello";

    void m1() {
        synchronized(s1) {      
        }
    }

    void m2() {
        synchronized(s2) {      
        }
    }

wait 会释放锁 notify sleep 不会释放锁

猜你喜欢

转载自blog.csdn.net/iluojie/article/details/79842613