“同步”是指同一个时间,只有一个线程能对指定的“同步区域”进行访问和修改。
1、使用synchronized实现同步
synchronized修饰的对象有以下几种:
- 修饰一个代码块,需要传入被修饰的对象,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,需要传入被修饰的类(.class),其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
前两种实际上是一种“对象锁”,而后两种则是一种“类锁”。
需要注意的是:
- 对象锁只锁死所有的“同步代码块和同步方法”,这意味着如果一个对象有两个同步方法,其中一个在被访问时,另外一个同步方法也会被锁
- 对象锁只锁“对象”,即两个不同的对象之间互不影响,如果两个线程分别访问两个不同的对象,并不会出现锁
- 当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。
- 类锁用静态方法实现时,同样只锁死所有的静态方法
- 类锁锁“类”,所有的该类的对象都会受影响
- “类锁”和“对象锁”不互斥,即一个同步静态方法被访问时,其他线程可以访问同步普通方法和非同步普通方法
修饰代码块:
public class ThreadTest { public static void main(String[] args) throws InterruptedException { SyncThread syncThread = new SyncThread(); Thread thread1 = new Thread(syncThread,"SyncThread1"); Thread thread2 = new Thread(syncThread,"SyncThread2"); thread1.start(); thread2.start(); }}class SyncThread implements Runnable{ private static int count; public SyncThread(){count=0;} @Override public void run() { //同步代码块 synchronized (this){ for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+":"+(count++)); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public int getCount(){ return count; }}
修饰方法:
//同步方法,效果和同步代码块相同,但同步代码块更灵活@Overridepublic synchronized void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+":"+(count++)); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }}
修饰静态方法:
//同步静态方法public synchronized static void method1(){ for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+":"+(count++)); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }}@Overridepublic void run() { method1();}
修饰类:
//同步类@Overridepublic synchronized void run() { synchronized (SyncThread.class){ for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName()+":"+(count++)); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }}
在修饰代码块的用法里,还有一种特殊的用法:
上面提到对象锁会锁死对象里所有的同步代码块,如果想要给分别给不同的块“加不同的锁”,即有两个同步代码块A和B,A每次只能有一个线程访问,B同样同时只能有一个线程访问,但是可以有不同的线程访问A和B。就可以使用下面的方法:为每个需要维护的变量分别创建“维护对象”:
public class Cinema { private long vacanciesCinema1; private long vacanciesCinema2; //下面这两个对象分别用于维护上面这两个变量 private final Object controlCinema1; private final Object controlCinema2; //对controlCinema1对象加锁,每次只能有一个线程可以访问sellTickets1方法 public boolean sellTickets1 (int number){ synchronized (controlCinema1){ if (number
2、在synchronized代码块中使用wait()、notify()和notifyAll()
wait()、notify()和notifyAll()方法只能在同步代码块中调用,在同步代码块之外调用会抛出异常。三个方法的说明如下:
- wait():使当前占用同步代码块的线程休眠,并且释放控制这个同步代码块的对象,同时允许其他线程执行这个对象控制的所有同步代码块。调用了wait()的线程会一直等待直到其他线程调用了notify()方法
- notify():随机唤醒某个调用了wait()的线程,使之可以重新获取对象的控制权
- notifyAll():唤醒全部调用了wait()的线程
wait()还有一个重载:wait(long)可以规定等待的时长。
使用wait()、notify()进行线程调度:
/** * 解决“生产者-消费者”问题 * 避免队列超过最大大小,生产者无法继续放入对象 * 避免队列为空时,消费者还要取出对象 */public class EventStorage { private int maxSize; private Liststorage; public EventStorage() { this.maxSize = 10; this.storage = new LinkedList<>(); } public synchronized void set(){ while(storage.size()==maxSize){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } storage.add(new Date()); System.out.printf("Set: %d",storage.size()); notifyAll(); } public synchronized void get(){ while (storage.size()==0){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.printf("Get: %d,%s",storage.size(),((LinkedList )storage).poll()); notifyAll(); } public static void main(String[] args) { EventStorage storage = new EventStorage(); new Thread(new Producer(storage)).start(); new Thread(new Consumer(storage)).start(); }}class Producer implements Runnable{ private EventStorage storage; public Producer(EventStorage storage){ this.storage = storage; } @Override public void run() { for (int i = 0; i < 100; i++) { storage.set(); } }}class Consumer implements Runnable{ private EventStorage storage; public Consumer(EventStorage storage){ this.storage = storage; } @Override public void run() { for (int i = 0; i < 100; i++) { storage.get(); } }}