同步(同步代码块synchronized(this) 同步方法 、全局锁、同步处理方法对比)

为什么会出现同步处理?

如果要写一个多个线程卖票的代码,按照原本思路代码如下:

class MythreadB implements  Runnable
{
    private Integer tickets=10;
    public void run()
    {
        while(tickets>0) {
            try {
                System.out.println(Thread.currentThread().getName() + "还剩" + tickets + "张票");
                tickets--;
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Synch
{
    public static void main(String[] args) {
        MythreadB thread=new MythreadB();
        new Thread(thread,"黄牛1").start();
        new Thread(thread,"黄牛2").start();
        new Thread(thread,"黄牛3").start();
    }
    
}

同步(同步代码块synchronized(this) 同步方法 、全局锁、同步处理方法对比)_第1张图片
从结果可以看出,出现了多个4张票并且2 、3张票不存在,这和事实相悖,是为什么呢?
是因为三个线程并发执行,黄牛2、3、1同时访问共享资源tickets,并且同时卖了一张票,并且将tickets–。事实是对共享资源tickets需要同步访问,即线程1访问时,线程2不能访问,线程2需要等线程1访问结束才可以访问,那么这就需要对共享资源加锁,实现同步处理。

需要同步处理,需要借助关键字synchronized。

同步处理包括同步方法和同步代码块。

同步代码块:
在方法中实现:

synchronized(对象){ }

既然是加锁,那么就需要有锁住的对象,一般锁的是当前对象this,但是也根据情况来锁对象。
同步代码块指同一时刻只有一个线程进入同步代码块,但是多个线程可以进入方法。

////同步代码块
class MythreadB implements  Runnable
{
    private Integer tickets=10;
    public void run()
    {
        for(int i=0;i<10;i++)
        {
            ////同步代码块,一次只允许一个线程进入该代码块
            synchronized (this)
            //锁住的是当前MythreadB对象,而只有一个MythreadB对象,3个线程是通过MythreadB转换为Thread创建出来的,
            // 所以实现了同步,一次只能有线程卖票,一个线程卖完一张票后,隐式解锁,其他线程可以卖票
            {
                  if(tickets>0) {
                    try {
                        System.out.println(Thread.currentThread().getName() + "还剩" + tickets + "张票");
                        tickets--;
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
public class Synch
{
    public static void main(String[] args) {
        MythreadB thread=new MythreadB();
        new Thread(thread,"黄牛1").start();
        new Thread(thread,"黄牛2").start();
        new Thread(thread,"黄牛3").start();
    }
}

同步(同步代码块synchronized(this) 同步方法 、全局锁、同步处理方法对比)_第2张图片
从结果可以看出,实现了同步,同一时刻只有一个线程卖票。注意:synchronized是隐式解锁,后面会讲解。

同步代码块是只能有一个线程进入该代码块,但是会有多个线程进入该方法,如果想要同一时刻只有一个线程进入方法,需要用同步方法。

同步方法

同步方法是当方法声明上加synchronized,表示此时只有一个线程进入同步方法。锁住的该类实例化的对象。

class MythreadB implements  Runnable
{
    private Integer tickets=10;
    public void run()
    {
        for(int i=0;i<10;i++)
        {
            sale();
        }
    }
    synchronized public void sale()  //同步方法,表示同一时刻只能有一个线程进入该方法,锁住的是MythraedB对象
    {
        if(tickets>0) {
            try {
                System.out.println(Thread.currentThread().getName() + "还剩" + tickets + "张票");
                tickets--;
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

同步(同步代码块synchronized(this) 同步方法 、全局锁、同步处理方法对比)_第3张图片

synchronized锁住的对象,并不是代码块。

验证如下:

class A
{
    synchronized public void print() 
    {
        System.out.println(Thread.currentThread().getName()+":进入print方法");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":离开print方法");
    }

}
class MythreadB implements  Runnable
{
    public void run()
    {
       A myA=new A();
       myA.print();
    }
}
public class Synch
{
    public static void main(String[] args) {
        MythreadB thread=new MythreadB();
        new Thread(thread,"线程1").start();
        new Thread(thread,"线程2").start();
        new Thread(thread,"线程3").start();
    }
}

同步(同步代码块synchronized(this) 同步方法 、全局锁、同步处理方法对比)_第4张图片

从结果分析:如果synchronized锁住的print这个代码块,那么当一个线程进入print方法并且离开print方法,另一个线程才可以进入,但是结果不是这样。是因为synchronized锁住的是A实例化出的对象,而在主方法中有三个线程,每个线程创建后,JVM回调run方法,run方法会new A对象,3个线程就会有3个A对象,synchronized只会将该线程的A对象锁住,并不能锁其他A对象。所以,3个线程会并发进入,并发离开。
总结:synchronized(this)以及普通synchronized方法,只能防止多个线程同时执行同一对象的同步端,synchronized锁的括号中的对象而非代码。

如果想要将这个print代码段锁住,即同一时刻只有一个线程进入print,该怎么做呢?

有2种方法:

  • 只有一个A对象,那么就会把A对象锁住;
  • 全局锁:将Class对象锁住。

锁同一个对象

////锁同一个对象
class A
{
    synchronized public void print()
    {
        System.out.println(Thread.currentThread().getName()+":进入print方法");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":离开print方法");
    }

}
class MythreadB implements  Runnable
{
    private A myA;  //现在就只有一个A对象

    public MythreadB(A a)
    {
        this.myA=a;
    }

    public void run()
    {
        this.myA.print();
    }
}
public class Synch
{
    public static void main(String[] args) {
        MythreadB thread=new MythreadB(new A());  //同一个A对象
        new Thread(thread,"线程1").start();
        new Thread(thread,"线程2").start();
        new Thread(thread,"线程3").start();
    }
}

同步(同步代码块synchronized(this) 同步方法 、全局锁、同步处理方法对比)_第5张图片

当只有一个对象A后,synchronized 就锁住了的三个线程的同一个A对象,那么一个线程就会等另一个线程出代码块解锁后才会进入print代码块。

全局锁:

全局锁,锁住的类对象,并不是类实例化出的对象。
如果我们想要锁住多个对象的同一方法,可以使用全局锁,将当前类对象锁住,使用类的静态同步方法 synchronized与static一起使用,此时锁的是当前使用的类对象而非类实例化出的对象。

class A
{
    synchronized static  public void print()
    //定义为静态方法,也就是将A这个类锁住,那么同一时刻,只能有一个类对象进入代码块
    {
        System.out.println(Thread.currentThread().getName()+":进入print方法");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":离开print方法");
    }

}
class MythreadB implements  Runnable
{

    public void run()
    {
       A.print();
    }
}
public class Synch
{
    public static void main(String[] args) {
        MythreadB thread=new MythreadB();
        new Thread(thread,"线程1").start();
        new Thread(thread,"线程2").start();
        new Thread(thread,"线程3").start();
    }
}

同样可以使用synchronized(this)同步代码块来实现全局锁,
在代码块中锁当前class(类)对象:synchronized(类名称.class){ }
代码如下:

////  同步代码块 :synchronized(类名称.class)
class A
{
      public void print()
      {
          synchronized (A.class)
          {
              System.out.println(Thread.currentThread().getName() + ":进入print方法");
              try {
                  Thread.sleep(100);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println(Thread.currentThread().getName() + ":离开print方法");
          }
      }
}
class MythreadB implements  Runnable
{
    public void run()
    {
      new A().print();
    }
}

同步(同步代码块synchronized(this) 同步方法 、全局锁、同步处理方法对比)_第6张图片

接下来是同步处理几种方法的对比
同步(同步代码块synchronized(this) 同步方法 、全局锁、同步处理方法对比)_第7张图片

小练习:线程1进入同步方法1,线程2能否进入同步方法2

//线程1进入同步方法1,线程2能否进入同步方法2
class A
{
    ////线程1持有A实例化出对象锁,并且一直死循环
     synchronized public void testA() {
         if (Thread.currentThread().getName().equals("线程1")) {
             while (true) {
             }
         }
     }

     ///线程2进入testB
     synchronized public void testB() {
         if (Thread.currentThread().getName().equals("线程2")) {
             System.out.println("线程2进入同步方法");
         }
     }
}
class MythreadB implements  Runnable
{
    private A myA;
    public MythreadB(A a)
    {
        this.myA=a;
    }
    public void run()
    {
       this.myA.testA();
       this.myA.testB();
    }
}
public class Synch
{
    public static void main(String[] args) {
        MythreadB thread=new MythreadB(new A());
        new Thread(thread,"线程1").start();
        new Thread(thread,"线程2").start();
    }
}

结果:并没有输出“线程2进入同步方法”,是因为线程1给A实例化出对象加锁,在线程1没有释放锁前,线程2无法获得实例化对象锁,这也更加说明了synchronized锁的是对象而不是代码块。
但是如果在testA方法前不加synchronized ,即线程1没有A实例化对象锁,线程2可以进入testB,输出“线程2进入同步方法”。

你可能感兴趣的:(java)