多线程高并发系列之synchronized锁

Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单,其原理也不难,我在这里做下总结:

  1. 当synchronized修饰在代码块的时候,通过查看编译后的代码得知,是通过指令monitorenter和monitorexit来完成的,进入则monitorenter+1,如果没有持有锁,则设置为1,如果持有锁,则加1。
  2. 当synchronized修饰在方法上时,通过编译后得治,不是通过指令monitorenter和monitorexit来完成的,但其在常量池中多了ACC_SYNCHRONIZED标示符,根据此标识符去操作monitor,所以这种方式也称为是通过隐式的方式实现的,其本质相同

synchronized使用

  1. 上面也说了,synchronized可以写在方法上,也可以写在代码块上,只是有可能锁的类型不同,如果写在方法上,锁的就是当前类的对象,在代码块上使用,可以指定锁的对象。如果作用在静态代码块上,锁的就是指定的类的class对象了
  2. synchronized锁属于全自动类型的,自动上锁解锁,而且在执行代码的过程中,如果抛异常了,synchronized也会自动释放锁
  3. synchronized锁是可重入锁,就是当前线程调用A的方法,A方法中调用B方法,可以直接调用,不用竞争锁。jvm这样设计,也是为了防止死锁的产生,所以,在这里你应该也能明白了,为什么当前线程访问A方法时,其他线程访问其他方法时,不能让其访问的原因了。或者这样说,T1线程访问A方法时,T2线程也来访问A方法,肯定不被允许的,这不不用多说,因为锁的是一个对象,那个对象被T1线程锁着呢,T2线程当然获取不了锁。那相同的,T2线程去访问B方法时,B方法中锁的不也是同一个对象吗,也就是说T2线程还要去获取T1线程在A方法中锁定的对象的锁。
  4. threadlocal线程私有变量,举个例子你就知道了:
public class ThreadLocalTest {
	class Person {
		String name = "zhangsan";
	}
	@Test
	public void threadLocal1() {
		ThreadLocal<Person> tl = new ThreadLocal<Person>();
		Person person = new Person();
		tl.set(person);
		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("新启线程"+tl.get());
			}
		}).start();
		System.out.println(tl.get());
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

新启的线程打印person为null,也就是说,通过threadlocal设置的对象,只能在当前线程中使用,其他线程获取为空。

  1. volatile关键字请点击跳转
  2. 重点说下notify和wait,因为这里面的坑,稍不注意就掉进去了,之前写过一篇,我再举个生产者、消费者的例子:
    需求:有2个生产者线程、10个消费者线程,如果容器满了,则生产者暂停,如果容器空了,则消费者暂停
public class ProducerAndConsumer {
	//有2个生产者线程、10个消费者线程,如果容器满了,则生产者暂停,如
	//果容器空了,则消费者暂停
	final private LinkedList list = new LinkedList();
	final private int maxValue = 10;
	/**
	 * 生产者,按照要求:当容器满了,就停止。
	 */
	public synchronized void producer(Object o) {
		/**
		 * 最初的实现是用if,但是为什么又改成if而改成while呢:(这是
		 * 一个坑,很容易掉里面)
		 * 	1.你想啊,如果此时,容器满了,然后第一个线程到这后,判断
		 *    if逻辑,然后执行wait方法进行等待,并【释放当前对象锁】
		 *  2.然后cpu调起哪个线程,是随机选择的,如果此时,又调起了一
		 *    个生产者线程,那么也执行到了这个wait方法。
		 *  3.目前已经有两个线程在wait这了,然后cpu执行消费者线程消费
		 *    了1个,现在容器中还有9个,然后消费者线程唤醒通过
		 *    notifyall唤醒线程,唤醒了这 两个生产者线程,那么:
		 *  	3.1 首先第一个生产者线程开始执行,从wait处开始往下执
		 *          行,添加元素,容器又满了,释放锁
		 *  	3.2 然后第二个生产者线程开始执行,也是从wait处开始执
		 *          行,因为使用的不是while循环,所以会直接往下执行,
		 *          也就是添加元素,那么容器大小就不是10 个了。
		 *  4.所以,使用wait时的场景,99%都是与while循环配合使用
		 *  5.如果wait和notify不写在synchronized锁里面,会抛
		 *    IllegalMonitorStateException异常,为什么呢:参考博客
		 *    https://www.cnblogs.com/set-cookie/p/8686218.html
		 */
//		if(list.size()==maxValue) {
		while(list.size()==maxValue) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		list.add(o);
		//容器中有东西了,唤醒等待的线程线程进行消费(唤醒的线程中包
		//括生产者线程和消费者线程),其主要目的是消费者线程
		this.notifyAll();
	}
	/**
	 * 消费者
	 */
	public  synchronized Object consumer() {
		while(list.size()==0) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		//我拿走东西了,唤醒等待的线程进行生产(唤醒的线程中包括生产
		//者线程和消费者线程),其主要目的是生产者线程
		Object o = list.removeFirst();
		this.notifyAll();
		return o;
	}
	public static void main(String[] args) {
		ProducerAndConsumer pcTest = new ProducerAndConsumer();
		//启动消费者线程
		for(int i=0;i<10;i++) {
			new Thread(()->{
				for(int j=0;j<10;j++) {
					System.out.println(pcTest.consumer()+"aa");
				}
			},"c"+i) .start();
		}
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//启动生产者线程
		for(int i=0;i<2;i++) {
			new Thread(()->{
				for(int j=0;j<30;j++) {
				  pcTest.producer(Thread.currentThread().getName());
				}
			},"p"+i) .start();
		}
	}
  1. 线程停止stop、interrupt方法,
    thread.stop(),是直接把你干掉了,没有一点余地的直接kill掉,都不会进入catch中,所以不到万不得已,不要用stop方法。
    thread.interrupt()时,线程还是可以进下面catch中的代码的。
    但是这两个方法都不被推荐,更理想的办法是定义变量,:
public class ThreadStopByArgs {
	/**
	 * 通过变量的形式,是线程进行停止
	 *
	 */
	public static void main(String[] args) {
		ThreadRunnerTest threadRunnerTest = new ThreadRunnerTest();
		Thread thread = new Thread(threadRunnerTest);
		thread.start();
		try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("stop");
		threadRunnerTest.changeFlag();
	}
}
class ThreadRunnerTest implements Runnable {
	private volatile boolean flag = true;
	public void run() {
		int i=0;
		while(flag) {
			//System.out.println(i++);测试多线程时,如果使用syso输
			//出,要小心使用,因为syso中有sync
		}
	}
	public void changeFlag(){
		flag = false;
	}
}
  1. 其他的方法,比如sleep、yield、join、stop、interrupt等方法,请您自行学习吧,没啥好记得
    锁必须锁的是同一个对象的基础上的,如同:你卫生间还能有多个门吗?也就是说,访问共享的资源时,只有一个门能让你去访问,这样才能控制共享资源和线程的安全性。安全的意义并不只针对车祸那种类型的,只要预期结果被改变是因为在执行过程中被其他因素干扰导致的,就属于不安全。
    参考博客:synchronized原理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值