多线程的理解思路梳理 + synchronized与Lock 的比较

理解多线程的根本:资源问题与锁的对象

synchronized与Lock解决的是多线程访问资源时的线程安全问题,那么这个存在线程安全问题的多线程模型中,一定满足一个条件:多个线程访问一个共享资源。

通常的实现模型是,自定义一个资源类,类中存储具体资源,以及定义资源访问的方法,这些方法就是需要提供线程安全的。

synchronized的理解

这时使用synchronized有三种实现方式:

  1. synchronized声明普通方法:调用将取得当前类对象的锁
  2. synchronized声明静态方法:调用将取得当前类的Class的锁
  3. synchronized声明代码块:进入时取得括号声明的对象的锁

普通方法和静态方法的说明:普通和静态都可以实现资源的互斥访问,只是一般当资源是静态变量时,静态的同步方法才有意义。特别注意普通方法取得的是对象锁,静态方法取得的是类锁,对象锁和类锁是不互斥的。

同步方法和同步代码块的说明:当一个资源访问方法并不是所有代码都需要进行同步,一部分代码可以先行执行的时候,可以用synchronized代码块代替方法声明。注意代码块锁定的是小括号中声明的对象,取得的仍然是对象锁,当最先进入同步代码块的线程取得了这个资源类对象的锁后,其他的线程就在代码块外阻塞等待锁的释放。尽量使用同步代码块代替同步方法可以缩小锁的粒度。

附1:生产者消费者实现代码

/*
 * synchronized同步方法在资源类Property中,意味着produce和consume方法锁定的是调用这个方法的Property对象,所以无论是多个生产者和消费者,还是一个生产者和一个消费者,只要启动了多个线程就能实现效果
 */

class Property<T>{
	private static final int MAX_LENGTH = 5;
	private String properties[];
	private int index = 0;//货物索引
	public Property() {
		this.properties = new String[MAX_LENGTH];
	}
	public synchronized void produce(String str) {
		if(this.index==5) {
			System.out.println("我是生产者:"+Thread.currentThread().getName()+",产品已满,我无法生产,马上释放锁");
			try {
				super.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		else {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			properties[index++] = str;
			System.out.println("[生产者:"+Thread.currentThread().getName()+"]生产了产品");
			super.notify();
		}
	}
	public synchronized void consume() {
		if(this.index==0) {
			System.out.println("我是消费者:"+Thread.currentThread().getName()+",没有产品,我无法消费,马上释放锁");
			try {
				super.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}else {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("[消费者:"+Thread.currentThread().getName()+"]消费了产品:"+this.properties[--this.index]);
			super.notify();
		}	
	}
}
class Producer implements Runnable{
	private Property pro;
	public Producer(Property pro) {
		this.pro = pro;
	}
	@Override
	public void run() {
		for(int x=0;x<10;x++) {
			this.pro.produce("["+Thread.currentThread().getName()+"]"+"的生产品");
		}
	}
}
class Consumer implements Runnable{
	private Property pro;
	public Consumer(Property pro) {
		this.pro = pro;
	}
	@Override
	public void run() {
		for(int x=0;x<10;x++) {
			this.pro.consume();
		}
	}
}

public class ThreadTest {
	public static void main(String[] args) {
		Property pro = new Property();
		Producer p = new Producer(pro);
		Consumer c = new Consumer(pro);
		for(int x=1;x<=5;x++) {
			new Thread(p,String.valueOf(x)).start();
		}
		for(int x=1;x<=5;x++) {
			new Thread(c,String.valueOf(x)).start();
		}
	}
}

理解Thread和Runnable的区别

Runnable接口解决的不仅仅是Thread类的单继承局限,在有些情况下,使用继承Thread类的方式无法满足同步要求。当可以单独定义资源类时,将资源类的对象设为线程实现类(如定义MyThread extends Thread)的成员,无论使用Thread还是Runnbale,只要main方法中绑定了同一资源类对象,就可以实现同步访问;但当没有这个所谓的资源类,仅仅希望实现同步方法时,这时synchronized的作用只能移到run()方法上,这意味着继承Thread时,每次new实例化一个新的线程对象,synchronized进入run()时取得的是各个不同的线程对象的锁,是无法同步的,而Runnable实现就不同,可以通过先实例化一个Runnable实现类,用这一个Runnable对象去创建多个线程,这样所有线程尝试取得的就是这个Runnable对象的锁,所以synchronized声明run()方法仍然可以实现同步

附2:Thread和Runnable都可以进行资源同步控制

class Property{
	public synchronized void fun() {
		//同步操作
	}
}
class MyThread extends Thread{
	Property pro;//资源绑定在线程实现类内部
	public MyThread(Property pro) {
		this.pro = pro;
	}
	@Override
	public void run() {
		// 资源操作
	}
}
class MyRunnable implements Runnable{
	Property pro;//资源绑定在线程实现类内部
	public MyRunnable(Property pro) {
		this.pro = pro;
	}
	public void run() {
		// 资源操作
	}
}
public class PropertiesTest {
	public static void main(String[] args) {
		Property pro = new Property();//用同一个资源类对象构造线程对象
		Thread t1 = new MyThread(pro);
		Thread t2 = new MyThread(pro);
		t1.start();
		t2.start();
		Thread r1 = new Thread(new MyRunnable(pro));
		Thread r2 = new Thread(new MyRunnable(pro));
		r1.start();
		r2.start();
	}
}

附3:Thread无法实现单纯的方法同步

class MyRunnable implements Runnable {
	@Override
	public synchronized void run() {
		for(int x=0;x<100;x++){
			System.out.println(Thread.currentThread().getName());
		}	
	}
}
class MyThread extends Thread{
	@Override
	public synchronized void run() {
		for(int x=0;x<100;x++){
			System.out.println(Thread.currentThread().getName());
		}	
	}
}
public class RunTest{	
	public static void main(String[] args) throws InterruptedException {
		/*for(int x=0;x<3;x++){	
			Thread t = new Thread(new MyRunnable(),String.valueOf(x));
			t.start();
		}*/
		for(int x=3;x<6;x++){	
			Thread t = new MyThread();
			t.setName(String.valueOf(x));
			t.start();
		}
	}
}

截取部分结果:没有实现同步
在这里插入图片描述

最后总结synchronized和Lock的区别

性能不一致:资源竞争激烈的情况下,Lock性能会比synchronized好,竞争不激烈的情况下,synchronized比Lock性能好。但jdk1.6后synchronized也加入了CAS的优化,性能大大提升。
一般认为synchronized关键字的实现是源自像信号量之类的线程同步机制,在高并发状态下,CPU消耗过多的时间在线程的调度上,从而造成了性能的极大浪费;Lock实现原理则是依赖于硬件,现代处理器都支持CAS指令。
机制不一致:synchronized是在JVM层面实现的,系统会监控锁的释放与否,无法手动中断阻塞线程;Lock是基于代码实现的,可以非阻塞地获取锁,需要手动释放,推荐在finally块中释放。
用法不一样:synchronized可以用在代码块上,方法上;Lock通过代码实现,有更精确的线程语义。synchronized只能以一个条件进行线程的休眠(wait)和唤醒(notify)控制;Lock可以获取多个条件(Condition)进行更细致的线程休眠(await)和唤醒(signal)的控制

 
 
 
(文章内容有过多方参考,来源过广,不一一列出)

猜你喜欢

转载自blog.csdn.net/CjEntus/article/details/83351351