并发-Synchronized

Synchronized的理解

  • 前言
    • 上帝视角
    • Synchronized使用
    • 实例锁和全局锁
    • 死锁
    • Synchronized原理
    • Synchronized实现生产者消费者模式

前言

在我的理解中,Java要学习的有三点Collection集合类线程相关io流;但每一个模块的学习都是需要比较久的时间,如果你是学生,请一步一步啃完,每点都不要错过。如果你是工作了几年的,请先将三个模块中必看的过一遍,记录下来。然后学习你工作相关的知识,将必须掌握的先学习,然后在后面的时间中补充这些模块中的知识,形成系统。

上帝视角

Synchronize是我们实现线程互斥同步的常用手法,Synchronize本身是一个悲观机制的独占锁,并且可重入、非公平。
Synchronize加锁后,线程访问同步方式时会被阻塞,阻塞状态无法手动解除

Synchronized使用

使用Synchronized修饰方法代码块,当线程A访问“某对象”的Synchronized方法,那么其他线程访问“该对象”的该Synchronized方法或Synchronized代码块将被阻塞;并且访问该对象的其他Synchronized方法或Synchronized代码块也将阻塞;访问“该对象”的非同步代码块将被允许

public class Animal {

	//同步方法
	public synchronized void eat() {
		for (int i = 0; i < 10; i++) {
			try {
				System.out.println(Thread.currentThread().getName() + "eat:" + i);
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	//同步方法
	public synchronized void run() {
		for (int i = 0; i < 10; i++) {
			try {
				System.out.println(Thread.currentThread().getName() + "run:" + i);
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	//同步代码块,同步对象this
	public void sleeps() {
		synchronized (this) {
			for (int i = 0; i < 10; i++) {
				try {
					System.out.println(Thread.currentThread().getName() + "sleeps:" + i);
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
	//同步代码块,同步对象Animal.class
	public static void readBook() {
		synchronized (Animal.class) {
			for (int i = 0; i < 10; i++) {
				try {
					System.out.println(Thread.currentThread().getName() + "readBook:" + i);
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

	public void shout() {
		for (int i = 0; i < 10; i++) {
			try {
				System.out.println(Thread.currentThread().getName() + "shout:" + i);
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

验证1:当线程A访问“某对象”的Synchronized方法,那么其他线程访问“该对象”的该Synchronized方法或Synchronized代码块将被阻塞;并且访问该对象的其他Synchronized方法或Synchronized代码块也将阻塞

public class SyncTest {

	public static void main(String[] args) {
		Animal animal = new Animal();

		Thread t = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				animal.eat();
			}
		});
		Thread t2 = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				animal.sleeps();
			}
		});

		t.start();
		t2.start();

	}
}

打印如下:当线程t调用同步方法eat()时,另一个线程t2同步方法阻塞;当线程t调用同步方法animal.eat()方法时,另一个线程t2同步方法animal.sleeps()阻塞并发-Synchronized_第1张图片并发-Synchronized_第2张图片

验证2:当线程A访问“某对象”的Synchronized方法,访问“该对象”的非同步代码块将被允许。

public class SyncTest {

	public static void main(String[] args) {
		Animal animal = new Animal();

		Thread t = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				animal.eat();
			}
		});
		Thread t2 = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				animal.shout();;
			}
		});

		t.start();
		t2.start();

	}
}

打印如下:线程调用交替进行,当一个对象的同步方法被调用的时候,其他线程调用当前对象的其他非同步方法将不被阻塞
上述特点都是实例锁的相关特点,在Synchronized中如果将锁使用在非静态方法或类上那么这就是一个实例锁,如果将锁使用在静态方法上或者类上,那这就是全局锁
并发-Synchronized_第3张图片

实例锁和全局锁

实例锁是一个对象锁,如果该对象是单例模式,那么其具有全局锁的效果
全局锁是锁在类上或者静态方法上,无论拥类有多少个对象,线程都共享该锁

Animal 有同步方法 eat()、run() , 同步代码块sleeps()、静态同步代码块readBook()
接下来我们研究

  1. animal1.eat()、animal2.eat()
  2. animal1.eat()、animal2.readBook()
  3. animal1.readBook() animal2.readBook()

1、结果::相互交替执行。因为synchronized对象锁是存在于各自的对象animal1和animal2上的属于各自实例,使用的是各自的实例锁,故相互交替执行。

并发-Synchronized_第4张图片

2、结果:相互交替执行。animal1.eat()是调用的animal1的对象锁,属于实例锁,而animal2.readBook()相等于调用的是一个静态方法,静态方法不属于实例类,本身相等于调用的Animal.readBook(),属于全局锁,所以不是同一个锁。
并发-Synchronized_第5张图片

3、结果:被阻塞。因为readBook()是一个静态方法,那么两个方法其实都相当于Animal.readBook(使用的全局锁, 全局锁不管这个类实例多少个对象,其锁都是在SomeThing类上,所以会被阻塞。

并发-Synchronized_第6张图片

死锁

我们知道Synchronized可以修饰方法和代码块,在使用Synchronized修饰代码块的时候得避免死锁的产生,死锁的产生主要是线程1获得了同步锁A,并且在A中要获取同步锁B,但在同步锁A的时候,有另一个线程2已经获取了同步锁B,并在B中获取同步锁A, 那么线程A会等待线程B解锁,但线程B又等待线程A解锁,这时候就产生了死锁。Synchronized的阻塞过程不能手动结束

	public class syncTest2 {
		
	private static Object o1;
	private static Object o2;

	public static void main(String[] args) {
		new syncTest2().deadLock();
	}

	public void deadLock() {

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (o1) {
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (o2) {
						System.out.println(Thread.currentThread().getName() );
					}
				}
			}
		});

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (o2) {
					synchronized (o1) {
						System.out.println(Thread.currentThread().getName() );
					}
				}
			}
		});

		t1.start();
		t2.start();
	}
}

避免死锁:
1、使用Lock接口的tryLock(long timeout, TimeUnit unit)方法,设置超时时间,超时可以退出防止死锁
2、降低锁的使用粒度,只加锁共享变量,这样既增加效率,同时尽量避免死锁
3、减少或替代Synchronized的使用

Synchronized原理

并发-Synchronized_第7张图片
它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。

Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;

Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中;

Wait Set:哪些调用wait方法被阻塞的线程被放置在这里;

OnDeck:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为OnDeck;

Owner:当前已经获取到所资源的线程被称为Owner;

!Owner:当前释放锁的线程。

JVM每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下,ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选竞争线程。Owner线程会在unlock时,将ContentionList中的部分线程迁移到EntryList中,并指定EntryList中的某个线程为OnDeck线程(一般是最先进去的那个线程)。Owner线程并不直接把锁传递给OnDeck线程,而是把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM中,也把这种选择行为称之为“竞争切换”。

OnDeck线程获取到锁资源后会变为Owner线程,而没有得到锁资源的仍然停留在EntryList中。如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,直到某个时刻通过notify或者notifyAll唤醒,会重新进去EntryList中。

处于ContentionList、EntryList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。

Synchronized是非公平锁。 Synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck线程的锁资源

Synchronized实现生产者消费者模式

	//消费
public class ConsumptionFactory implements Runnable {
	private Warehouse warehouse;

	public ConsumptionFactory(Warehouse warehouse) {
		this.warehouse = warehouse;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		onConsumption();
	}

	private void onConsumption() {
		while (true) {
			synchronized (warehouse) {
				// 如果数量够,就消费
				if (warehouse.getCommodityCount() > 0) {
					warehouse.delete();
					System.out.println(Thread.currentThread().getName() + "====消费后剩余:" + warehouse.getCommodityCount());
					warehouse.notifyAll();
				} else {
					try {
						warehouse.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
	}
}

	//生产
public class ProductionFactiry implements Runnable {
	private Warehouse warehouse;

	public ProductionFactiry(Warehouse warehouse) {
		this.warehouse = warehouse;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		onProduction();
	}

	private void onProduction() {
		while (true) {
			synchronized (warehouse) {

				warehouse.add();
				System.out.println(Thread.currentThread().getName() + "---生产后剩余" + warehouse.getCommodityCount());

				warehouse.notifyAll();

				try {
					warehouse.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

		}

	}
}

	public class Warehouse {

	private int commodityCount = 0;

	public synchronized void add() {
		commodityCount++;

	}

	public synchronized void delete() {
		commodityCount--;
	}

	public synchronized int getCommodityCount() {
		return commodityCount;
	}

}
	//生产消费

public class ThreadTest {

	public static void main(String[] args) {
		Warehouse warehouse = new Warehouse();
		ConsumptionFactory consumptionFactory = new ConsumptionFactory(warehouse);
		ProductionFactiry productionFactiry = new ProductionFactiry(warehouse);

		Thread c1 = new Thread(consumptionFactory);
		Thread c2 = new Thread(consumptionFactory);

		Thread p1 = new Thread(productionFactiry);
		Thread p2 = new Thread(productionFactiry);

		c1.start();
		c2.start();
		p1.start();
		p2.start();
	}
}

Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级
Java – 偏向锁、轻量级锁、自旋锁、重量级锁

你可能感兴趣的:(Java多线程)