synchronized

demo1
package syndemo1;

/**

  • synchronized:加锁的意思,他是对某个对象加锁,而不是对某段代码。

  • 下面例子中创建了一个对象o专门用于加锁。
    */
    public class T1 {
    private int count = 1;
    private Object o = new Object();//o的作用就是充当锁

    public static void main(String[] args) {
    T1 t = new T1();
    t.m();
    }

    public void m(){
    /**
    * 1、要执行下面的代码,必须先拿到o的锁。new Object()在堆里创建了一个对象,o是在栈里指向堆里面的对象。这里实际是
    * 要找堆里面的对象申请锁的。下面统一说是找o申请锁。
    * 2、这里可以理解为向o申请锁,也可以理解为给o加锁,怎么好理解怎么整,但是锁只有一把。
    * 3、第一个线程把这把锁释放了,第二个线程才能申请到这把锁,才能调用下面的方法。一个线程拿到了这把锁别的线程就拿不到了,
    * 这就叫互斥锁。
    */
    synchronized(o){
    count--;
    System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
    }
    }
    }

demo2
package syndemo2;

public class T2 {
private int count = 2;

public static void main(String[] args) {
    T2 t = new T2();
    t.m();
}

/**
 * 任何线程要执行下面的代码,先要给this加锁,这里是给自身加锁。或者说要用这个方法,要先new一个T的对象指向自身。
 * 也就是说this就是T2的对象。
 *
 * m()方法里面“一开始就对this加锁了”,对这种有个简单的写法,见T3。
 */
public void m(){
    synchronized (this){
        count--;
        System.out.println("线程名:"+Thread.currentThread().getName()+"    count:"+count);
    }
}

}

demo3
package syndemo3;

public class T3 {
private int count = 3;

public static void main(String[] args) {
    T3 t = new T3();
    t.m();
}

/**
 * 对T2里面的简单写法如下。这个例子里面锁定的是当前对象this。
 */
public synchronized void m(){
    count--;
    System.out.println("线程名:"+Thread.currentThread().getName()+"    count:"+count);
}

}

demo4
package syndemo4;
public class T4 {
private static int count = 4;

public static void main(String[] args) {
   m();//这里也可以写为T4.m()
   mm();//这里也可以写为T4.mm()
}

/**
 *这里m()方法和mm()方法中的两个加锁的效果是一样的。
 * 我们知道万物皆对象,T4.class是将类抽象成了一个对象。
 * 静态方法里面synchronized加锁的对象是当前类的class对象。
 */
public synchronized static void m(){
    count--;
    System.out.println("线程名:"+Thread.currentThread().getName()+"    count:"+count);
}

public static void mm(){
    /**
     * 静态属性和静态方法的调用是类名.类方法或类名.类属性,这个时候调用方法的时候是没有new一个对象的,所以下面这行代码里
     * 就不能写synchronized(this)。即这里不能给this加锁!这个时候锁定的是当前类的class对象。
     */
    synchronized (T4.class){
        count--;
        System.out.println("线程名:"+Thread.currentThread().getName()+"    count:"+count);
    }
}

}

demo5
package syndemo5;

public class T5 implements Runnable{
private int count = 10;
public /synchronized/ void run() {
count--;
System.out.println("线程名:"+Thread.currentThread().getName()+" count:"+count);
}

public static void main(String[] args) {
    T5 t = new T5();
    for(int i = 0; i < 5; i++){
        new Thread(t, "THREAD" + i).start();
    }
}
/**
 * 多次运行之后,可能会拿到如下结果:
 * 线程名:THREAD1    count:8
 * 线程名:THREAD0    count:8
 * 线程名:THREAD2    count:7
 * 线程名:THREAD3    count:6
 * 线程名:THREAD4    count:5
 * 分析:1、上面的代码中只new了一个T5的对象t,而不是在每个线程中都new了对象。所以这些线程是共同访问这个对象的。
 * 2、这5个线程访问的是同一个count,count在堆里面,t在栈里面。
 * 3、这里对运行结果做个分析。第一个线程count--之后还没有打印之前,第二个线程进来了,做了个count--操作,这时候
 *第一个线程才开始打印结果,第二个线程也随之打印,所以前两个线程打印的结果都是count--再count--的结果,即8,后边的三个
 * 线程都是count--立马打印,即结果正确。
 * 解决办法。只要将上面的synchronized打开给加上锁,重复多次执行拿到的结果都是正确的。这里不展示测试结果了。
 */

}

demo6
package syndemo6;
/**

  • 问题:同步和非同步方法是否可以同时调用?
  • 答:可以。理解:m1执行需要锁,m2不管有没有锁都会执行,所以可以同时调用。(注意下面两个方法中的休眠时间)
  • https://blog.csdn.net/zhou_fan_xi/article/details/83752697 lambda表达式引入依赖后报错的解决方法
  • 运行结果:
  • 线程名:t1 m1 start
  • 线程名:t2 m2
  • 线程名:t1 m1 end
    */
    public class T6 {
    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(){
    try {
    Thread.sleep(5000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println("线程名:"+Thread.currentThread().getName()+" m2");
    }
    public static void main(String[] args) {
    T6 t = new T6();
    new Thread(()->t.m1(),"t1").start();//在一个线程中调用m1()方法
    new Thread(()->t.m2(),"t2").start();//在另一个线程中调用m2()方法
    }
    }

demo7
package syndemo7;
import java.util.concurrent.TimeUnit;
/**

  • 下面这个类模拟了一个充钱和查询钱的业务。对充钱的过程加了锁,对查询钱的过程没加锁,在执行过程中可能产生脏读。
    */
    public class Account {
    String name;
    double money;

    //充钱
    public synchronized void setMoney(String name, double money){
    this.name = name;

     /**
      *  这里线程休息了2秒后才把钱设置进去。这里是为了模拟真实义务逻辑复杂的情况,将代码执行时间放大
      *  尽管对写的过程加了锁,但是在写的过程还没有完成的时候进行了读,也就是在休息的这2秒内进行了读,
      *  读是没加锁的,就可能发生脏读。
      */
     try {
         Thread.sleep(2000);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
    
     this.money = money;
    

    }
    //查询钱

    /**
    如果业务里面允许暂时性的脏读,那么读是不用加锁的,这样可以提高性能。如果不允许,就读写都加锁,把下面的加锁的代码打开即可。
    /
    public /
    synchronized
    / double getMoney(String name){
    return this.money;
    }

    public static void main(String[] args) {
    Account account = new Account();

// new Thread(new Runnable() {//开启一个线程,调用setMoney()方法
// @Override
// public void run() {
// account.setMoney("张三",101.0);
// }
// }).start();

    //开启一个线程,调用setMoney()方法。下面这行代码等同于上面注释的六行代码
    new Thread(()->account.setMoney("张三",100.0)).start();

    try {
        TimeUnit.SECONDS.sleep(1);//线程休息1秒
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println(account.getMoney("张三"));//查询张三的钱

    try {
        TimeUnit.SECONDS.sleep(2);//线程休息2秒
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    System.out.println(account.getMoney("张三"));//查询张三的钱
}

}

demo8
package syndemo8;
import java.util.concurrent.TimeUnit;
/**

  • 一个同步方法里面可以调用另一个同步方法。一个线程已经拥有了某个对象的锁,再次申请的时候仍然会得到该对象的锁,
  • 也就是说synchronized获得的锁是可重入的。
    /
    public class T8 {
    public static void main(String[] args) {
    new T8().m1();
    }
    /
    *
    • m1方法和m2在同一个线程(主线程)里面,所以这两个方法都能获得这把锁。即同一个线程同一把锁,所以一个同步方法里面可以调用另一个同步方法。
      */
      synchronized void m1(){
      System.out.println("m1 start");
      try {
      TimeUnit.SECONDS.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      m2();
      }
      synchronized void m2(){
      try {
      TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println("m2被调用");
      }
      }

demo9
package syndemo9;
/**

  • 这个类里面模拟了死锁
    */
    public class T9 {
    private final Object o1 = new Object();
    private final Object o2 = new Object();
    private void m1(){
    synchronized(o1){
    System.out.println(Thread.currentThread().getName()+"获取对象o1锁");
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    m2();
    }
    System.out.println("m1结束");
    }
    private void m2(){
    synchronized(o2){
    System.out.println(Thread.currentThread().getName()+"获取对象o2锁");
    m1();
    }
    System.out.println("m2结束");
    }
    public static void main(String[] args) {
    T9 t = new T9();
    new Thread(t::m1, "线程1").start();
    new Thread(t::m2, "线程2").start();
    }
    }

demo10
package syndemo1011;
import java.util.concurrent.TimeUnit;
/**

  • 一个同步方法里面可以调用另一个同步方法。一个线程已经拥有了某个对象的锁,再次申请的时候仍然会得到该对象的锁,
  • 也就是说synchronized获得的锁是可重入的。下面展示的是继承中可能发生的情况,子类的同步方法里面调用父类的同步方法,不会死锁。
  • 运行结果:
  • child m start
  • m start
  • m end
  • child m end
    /
    public class T10 {
    public synchronized void m(){
    System.out.println("m start");
    try {
    TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println("m end");
    }
    public static void main(String[] args) {
    /
    *
    * 根据前面的知识我们知道:这里的两个m()方法都是给this加锁,而this就是下面new出来的T11的对象。所以这俩个加锁的是同一个对象
    */
    new T11().m();
    }
    }
    class T11 extends T10{
    @Override
    public synchronized void m() {
    System.out.println("child m start");
    super.m();//子类调用父类的同步方法
    System.out.println("child m end");
    }
    }

demo11
package syndemo12;
import java.util.concurrent.TimeUnit;
/**

  • 程序在执行过程中,如果出现异常,默认情况下锁会被释放。在并发过程中有异常要特别注意,不然会发生不一致的情况。

  • 运行结果请自行观察
    /
    public class T12 {
    int count = 0;
    synchronized void m(){
    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 == 6){
    /
    *
    * 这里抛异常,锁将会被释放,如果进行try……catch……,锁就不会被释放。
    */
    int i = 1/0;
    }
    }
    }

    public static void main(String[] args) {
    T12 t12 = new T12();
    Runnable runnable = new Runnable() {
    @Override
    public void run() {
    t12.m();
    }
    };

     new Thread(runnable,"t1").start();//创建第一个线程
    
     try {
         TimeUnit.SECONDS.sleep(10);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
    
     new Thread(runnable,"t2").start();//创建第二个线程
    

    }

}

你可能感兴趣的:(synchronized)