java多线程和线程安全——synchronized、volatile

线程安全的主要诱因

  • 存在共享数据(也称临界资源)
  • 存在多条线程共同操作这些共享数据

解决问题的根本方法:

同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据迸行操作。

synchronized

synchronized锁的不是代码,锁的都是对象

互斥锁的特性

  • 互斥性:即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一时间只有一个线程对需要同步的代码块(复合操作)进行访问。互斥性也称为操作的原子性。
  • 可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致。

根据获取的锁的分类:获取对象锁和获取类锁

获取对象锁的两种用法

  • 同步代码块( synchronized (this), synchronized(类实例对象)),锁是小括号()中的实例对象
  • 同步非静态方法( synchronized method),锁是当前对象的实例对象。

获取类锁的两种用法

  • 同步代码块( synchronized(类cass)),锁是小括号()中的类对象(Cass对象)
  • 同步静态方法( synchronized static method),锁是当前对象的类对象( Class对象)

不同锁的对象:

package com.lmm.Multithreading;

import java.text.SimpleDateFormat;
import java.util.Date;

public class SyncThread implements Runnable {

	@Override
	public void run() {
		String threadName = Thread.currentThread().getName();
		if (threadName.startsWith("A")) {// 异步方法
			async();
		} else if (threadName.startsWith("B")) {
			syncObjectBlock1();
		} else if (threadName.startsWith("C")) {
			syncObjectMethod1();
		} else if (threadName.startsWith("D")) {
			syncClassBlock1();
		} else if (threadName.startsWith("E")) {
			syncClassMethod1();
		}

	}

	/**
	 * 异步方法
	 */
	private void async() {
		try {
			System.out.println(Thread.currentThread().getName()
					+ "_Async_Start: "
					+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
			Thread.sleep(1000);
			System.out.println(Thread.currentThread().getName()
					+ "_Async_End: "
					+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 方法中有 synchronized(this|object) {} 同步代码块 被修饰的代码谁先开始,谁先结束,其他抢占
	 */
	private void syncObjectBlock1() {
		System.out.println(Thread.currentThread().getName()
				+ "_SyncObjectBlock1: "
				+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
		synchronized (this) {// 获取对象锁
			try {
				System.out.println(Thread.currentThread().getName()
						+ "_SyncObjectBlock1_Start: "
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
				Thread.sleep(1000);
				System.out.println(Thread.currentThread().getName()
						+ "_SyncObjectBlock1_End: "
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * synchronized 修饰非静态同步方法 谁先开始,谁先运行
	 */
	private synchronized void syncObjectMethod1() {
		System.out.println(Thread.currentThread().getName()
				+ "_SyncObjectMethod1: "
				+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
		try {
			System.out.println(Thread.currentThread().getName()
					+ "_SyncObjectMethod1_Start: "
					+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
			Thread.sleep(1000);
			System.out.println(Thread.currentThread().getName()
					+ "_SyncObjectMethod1_End: "
					+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	// 效果同synchronized修饰同步代码块
	private void syncClassBlock1() {
		System.out.println(Thread.currentThread().getName()
				+ "_SyncClassBlock1: "
				+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
		synchronized (SyncThread.class) {
			try {
				System.out.println(Thread.currentThread().getName()
						+ "_SyncClassBlock1_Start: "
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
				Thread.sleep(1000);
				System.out.println(Thread.currentThread().getName()
						+ "_SyncClassBlock1_End: "
						+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	// 效果同synchronized修饰非静态方法
	private synchronized static void syncClassMethod1() {
		System.out.println(Thread.currentThread().getName()
				+ "_SyncClassMethod1: "
				+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
		try {
			System.out.println(Thread.currentThread().getName()
					+ "_SyncClassMethod1_Start: "
					+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
			Thread.sleep(1000);
			System.out.println(Thread.currentThread().getName()
					+ "_SyncClassMethod1_End: "
					+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

测试代码

package com.lmm.Multithreading;

public class SyncDemo {
	public static void main(String... args) {
		System.out
				.println("共享同一个对象锁,被synchronized修饰的区域,一旦start只能等到end,才能由另一个被synchronized修饰的区域start");
		SyncThread syncThread = new SyncThread();
		Thread A_thread1 = new Thread(syncThread, "A_thread1");
		Thread A_thread2 = new Thread(syncThread, "A_thread2");
		Thread B_thread1 = new Thread(syncThread, "B_thread1");
		Thread B_thread2 = new Thread(syncThread, "B_thread2");
		Thread C_thread1 = new Thread(syncThread, "C_thread1");
		Thread C_thread2 = new Thread(syncThread, "C_thread2");
		Thread D_thread1 = new Thread(syncThread, "D_thread1");
		Thread D_thread2 = new Thread(syncThread, "D_thread2");
		Thread E_thread1 = new Thread(syncThread, "E_thread1");
		Thread E_thread2 = new Thread(syncThread, "E_thread2");
		A_thread1.start();
		A_thread2.start();
		B_thread1.start();
		B_thread2.start();
		C_thread1.start();
		C_thread2.start();
		D_thread1.start();
		D_thread2.start();
		E_thread1.start();
		E_thread2.start();
		System.out.println("同一个类的不同对象锁之间互不干扰,是异步的");
		Thread A_thread3 = new Thread(new SyncThread(), "A_thread3");
		Thread A_thread4 = new Thread(new SyncThread(), "A_thread4");
		Thread B_thread3 = new Thread(new SyncThread(), "B_thread3");
		Thread B_thread4 = new Thread(new SyncThread(), "B_thread4");
		Thread C_thread3 = new Thread(new SyncThread(), "C_thread3");
		Thread C_thread4 = new Thread(new SyncThread(), "C_thread4");
		A_thread3.start();
		A_thread4.start();
		B_thread3.start();
		B_thread4.start();
		C_thread3.start();
		C_thread4.start();

		System.out.println("一个类只有一个类锁,所以在新建类对象的时候其实是同一把类锁,所以表现为同步的");
		Thread D_thread3 = new Thread(new SyncThread(), "D_thread3");
		Thread D_thread4 = new Thread(new SyncThread(), "D_thread4");
		Thread E_thread3 = new Thread(new SyncThread(), "E_thread3");
		Thread E_thread4 = new Thread(new SyncThread(), "E_thread4");
		D_thread3.start();
		D_thread4.start();
		E_thread3.start();
		E_thread4.start();
	}
}

测试结果(每次运行都不同,但是由于锁,有些先后顺序是一定的,可以按ABCDE分开执行试试)

共享同一个对象锁,被synchronized修饰的区域,一旦start只能等到end,才能由另一个被synchronized修饰的区域start
同一个类的不同对象锁之间互不干扰,是异步的
一个类只有一个类锁,所以在新建类对象的时候其实是同一把类锁,所以表现为同步的
E_thread1_SyncClassMethod1: 10:06:23
A_thread1_Async_Start: 10:06:23
D_thread1_SyncClassBlock1: 10:06:23
D_thread3_SyncClassBlock1: 10:06:23
D_thread4_SyncClassBlock1: 10:06:23
E_thread1_SyncClassMethod1_Start: 10:06:23
A_thread2_Async_Start: 10:06:23
B_thread4_SyncObjectBlock1: 10:06:23
A_thread3_Async_Start: 10:06:23
C_thread3_SyncObjectMethod1: 10:06:23
B_thread4_SyncObjectBlock1_Start: 10:06:23
C_thread4_SyncObjectMethod1: 10:06:23
B_thread1_SyncObjectBlock1: 10:06:23
B_thread3_SyncObjectBlock1: 10:06:23
D_thread2_SyncClassBlock1: 10:06:23
A_thread4_Async_Start: 10:06:23
C_thread1_SyncObjectMethod1: 10:06:23
B_thread2_SyncObjectBlock1: 10:06:23
C_thread1_SyncObjectMethod1_Start: 10:06:23
C_thread4_SyncObjectMethod1_Start: 10:06:23
B_thread3_SyncObjectBlock1_Start: 10:06:23
C_thread3_SyncObjectMethod1_Start: 10:06:23
B_thread3_SyncObjectBlock1_End: 10:06:24
A_thread3_Async_End: 10:06:24
C_thread1_SyncObjectMethod1_End: 10:06:24
E_thread1_SyncClassMethod1_End: 10:06:24
B_thread2_SyncObjectBlock1_Start: 10:06:24
A_thread1_Async_End: 10:06:24
B_thread4_SyncObjectBlock1_End: 10:06:24
D_thread2_SyncClassBlock1_Start: 10:06:24
C_thread3_SyncObjectMethod1_End: 10:06:24
A_thread2_Async_End: 10:06:24
A_thread4_Async_End: 10:06:24
C_thread4_SyncObjectMethod1_End: 10:06:24
B_thread2_SyncObjectBlock1_End: 10:06:25
B_thread1_SyncObjectBlock1_Start: 10:06:25
D_thread2_SyncClassBlock1_End: 10:06:25
D_thread4_SyncClassBlock1_Start: 10:06:25
B_thread1_SyncObjectBlock1_End: 10:06:26
C_thread2_SyncObjectMethod1: 10:06:26
C_thread2_SyncObjectMethod1_Start: 10:06:26
D_thread4_SyncClassBlock1_End: 10:06:26
D_thread3_SyncClassBlock1_Start: 10:06:26
C_thread2_SyncObjectMethod1_End: 10:06:27
D_thread3_SyncClassBlock1_End: 10:06:27
D_thread1_SyncClassBlock1_Start: 10:06:27
D_thread1_SyncClassBlock1_End: 10:06:28
E_thread4_SyncClassMethod1: 10:06:28
E_thread4_SyncClassMethod1_Start: 10:06:28
E_thread4_SyncClassMethod1_End: 10:06:29
E_thread3_SyncClassMethod1: 10:06:29
E_thread3_SyncClassMethod1_Start: 10:06:29
E_thread3_SyncClassMethod1_End: 10:06:30
E_thread2_SyncClassMethod1: 10:06:30
E_thread2_SyncClassMethod1_Start: 10:06:30
E_thread2_SyncClassMethod1_End: 10:06:31

对象锁和类锁的总结

  1. 有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块
  2. 若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步代码块的线程会被阻塞
  3. 若锁住的是同一个对象,一个线程在访问对象的同步方法时,另一个访问对象同步方法的线程会被阻塞
  4. 一个访问对象同步方法的线程会被阻塞,反之亦然;
  5. 同一个类的不同对象的对象锁互不干扰
  6. 类锁由于也是一种特殊的对象锁,因此表现和上述1,2,3,4一致,而由于—个类只有一把对象锁,所以同一个类的不同对象使用类锁将会是同步的
  7. 类锁和对象锁互不干扰

synchronized底层实现原理

Java对象头

对象头含有三部分:Mark Word(存储对象自身运行时数据)、Class Metadata Address(存储类元数据的指针)、Array length(数组长度,只有数组类型才有)。

重点在Mark Word部分,Mark Word数据结构被设计成非固定的,会随着对象的不同状态而变化,如下图所示。

Monitor

Monitor可以理解为一种同步工具,也可理解为一种同步机制,常常被描述为一个Java对象。

  • 互斥:一个Monitor在一个时刻只能被一个线程持有,即Monitor中的所有方法都是互斥的。
  • signal机制:如果条件变量不满足,允许一个正在持有Monitor的线程暂时释放持有权,当条件变量满足时,当前线程可以唤醒正在等待该条件变量的线程,然后重新获取Monitor的持有权。

所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。

Monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,成本非常高。

自旋锁与自适应自旋锁

自旋锁

  • 许多情况下,共享数据的锁定状态持续时间较短,切换线程不值得
  • 通过让线程执行忙循环等待锁的释放,不让出CPU
  • 缺点:若锁被其他线程长时间占用,会带来许多性能上的开销

自适应自旋锁

  • 自旋的次数不再固定
  • 由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定

锁消除

JVM编译时,对运行上下文进行扫描,去除不可能存在竞争的锁

package com.lmm.Multithreading;

/**
 * @author lmm E-mail:violet_mmhh@163.com
 * @time 时间:2019年8月26日
 * @function 功能:
 */
public class StringBufferWithoutSync {
	public void add(String str1, String str2) {
		// StringBuffer是线程安全,由于sb只会在append方法中使用,不可能被其他线程引用
		// 因此sb属于不可能共享的资源,JVM会自动消除内部的锁
		StringBuffer sb = new StringBuffer();
		sb.append(str1).append(str2);
	}

	public static void main(String[] args) {
		StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
		for (int i = 0; i < 1000; i++) {
			withoutSync.add("aaa", "bbb");
		}
	}

}

锁粗化

通过扩大加锁的范围,避免反复加锁和解锁

public class CoarseSync {
	public static String copyString100Times(String target) {
		int i = 0;
		StringBuffer sb = new StringBuffer();
		while (i < 100) {
			sb.append(target);
		}
		return sb.toString();
	}
}

synchronized的四种状态

总共有4种锁状态,级别由低到高依次为:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。这几个状态会随着竞争情况逐渐升级。(注意:锁可以升级但不能降级

无锁是指没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功。

偏向锁是指当一段同步代码一直被同一个线程所访问时,即不存在多个线程的竞争时,那么该线程在后续访问时便会自动获得锁,从而降低获取锁带来的消耗,即提高性能。

核心思想:如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word结构也变成偏向锁结构,当该线程再次请求锁的时候,无需再做任何的同步操作,即获取锁的过程只需要检查Mark Word的锁标记位为偏向锁以及当前线程ID等于Mark Word的ThReadID即可,这样就省略了大量有关锁申请的操作。(因此它不适用于锁竞争比较激烈的场合)。

轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。

适用场景:线程交替执行同步块。

若存在同一时间访问同一锁的情况,就会导致轻量级锁升级为重量级锁。

重量级锁是指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。

锁的内存语义

  • 当线程释放锁的时候,java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中。
  • 而当线程获取锁的时候,java内存模型会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主存中读取共享变量。

总结

synchronized和ReentrantLock的区别

ReentrantLock(再入锁/重入锁)

jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock。虽然在性能上ReentrantLock和synchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。

  • import java.util.concurrent.locks.*;
  • 与CountDownLatch、FutureTask、Semaphore一样基于AQS(AbstractQueuedSynchronizer)实现;
  • 能够实现比synchronized更细粒度的控制,如控制fairness
  • 调用Lock()之后,必须调用Unlock()释放锁;
  • 性能未必比synchronized高,并且也是可重入(当一个线程试图获取他已经获取的锁时,这个获取就会自动成功)的;

ReentrantLock的公平性设置

  • ReentrantLock fairLock = new ReentrantLock(true);//给一个true的参数就会变成公平锁
  • 参数为true的时候,倾向于将锁赋予等待时间最长的线程
  • 公平锁:获取锁的顺序按照先后lock()方法的顺序(慎用)
  • 非公平锁:抢占的顺序不一定,看运气
  • synchronized是非公平锁。

ReentrantLock 将锁对象化

  • 判断是否有线程或者某个特定线程,在排队等待获取锁。
  • 带超时获取锁的尝试。
  • 感知到有没有成功获取锁。

能否将wait、notify和notifyall对象化(答案是肯定的)

通过import java.util.concurrent.locks.Condition实现

总结

  • synchronized是关键字,ReentrantLock是类;
  • ReentrantLock可以获取锁的等待时间进行设置,避免死锁;
  • ReentrantLock可以获取各种锁的信息;
  • ReentrantLock可以灵活的实现多路通知;
  • 机制:synchronized操作Mark Word,ReentrantLock底层调用Unsafe类的pack()方法

volatile和synchronized的区别

  1. volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  2. volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
  3. volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
  4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

volatile的两个作用(参考

保证内存的可见性

volatile的特殊规则就是:

  • read、load、use动作必须连续出现
  • assign、store、write动作必须连续出现

所以,使用volatile变量能够保证:

  • 每次读取前必须先从主内存刷新最新的值。
  • 每次写入后必须立即同步回主内存当中。

也就是说,volatile关键字修饰的变量看到的随时是自己的最新值。线程1中对变量v的最新修改,对线程2是可见的。

防止指令重排序

volatile关键字通过“内存屏障”来防止指令被重排序。

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。然而,对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,Java内存模型采取保守策略。

下面是基于保守策略的JMM内存屏障插入策略:

  • 在每个volatile写操作的前面插入一个StoreStore屏障。
  • 在每个volatile写操作的后面插入一个StoreLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadLoad屏障。
  • 在每个volatile读操作的后面插入一个LoadStore屏障。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值