Java基础——Synchronized用法

8,581 阅读6分钟


volatile关键字,今天分享学习一下他的大表哥Synchronized。Synchronized是Java并发编程中的同步机制关键字,他能后保证同一个时刻只有一条线程能够执行被关键字修饰的代码,其他线程就会在队列中进行等待,等待这条线程执行完毕后,下一条线程才能对执行这段代码。

作用范围

Synchronized在Java中是一种同步锁,他的修饰范围只要以下两个:

  • 修饰代码块
  • 修饰方法

修饰代码块

synchronized(this){.....同步代码块....},这就是同步代码块,括号中的this可以填入任何你想要锁住的操作对象,当有一个线程访问括号中的对象时,其他线程都会被阻塞等待。

[ 例1 ]

class MyThread implements Runnable {
    private int count;

    public MyThread() {
        count = 0;
    }

    public  void run() {
        //同步代码块中锁定的是当前实例对象
        synchronized(this) {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


public class test {

	public static void main(String[] args) {
		SyncThread syncThread = new SyncThread();
		//注意此刻是一个同一个实例对象
		Thread thread1 = new Thread(syncThread, "SyncThread1");
		Thread thread2 = new Thread(syncThread, "SyncThread2");
		thread1.start();
		thread2.start();
	}

}

运行结果


运行结果看出,运算现实由MyThread1执行,等待MyThread1执行完后MyThread2才进入同步代码块执行。

MyThread的run方法同步代码块锁住的对象时this,这里的this是指当前实例对象。

在程序入口中,MyThread只有一个实例对象,所以当MyThread1锁定时,MyThread2会等待阻塞。等到MyThread1执行完毕后,MyThread2才能对当前实例锁定。MyThread1和MyThread2之间使用同一把锁,存在了互斥的关系。

[ 例2 ]  稍作修改:

public class test {

	public static void main(String[] args) {
		Thread thread1 = new Thread(new MyThread(), "MyThread1");
		Thread thread2 = new Thread(new MyThread(), "MyThread2");
		thread1.start();
		thread2.start();
	}

}

运行结果:


运行结果表明,两条线程各自运操作各自的对象,并且输出各自的值。

程序入口稍作修改的是新建了两个对象,此时MyThread中同步代码块的锁就不起作用了,因为同步代码块只是锁定当前实例,此时此刻已经是两个不同实例了,所以现在的MyThread1和MyThread2各自拥有锁,不存在互斥关系,并行执行。

再稍作修改,当对象中一个线程访问同步代码块,另一个线程访问普通代码块,此时会发生什么呢?

[ 例2 ]

class MyThread implements Runnable {
    private int count;

    public MyThread() {
        count = 0;
    }

    public  void run() {
    	String threadName = Thread.currentThread().getName();
        if (threadName.equals("MyThread1")) {
           countAdd();
        } else if (threadName.equals("MyThread2")) {
           printCount();
        }
    }
    
    //同步代码块,锁定当前实例对象
    public void countAdd() {
        synchronized(this) {
           for (int i = 0; i < 5; i ++) {
              try {
                 System.out.println(Thread.currentThread().getName() + ":" + (count++));
                 Thread.sleep(100);
              } catch (InterruptedException e) {
                 e.printStackTrace();
              }
           }
        }
     }
    //非同步代码块
    public void printCount() {
        for (int i = 0; i < 5; i ++) {
           try {
              System.out.println(Thread.currentThread().getName() + " count:" + count);
              Thread.sleep(100);
           } catch (InterruptedException e) {
              e.printStackTrace();
           }
        }
     }
}

public class test {

	public static void main(String[] args) {
		MyThread mThread = new MyThread();
		
		Thread thread1 = new Thread(mThread, "MyThread1");
		Thread thread2 = new Thread(mThread, "MyThread2");
		thread1.start();
		thread2.start();
	}

}

运行结果:


运行结果表明,MyThread1和MyThread2互不干扰各自输出

MyThread修改为一个自增同步方法,一个普通打印方法,程序入口只有一个实例对象,此时新建两条线程访问同一个MyThread对象中的不同代码块,他们是互不干扰,各自干活的。

这个例子主要是要阐述一个点,只有当两条或者多条线程同时访问一个同步方法的时候才会造成阻塞等待状态,而其他编程访问其他非同步方法的时候是不受任何约束的。

[ 例4 ]

class Account {
   String name;
   float amount;

   public Account(String name, float amount) {
      this.name = name;
      this.amount = amount;
   }
   //存钱
   public  void deposit(float amt) {
      amount += amt;
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }
   //取钱
   public  void withdraw(float amt) {
      amount -= amt;
      try {
         Thread.sleep(100);
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
   }

    public float getAmount() {
      return amount;
    }

}

class MyThread implements Runnable{
   private Account account;
   public MyThread(Account account) {
      this.account = account;
   }

   public void run() {
      synchronized (account) {
         account.deposit(500);
         account.withdraw(500);
         System.out.println(Thread.currentThread().getName() + ":" + account.getAmount());
      }
   }
}

public class test {
	
	public static void main(String[] args) {
		Account account = new Account("XXX", 10000.0f);
		MyThread mThread = new MyThread(account);

		Thread threads[] = new Thread[3];
		for (int i = 0; i < 3; i ++) {
		   threads[i] = new Thread(mThread, "Thread" + i);
		   threads[i].start();
		}
	}
}

运行结果


运行结果表明,三条线程分别对account实例进行+500和-500的操作,并且他们是串行的。

MyThread的run中,锁定得是account对象,执行的是对account进行+500和-500的操作。程序执行新建了三条线程访问,分别执行MyThread中的run方法。因为传入的都是实例account,所以三条线程之间是使用同一把锁,互斥,必须等当前线程完成后,下一条线程才能访问account。

修饰方法

synchronized修饰方法的写法:public synchronized void method(){.....} 。修饰方法也是同样简单,此时synchronized是锁定当前实例对象的代码块。也就是当多条线程操作同一个实例对象的同步方法是时,只有一条线程可以访问,其他线程都需要等待。

提个醒

class Father {
   public synchronized void method() {   }
}
class Son extends Father {
   public void method() { super.method();   }
}

synchronized是不能够继承的,简单的说就是子类可以重写父类方法,但没有同步效果。不过调用父类方法还是同步,例子中的super.method()方法仍然是同步的。

定义接口时不能使用synchronized关键字修饰方法。

构造函数不能使用synchronized关键字修饰,但是方法体内可以写同步代码块。

[例5] 修饰静态方法

class MyThread implements Runnable {
   private static int count;

   public MyThread() {
      count = 0;
   }

   public synchronized static void method() {
      for (int i = 0; i < 5; i ++) {
         try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }
   
   public void run() {
      method();
   }
}

public static void main(String[] args) {
		MyThread mThread1 = new MyThread();
		MyThread mThread2 = new MyThread();
		Thread thread1 = new Thread(mThread1, "Thread1");
		Thread thread2 = new Thread(mThread2, "Thread2");
		thread1.start();
		thread2.start();
	}

运行结果:


运行结果看出,运算现实由MyThread1执行,等待MyThread1执行完后MyThread2才进入同步代码块执行。

例子主要阐述,尽管两条线程各自操作不同的对象,但是都调用MyThread中的静态方法,静态方法属于类,即静态同步方法锁定的是类对象。例子中的两条线程用的是同一把锁,纯在互斥关系。

[例6]

class MyThread implements Runnable {
   private static int count;

   public MyThread() {
      count = 0;
   }

   public void method() {
       synchronized(MyThread.class){
		   for (int i = 0; i < 5; i ++) {
		         try {
		            System.out.println(Thread.currentThread().getName() + ":" + (count++));
		            Thread.sleep(100);
		         } catch (InterruptedException e) {
		            e.printStackTrace();
		         }
		      }
	   }
   }
   
   public void run() {
      method();
   }
}

稍作修改,同步代码块中对象是MyThread.class的是类对象锁。运行结果是和例5一致。这种写法就是作用于该类型对象,也就是类对象锁,无论操作的是不是同一个实例对象,只要是该类型,调用该方法,就只有一把锁可用,只允许一条线程执行代码块。

结语

其实上面6个例子中的使用方法,我们只要能区分好是类对象锁还是实例对象锁便能很灵活的运用synchronized同步锁了。实现同步系统开销会很大,使用不当甚至造成死锁,所以要谨慎使用同步锁,不免不必要的同步控制。

希望大家一起努力,一起成长!

                                                        ----- End -----

                                                             更多好文

                                                      请扫描下面二维码

                                                             欢迎关注~