线程同步机制是一套适用于协调线程之间的数据访问机制,该机制可以保障线程安全
java 平台提供的线程同步机制包括:锁、volatile 关键字、final 关键字,static 关键字、以及相关 API 如 object.wait/object.notify
锁概述
线程安全问题的产生前提是多个线程并发访问共享数据,将多个数据对共享数据的并发访问,转化为串行访问,即共享数据只能被一个线程访问,锁就是这种思路。
线程访问数据时必须先获得锁,获得锁的线程称为锁的持有线程,一个锁一次只能被一个线程持有,持有线程在获得锁之后和释放锁之前锁执行的代码称之为临界区。
锁具有排它性(Exclisive),即一个锁只能被一个线程持有,这种锁称为排它锁或者互斥锁。
涨姿势了!原来这才是多线程正确实现方式 JVM 部分把锁分为内部锁和显示锁,内部锁通过 Synchronized 关键字实现,显示锁通过 java.concurrent.locks.Lock 接口实现类实现的。
锁的作用锁能够实现对共享数据的安全,保障线程的原子性,可见性与有序性。
锁是通过互斥保障原子性,一个锁只能被一个线程持有,这就保证了临界区的代码一次只能被一个线程执行,使得临界区代码所执行的的操作自然而然的具有不可分割的特性,既具备了原子性。
好比一条路段所有车辆都在跑,并发执行,在经过某一个路段的时候,多车道变为一车道,一次只能通过一辆车,由并发执行改为串行执行。
可见性是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存这两个动作,锁的获得隐含着刷新处理器缓存的动作,锁的释放隐含着冲刷处理器缓存的动作。
锁能够保障有序性,写线程在临界区所执行的临界区看来像是完全按照源码顺序执行的。
锁的相关概念可重入性:一个线程持有该锁的时候能够再次/多次申请该锁如果一个线程持有一个锁的时候,还没有释放,但还能够继续成功申请该锁,称该锁可重入,反之。
锁的争用与调度 java 中内部锁属于非公平锁,显示锁支持非公平锁和公平锁
锁的粒度一个所可以保护的共享数据的数量大小称为锁的粒度。
锁保护共享数据量大,称为锁粒度粗,否则称为粒度细。
锁的粒度过粗会导致线程在申请锁时会进行不必要的等待,锁粒度过细会增加锁调度的开销。
比如银行有一个柜台一个员工可以办理开卡、销户、取现、贷款那么所有人都只能去这个柜台办理业务,会需要很长的等待时间。但是如果把业务细分,一个业务一个柜台,这时候增加了银行的开销,需要三个员工。
内部锁:SynchronizedJava 中每一个对象都有一个与之关联的内部锁,这种锁也叫监视器,是一种排它锁,可以保障原子性、可见性、排它性。
Synchronized(对象锁){同步代码块,可以在同步代码块中访问共享数据}修饰实例方法称为同步实例方法,修饰静态方法称为同步静态方法。
Synchronized 同步代码块 public class SynchronizedLock {public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();for (int i = 0; i <2 ; i++) {new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm();}}.start();}}public void mm(){for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}}两个线程的代码都在并发执行
涨姿势了!原来这才是多线程正确实现方式现在要打印的时候进行同步,同步的原理线程在执行的时候要先要获得锁
public class SynchronizedLock {public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();for (int i = 0; i <2 ; i++) {new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm();//使用锁的对象是 synchronizedLock 对象}}.start();}}public void mm(){synchronized (this)//this 作为当前对象{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}}}涨姿势了!原来这才是多线程正确实现方式因为 Synchronized 内部锁是排它锁,一次只能被一个线程持有,现在是 Thread-0 先取得锁对象,Thread-1 在等待区等待 Thread-0 执行完毕释放锁,Thread-1 获得锁再执行。
锁对象不同不能实现同步
public class SynchronizedLock {public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();SynchronizedLock synchronizedLock2=new SynchronizedLock();new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm();//使用锁的对象是 synchronizedLock 对象}}.start();new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock2.mm();//使用锁的对象是 synchronizedLock 对象}}.start();}public void mm(){synchronized (this)//this 作为当前对象{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}}}涨姿势了!原来这才是多线程正确实现方式因此想要同步必须使用同一个锁对象
使用常量作为锁对象
public class SynchronizedLock {public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();SynchronizedLock synchronizedLock2=new SynchronizedLock();new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm();//使用锁的对象是 synchronizedLock 对象}}.start();new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm();//使用锁的对象是 synchronizedLock 对象}}.start();}public static final Object obj=new Object();public void mm(){synchronized (obj)//常量作为当前对象{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}}}同步实例方法使用 synchronized 修饰实例方法,同步实例方法,默认使用 this 作为锁对象
public class SynchronizedLock {public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm();}}.start();new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm2();}}.start();}//同步实例方法 public synchronized void mm(){for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}public void mm2(){synchronized (this)//常量作为当前对象{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}}}同步静态方法使用 synchronized 修饰静态方法,同步静态方法,默认运行时使用 SynchronizedLock class 作为锁对象
public class SynchronizedLock {public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm2();}}.start();new Thread(new RunnableThread()){@Overridepublic void run() {SynchronizedLock.mm();//使用锁的对象是 SynchronizedLock.class}}.start();}//同步静态方法 public synchronized static void mm(){for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}public void mm2(){synchronized (SynchronizedLock.class)//常量作为当前对象{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}}}同步代码块和同步方法如何选择 public class SynchronizedLock {public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();new Thread(new RunnableThread()){@Overridepublic void run() {try {synchronizedLock.mm2();} catch (InterruptedException e) {e.printStackTrace();}}}.start();new Thread(new RunnableThread()){@Overridepublic void run() {try {synchronizedLock.mm2();//使用锁的对象是 SynchronizedLock.class} catch (InterruptedException e) {e.printStackTrace();}}}.start();}//同步实例方法 锁的粒度粗 执行效率低 public synchronized void mm() throws InterruptedException {long starttime= System.currentTimeMillis();System.out.println(“start”);Thread.sleep(3000);for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}System.out.println(“end”);long Endtime= System.currentTimeMillis();System.out.println(Endtime-starttime);}//同步代码块 锁的粒度细 并发效率高 public void mm2() throws InterruptedException {System.out.println(“start”);Thread.sleep(3000);synchronized (this)//常量作为当前对象{for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}System.out.println(“end”);}}在执行同步方法的时候,两次线程调用每次都需要休眠三秒,而同步代码块同时启动线程都先准备三秒,效率比较高
脏读 public class Test06 {public static void main(String[] args) throws InterruptedException {User user=new User();SubThread subThread=new SubThread(user);subThread.start();user.GetName();}static class SubThread extends Thread{public User user;
public SubThread(User user)
{
this.user=user;
}
@Override
public void run() {
user.SetValue("ww","456");
}
}
static class User
{
private String name="ylc";
private String pwd="123";
public void GetName()
{
System.out.println(Thread.currentThread().getName()+"==>"+name+"密码"+pwd);
}
public void SetValue(String name,String pwd)
{
System.out.println("原来为为name="+this.name+",pwd="+this.pwd);
this.name=name;
this.pwd=pwd;
System.out.println("更新为name="+name+",pwd="+pwd);
}
}
}涨姿势了!原来这才是多线程正确实现方式在修改数据还没有完成的时候,就读取到了原来的数据,而不是修改之后的
出现脏读的原因是对共享数据的修改和读取不同步引起的
解决办法是对修改和读取的方法进行同步方法上加上 synchronized 关键字
涨姿势了!原来这才是多线程正确实现方式线程出现异常释放锁假如在同步方法中,一个线程出现了异常,会不会没有释放锁,其他在等待的线程就在一直等待,论证:
public class SynchronizedLock {public static void main(String[] args) {SynchronizedLock synchronizedLock=new SynchronizedLock();new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm();}}.start();new Thread(new RunnableThread()){@Overridepublic void run() {synchronizedLock.mm2();}}.start();}//同步实例方法 public synchronized void mm(){for (int i = 0; i <100 ; i++) {if(i==50){Integer.parseInt(“abc”);//异常设置}System.out.println(Thread.currentThread().getName()+”–>”+i);}}public void mm2(){synchronized (this){for (int i = 0; i <100 ; i++) {System.out.println(Thread.currentThread().getName()+”–>”+i);}}}}涨姿势了!原来这才是多线程正确实现方式同步过程中线程出现异常,会自动释放锁对象,以供下一个线程继续执行
死锁多线程中可能需要使用多个锁,如果获取锁的顺序不一致,可能导致死锁。
public class Text06_5 {public static void main(String[] args) {SubThread subThread=new SubThread();SubThread subThread2=new SubThread();subThread.setName(“a”); subThread2.setName(“b”);subThread.start();subThread2.start();}static class SubThread extends Thread{private static final Object lock1=new Object();private static final Object lock2=new Object();
@Override
public void run() {
if("a".equals(Thread.currentThread().getName()))
{
synchronized (lock1)
{
System.out.println("a 线程 lock1获得了锁,再需要获得lock2");
synchronized (lock2)
{
System.out.println("a 线程 lock2获得了锁");
}
}
}
if("b".equals(Thread.currentThread().getName()))
{
synchronized (lock2)
{
System.out.println("b 线程 lock2获得了锁,再需要获得lock1");
synchronized (lock1)
{
System.out.println(" b 线程 lock1获得了锁");
}
}
}
}
}
}涨姿势了!原来这才是多线程正确实现方式程序还在运行,却进入了卡死状态,a 线程得到了 lock1,要想把该线程释放的执行下面的代码获取 lock2,而 lock2 被 b 线程获取无法释放,出现了鹬蚌相争的情况。
避免死锁:当需要获得锁时,所有线程获得锁的顺序一致,a 线程先锁 lock1,再锁 lock2,b 线程同理,就不会出现死锁了。
小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。如果在阅读过程中有疑问,请留言讨论
原文链接:https://www.cnblogs.com/cg-ww/p/14491507.html?utm_source=tuicool&utm_medium=referral
声明:本文部分素材转载自互联网,如有侵权立即删除 。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容