线程与并发 - synchronized

    技术2022-07-11  93

    多线程与高并发
    synchronized 当多个线程同时访问同一个资源的时候需要对这个资源上锁。synchronized 既保证原子性,也保证线程间的可见性

    当我们对一个数字进行递增操作时,如果两个程序同时访问,第一个线程读到count=0,并对其+1,在自己线程内部的内存里还没有写回去的时候;第二个线程读到的count也是0,并+1写回去;但是程序明明对count进行了两次+1操作,但结果还是1。 那么我们对这个递增过程加上一把锁,当第一个程序访问的时候,这个资源是它独占的,不允许别的线程访问计算,当第一个线程计算完成并释放锁之后其它线程才能访问,这样就保证了线程安全。

    public class Thread_006 { private static int count = 0; public static void main(String[] args) { new Thread(()->{ countAdd(); }).start(); new Thread(()->{ countAdd(); }).start(); } //去掉synchronized 可以看出来不加锁的情况下我们预期的结果与实际结果是不符合的 static /*synchronized*/ void countAdd(){ for (int i = 0; i < 100; i++) { try { //相当于你线程处理的业务 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } count++; } System.out.println(Thread.currentThread().getName()+"count = "+ count); } } 当不在countAdd方法上加锁时的执行结果: Thread-1count = 155 Thread-0count = 155 当在countAdd方法上加锁的执行结果: Thread-0count = 100 Thread-1count = 200
    synchronized 加锁的方式
    我们加锁代码有个原则:尽量少的锁代码,比如我的方法中只需要对count++进行synchronized,我们没必要把整个方法锁住,只锁count++就可以了。但是如果我们一个方法内有N多个地方需要加锁,我们就直接锁方法,就不要每次都让线程竞争了。 public class Thread_007 { private int count = 0; private Object o = new Object();//不可以使用String常量 Integer Long //锁定Object public void m1(){ synchronized (o){ count++; } } //锁定当前对象 public void m2(){ synchronized (this){ count++; } } //等同与m2锁定当前对象 public synchronized void m3(){ count++; } }
    静态方法加锁
    静态方法是没有this对象的,我们也可以不用new出来一个对象进行加锁 public class Thread_008 { private static int count = 10; //等同于synchronized (Thread_008.class) public synchronized static void m1(){ count--; } public static void m2(){ synchronized (Thread_008.class){ count--; } } }
    synchronized保证原子性与线程间可见
    public class Thread_009 { private int count = 100000; public /*synchronized*/ void run(){ count--; System.out.println(Thread.currentThread().getName()+" count = "+count); } public static void main(String[] args) { Thread_009 t = new Thread_009(); for (int i = 100000; i > 0 ; i--) { new Thread(t::run,"Thread "+i+" ").start(); } } } 不加sychronized执行结果,我们可以看到多个线程输出了同一个值,最终结果不是我们预期的0 ...... Thread 38223 count = 38230 Thread 38166 count = 38229 Thread 38226 count = 38229 Thread 38169 count = 38229 Thread 38284 count = 38230 Thread 38168 count = 38228 ...... 当我们加上sychronized时,会解决这个问题 Thread 4 count = 3 Thread 5 count = 2 Thread 2 count = 1 Thread 1 count = 0

    面试题:模拟银行账户,对业务写方法加锁,对业务读方法不加锁,可以吗? 答:不可以,容易产生脏读现象;具体看代码,解决方法就是把读方法也加锁

    public class Thread_010 { String name; double balance; public /*synchronized*/ void set(String name,double balance){ this.name = name; try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } this.balance = balance; } public synchronized double getBalance(){ return this.balance; } public static void main(String[] args) { Thread_010 account = new Thread_010(); new Thread(()->account.set("柯南",300.0)).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("当前账户余额:"+account.getBalance()); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("当前账户余额:"+account.getBalance()); } } 不加锁执行结果: 当前账户余额:0.0 当前账户余额:300.0 加锁执行结果: 当前账户余额:300.0 当前账户余额:300.0
    synchronized 锁重入|异常锁
    锁重入:一个同步方法内可以调用另一个同步方法,及一个类中两个方法都加了锁,那么他们锁定的都是同一个对象,当我们在m1中调用m2时,会发现他们呢是同一个线程的申请这把锁,允许执行m2,这就叫锁重入。异常锁:程序执行过程中,如果某个环节出现异常,默认锁会被释放,外部等待的程序就会冲进来,程序乱入,可能会访问到异常时产生的数据;一般用try-catch解决,保证流程是不会被异常中断的。
    synchronized 锁升级
    这里强烈安利 《我是厕所所长一、二》-马士兵 马老师的文章生动的讲解了锁升级的一个过程,通俗易懂!

    面试题:CAS(自旋锁)一定比系统锁的效率高吗? 答:不一定,分具体情况:执行时间短(加锁的代码),线程数少,用自旋;执行时间长,线程数多,用系统锁。

    Processed: 0.014, SQL: 9