热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

多线程具体实现

Java内存模型线程同步锁概述锁的作用锁的相关概念可重入性:一个线程持有该锁的时候能够再次多

Java内存模型

线程同步锁概述锁的作用锁的相关概念可重入性:一个线程持有该锁的时候能够再次/多次申请该锁锁的争用与调度锁的粒度内部锁:SynchronizedSynchronized同步代码块锁对象不同不能实现同步使用常量作为锁对象同步实例方法同步静态方法同步代码块和同步方法如何选择脏读线程出现异常释放锁死锁

锁概述锁的作用锁的相关概念可重入性:一个线程持有该锁的时候能够再次/多次申请该锁锁的争用与调度锁的粒度

锁的作用

锁的相关概念

可重入性:一个线程持有该锁的时候能够再次/多次申请该锁

锁的争用与调度

锁的粒度

内部锁:SynchronizedSynchronized同步代码块锁对象不同不能实现同步使用常量作为锁对象同步实例方法同步静态方法同步代码块和同步方法如何选择脏读线程出现异常释放锁死锁

Synchronized同步代码块

锁对象不同不能实现同步

使用常量作为锁对象

同步实例方法

同步静态方法

同步代码块和同步方法如何选择

脏读

线程出现异常释放锁

死锁


Java内存模型




线程同步

线程同步机制是一套适用于协调线程之间的数据访问机制,该机制可以保障线程安全

java平台提供的线程同步机制包括:锁、volatile关键字、final关键字,static关键字、以及相关API如object.wait/object.notify


锁概述

线程安全问题的产生前提是多个线程并发访问共享数据,将多个数据对共享数据的并发访问,转化为串行访问,即共享数据只能被一个线程访问,锁就是这种思路。

线程访问数据时必须先获得锁,获得锁的线程称为锁的持有线程,一个锁一次只能被一个线程持有,持有线程在获得锁之后和释放锁之前锁执行的代码称之为临界区。

锁具有排它性(Exclisive),即一个锁只能被一个线程持有,这种锁称为排它锁或者互斥锁。


JVM部分把锁分为内部锁和显示锁,内部锁通过Synchronized关键字实现,显示锁通过java.concurrent.locks.Lock接口实现类实现的。


锁的作用

锁能够实现对共享数据的安全,保障线程的原子性,可见性与有序性。

锁是通过互斥保障原子性,一个锁只能被一个线程持有,这就保证了临界区的代码一次只能被一个线程执行,使得临界区代码所执行的的操作自然而然的具有不可分割的特性,既具备了原子性。

好比一条路段所有车辆都在跑,并发执行,在经过某一个路段的时候,多车道变为一车道,一次只能通过一辆车,由并发执行改为串行执行。

可见性是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存这两个动作,锁的获得隐含着刷新处理器缓存的动作,锁的释放隐含着冲刷处理器缓存的动作。

锁能够保障有序性,写线程在临界区所执行的临界区看来像是完全按照源码顺序执行的。


锁的相关概念


可重入性:一个线程持有该锁的时候能够再次/多次申请该锁

如果一个线程持有一个锁的时候,还没有释放,但还能够继续成功申请该锁,称该锁可重入,反之。


锁的争用与调度

java中内部锁属于非公平锁,显示锁支持非公平锁和公平锁


锁的粒度

一个所可以保护的共享数据的数量大小称为锁的粒度。

锁保护共享数据量大,称为锁粒度粗,否则称为粒度细。

锁的粒度过粗会导致线程在申请锁时会进行不必要的等待,锁粒度过细会增加锁调度的开销。

比如银行有一个柜台一个员工可以办理开卡、销户、取现、贷款那么所有人都只能去这个柜台办理业务,会需要很长的等待时间。但是如果把业务细分,一个业务一个柜台,这时候增加了银行的开销,需要三个员工。


内部锁:Synchronized

Java中每一个对象都有一个与之关联的内部锁,这种锁也叫监视器,是一种排它锁,可以保障原子性、可见性、排它性。

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())
{
@Override
public 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())
{
@Override
public 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())
{
@Override
public void run() {
synchronizedLock.mm();//使用锁的对象是synchronizedLock对象
}
}.start();
new Thread(new RunnableThread())
{
@Override
public 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())
{
@Override
public void run() {
synchronizedLock.mm();//使用锁的对象是synchronizedLock对象
}
}.start();
new Thread(new RunnableThread())
{
@Override
public 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())
{
@Override
public void run() {
synchronizedLock.mm();
}
}.start();
new Thread(new RunnableThread())
{
@Override
public 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())
{
@Override
public void run() {
synchronizedLock.mm2();
}
}.start();
new Thread(new RunnableThread())
{
@Override
public 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())
{
@Override
public void run() {
try {
synchronizedLock.mm2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread(new RunnableThread())
{
@Override
public 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())
{
@Override
public void run() {
synchronizedLock.mm();
}
}.start();
new Thread(new RunnableThread())
{
@Override
public 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线程同理,就不会出现死锁了。



推荐阅读
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • ejava,刘聪dejava
    本文目录一览:1、什么是Java?2、java ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 深入理解线程、进程、多线程、线程池
    本文以QT的方式来走进线程池的应用、线程、进程、线程池、线程锁、互斥量、信号量、线程同步等的详解,一文让你小白变大神!为什么要使用多线程、线程锁、互斥量、信号量?为什么需要线程 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文详细介绍了云服务器API接口的概念和作用,以及如何使用API接口管理云上资源和开发应用程序。通过创建实例API、调整实例配置API、关闭实例API和退还实例API等功能,可以实现云服务器的创建、配置修改和销毁等操作。对于想要学习云服务器API接口的人来说,本文提供了详细的入门指南和使用方法。如果想进一步了解相关知识或阅读更多相关文章,请关注编程笔记行业资讯频道。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
author-avatar
沉稳之固_300
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有