c#!江湖救急 下载!!!!

多个格式相同的EXCEL文件合并(每个EXCEL都有多个SHEET),网上找了一代码,修改了下,但还需要进一步处理,可是我不会,所以来这请教高手们了。

这段代码导致我有几个SHEET就要写几次,好麻烦呀.....

页面中,有一个按钮,点击按钮通过js create 了一个 script标签 ,链接加载一个外部js文件,执行该js文件

该页面在所有浏览器都正常,但webbrowser里js文件就是加载不进来,感兴趣的朋友可以试一下~ 苦恼~

fiddle中显示下图红色圈,不知道什么意思~~~~~

ss:页面没有修改权限

学习多线程,力扣君给你讲个故事吧!

这里还有一篇关于正则表达式的文章。感兴趣的话可以一起看看~

NPR:“欢迎来到多线程的国度,勇士!”

你:“你你你,你不是正则王国的 NPC 吗?怎么又跑到多线程王国来了?”

NPR:“呃,你认错人了,那是我的双胞胎弟弟,我是他的哥哥 NPR。”

NPR 一脸天真无邪的微笑望着你,一时间你竟分不清这个所谓的 NPR 是真是假。

你:“行吧,先不管那么多了,我到你们王国来,实在是江湖救急,有事相求。”

NPR:“说来听听。”每天来多线程王国求助的人络绎不绝,NPR 早已见怪不怪。

你:“我写了一段读写程序,但读写后的结果总是不对,可我怎么看逻辑都是正确的,您能帮我看看吗?”

说着,你亮出了自己写的代码:

// 睡眠一秒保证线程执行完成

你:“这段代码简单得不能再简单了,这个类里只有一个 number 变量,我开启了一个新线程将它加了 10000 次,又开了一个线程让它减 10000 次,按理说结果肯定是 0,但我执行时,每次结果都不一样,就没有一次是 0。”

“我已经反反复复看了好多遍了”,你重复道,“可我怎么看都没有错,我想要么是我电脑的硬件问题,要么是 Java 基础库出了问题,或者是由于太阳黑子最近比较活跃,干扰了宇宙射线导致的。”

NPR 睁大了眼睛:“宇宙射线?哈,真是个毛头小子,程序可不是物理世界。看来你还不知道 Java 编程第一法则。”

说着,NPR 身前亮出几个鎏金大字:

Java 编程第一法则:程序出问题时,从自己的代码找原因,永远不要怀疑 Java 基础库。

NPR:“在我们多线程王国的人看来,你的这段代码错得很明显。当多个线程同时进行读写操作时,要保证结果正确,必须保证每一步操作都是原子操作。”

你:“原子操作?刚才你还说程序不是物理世界,现在怎么扯到原子了。”

NPR:“原子是元素能存在的最小单位,也就是说原子是不可分割的。这里借鉴了原子的概念,原子操作是指不能被中断的一个或一系列操作。”

你:“我还是不太明白,您可以先给我解释一下我的程序为什么出错吗?”

NPR:“没问题。你可知道,在你的 write 方法执行时,实际上会执行三条语句。”

NPR:“这三条语句的意思是:程序先从主内存中拷贝一个 number 的副本到本地线程中,增加后再回写到主内存。所以两个线程同时执行 write(1) 和 write(-1) 时可能出现这样一种情况:

  • 第一个线程拷贝了主内存中的 number,假设此时主内存中 number 的值为 0
  • 第一个线程被操作系统暂停
  • 操作系统调度了第二个线程,第二个线程拷贝了主内存中的 number,值为 0
  • 第二个线程将本地副本中的 number 减少 1,第二个线程的本地副本中的 number 变为 -1
  • 第二个线程中的值回写到主内存,主内存中的 number 变成 -1
  • 第一个线程被系统继续调度,本地副本中的 number 增加 1,第一个线程的本地副本中的 number 变为 1
  • 第一个线程中的值回写到主内存,number 变成 1

你看,执行了一次 +1,执行了一次 -1,因为不是原子操作,第一个线程被系统中断了一次,导致两次运算的最终结果不是 0,而是 1。”

NPR:“同样的,还存在另一种情况,如果第二个线程拷贝了副本后,第一个线程先回写到主内存,number 变成 1 ,然后第二个线程中的 -1 回写主内存,就会导致结果变成 -1,所以说你执行多次,有时候大于 0 ,有时候小于 0。就是因为这个原因。”

第三回 独一无二的钥匙

你:“可是为什么操作系统不把我一个线程执行完后,再去执行另一个线程呢?这样就不会有这个问题了。”

NPR:“那是因为操作系统需要提高电脑运行效率。线程的调度完全是由操作系统决定的,程序不能自己决定什么时候执行,以及执行多长时间。

你:“那就没有什么其他办法了吗?多个线程同时读写是一个很常见的需求啊,不能自己控制岂不是漏洞百出?”

NPR:“办法当然有,试想一下,如果我们制造一把钥匙,这把钥匙独一无二。在一个线程执行 write 前,先检查这个线程有没有拿到钥匙,有钥匙的话我们才让它执行,执行完再把钥匙交出来。没有拿到钥匙的线程就先等待钥匙。这样是不是就能保证一次只有一个线程能执行 write 了。”

你:“有点意思,你说的这个钥匙像是一个通行证,因为通行证只有一个,所以每次只有一个线程能拿到通行证,确实能保证一次只有一个线程执行。”

NPR:“你可以尝试用伪代码实现它吗?”

你:“没问题,代码应该是类似这样的。”

NPR:“没错,Java 里的 sychronized 关键字就是用来实现这个功能的,实际代码和这个差不多。”

你:“为什么没有看到交出钥匙的代码呢?”

NPR:“因为交出钥匙是每次都会执行的操作,所以被封装到 synchronized 中了,当程序执行到 synchronized 的 } 时,就会交出钥匙。另外,在我们多线程王国,一般不把它称之为钥匙,而是按照它的功能,将其称之为锁。”

你:“我把我的程序中的 write 方法换成这样,果然每次执行都是 0 了。真是太感谢您了,NPR 先生!”

NPR:“别客气,先别高兴地太早,现在我们给 write 方法加上锁后,写入时没有问题了,读取时还是有问题的。”

你:“我想一想,现在读取时不需要获取钥匙,所以读取时可以直接将主内存中的 number 值拷贝到自己的工作内存。而此时有可能存在线程正在写入值,这就会导致读取线程无法读到写入后的最新值!”

NPR:“没错,就是这个问题,我们写个测试类来验证一下。”

// 睡眠一秒保证线程执行完成

你:“果然如此,所以我要给 read 中的代码也加上判断,它也要拿到钥匙后才能读取,这样就能保证读取时不会有写操作,写的时候也没有读取操作了。”

NPR:“很好,运行结果没有错,说明我们确实解决了多线程竞争的问题。但这样的流程往往并不是我们想要的。更常见的需求是写入全部完成后,再去读取值。”

你:“没错,不过这难不倒我,我可以增加一个标志位来实现这个功能。”

// 标志是否写入完成 // 如果还没有写入完成,循环等待直到写入完成 // 睡眠一秒保证线程执行完成

你:”我增加了一个标志位 writeComplete,在写入完成后,将它置为 true,读取时循环等待,直到它变成 true 时才读取结果。“

NPR 不置可否,说道:“你运行一下试试。”

你:“???为什么写入一会之后,就一直卡在等待写入完成?”

NPR 抬头看了看天,若有所思地说:“据说太阳黑子每 11 年活跃一次,上一次活跃还是 2009 年,最近也差不多该发出新一轮的宇宙射线了。”

你翻了个白眼,感叹道:“你和你的双胞胎弟弟还真是如出一辙,都喜欢阴阳怪气地取笑人。快给我讲讲我的程序是哪里出了问题吧!”

NPR:“哈哈,看来你终于领悟了 Java 编程第一法则。这次的问题在于现在读写方法都需要获取锁,一旦进入 read 函数,write 函数就必须等待,直到 read 函数释放锁。这会导致什么问题?”

你:“ write 函数在等待 read 函数,write 函数中的循环无法执行完,那么 writeComplete 就无法被置为 true,所以 read 函数就会无限循环。啊,这样就陷入死局了!这可怎么办?”

NPR 微微一笑,身前再次亮起几个鎏金大字:

Java 编程第二法则:当你无法解决问题的时候,往往说明了现有知识量储备不足。

NPR:“单用 synchronized 是无法实现这个功能的,但在解决问题之前,我们最好先搞清楚我们想要的是什么。”

你:“我想要的效果是:写入操作不受限制;如果写入还没有完成,read 方法先进入等待状态。write 方法写入完成后,通知 read 开始读取。”

NPR:“很好,像上次一样,先尝试写一下伪代码吧!”

你:“好的,我想 read 方法中应该有一个等待方法,并且这个等待方法不能阻塞写入过程。”

// 如果还没有写入完成,循环等待直到写入完成 等待并且不要阻塞写入

你:“在写线程写完后,唤醒读取线程继续读取。”

写入完成唤醒读取线程

NPR:“没错,我的勇士,你在多线程编程上真是有天赋!你写出的正是等待/唤醒机制设计者的设计思路。这个等待方法叫做 wait(),唤醒方法叫做 notify()。唯一需要注意的一点是,等待与唤醒操作必须在锁的范围内执行,也就是说,调用 wait() 或 notify() 时,都必须用 synchronized 锁住被 wait/notify 的对象。”

// 标志是否写入完成 // 如果还没有写入完成,循环等待直到写入完成 // 等待,并且不要阻塞写入 // 睡眠一秒保证线程执行完成

你:“现在运行果然没有问题了,达到了我预期的效果。”

NPR:“不错,由于本例中只有一个线程在等待,所以我们只需要使用 notify() 函数,如果有多个线程需要唤醒,我们应该用 notifyAll() 函数。实际上,工作中往往都是使用 notifyAll() 函数。”

你:“是为了防止某些线程由于没有被唤醒一直等待吗?”

NPR:“没错。很好,你已经掌握了我们多线程王国的 synchronized 关键字和 wait/notify 机制,但我想告诉你,synchronized 并不是一个很好的加锁方案。”

你:“啊?我觉得 synchronized 已经很好用了啊,简直是一个相当伟大的发明,它还有什么缺点吗?”

NPR:“年轻人啊,真是健忘。你刚才已经遇到了一次 synchronized 的缺点,由于使用了 synchronized,你刚才的程序陷入了死循环中。”

你:“的确,但那是我代码逻辑没有考虑清楚导致的,不能让 synchronized 背锅吧!”

“‘如果不曾见过太阳,我本可以忍受黑暗’”。NPR 突然吟诵起狄金森的诗句,“这样的死循环完全是可以避免的,如果你使用过更加优秀的加锁工具,可能就不会再觉得 synchronized 有多好了。”

NPR 凝望着天空,思绪仿佛回到了多年以前,你惊异的发现 NPR 眼中竟闪烁出崇拜的光芒。

只听 NPR 娓娓道来:“很早以前,我们王国只有 synchronized 关键字可以使用,每个 Java 程序员必须小心翼翼,生怕线上的程序陷入无尽等待。而 synchronized 又是一个很重的操作,为了优化 synchronized 的效率,一代又一代的程序员们做了非常多的努力,但并发始终是一个艰难又让人头疼的问题。直到后来,并发大师 Doug Lea 的出现,这个鼻梁挂着眼镜,留着德王威廉二世的胡子,脸上永远挂着谦逊腼腆笑容的老大爷,亲自操刀设计了 Java 并发工具包 java.util.concurrent。这套工具在 Java 5 中被引入,从此以后,Java 并发变得相当容易。”

作为一名年轻的 Java 工程师,你实在很难代入 NPR 的情绪中,只是简单地说道:“哦,那他很棒棒哦!他的这个工具包要怎么用呢?”

刚听 NPR 吹了半天,以为这个工具包会有多牛的你,盯着这段代码端详了半天,却根本没看出有多大区别,忍不住吐槽道:“恕我直言,看起来完全没有那么神奇,只是把 synchronized 关键字换成了 ReentrantLock 类而已。”

NPR:“当然,这只是替换,ReentrantLock 的优势在于,它可以设置尝试获取锁的等待时间,超过等待时间便不再尝试获取锁,这在实际开发中非常有用。”

你:“原来如此,看来 ReentrantLock 比 synchronized 更安全,可以完全避免无限等待。小 Doug 还是有一点实力的。”

第七回 睡觉记得定闹钟

// 标志是否写入完成 // 如果还没有写入完成,循环等待直到写入完成 // 等待,并且不要阻塞写入 // 睡眠一秒保证线程执行完成

NPR:“独到的优点~我喜欢这个词!很好地描述出 ReentrantLock 拓展了 synchronized 没有的功能。其实,Condition 也有两个独到的优点~你不妨猜猜看是什么。”

// 1 秒内没有被唤醒,自己醒来

你:“哈哈,这就像线程睡觉前,先给自己定了一个闹钟,如果没人唤醒自己,就自己醒过来,真是太有趣了!”

NPR:“一个定时唤醒自己的闹钟,非常棒的理解!”

你:“您刚才说 Condition 有两个独到的优点,那另一个是什么呢?”

NPR:“Condition,译为情境、状况。当我们在不同的情境下,通过使用多个不同的 Condition,再调用不同 Condition 的 signal 方法,就可以唤醒自己想要唤醒的某个或某一部分线程。”

你:“妙啊,这样的同步操作真是太灵活了!我逐渐感受到 Doug Lea 的大师魅力了!”

这时,NPR 眼中再次泛起崇拜的光芒:“伟大的 Doug Lea,我们多线程王国的一颗巨星,浑身散发着睿智的光芒,亿万 Java 程序员心中的梦。”

你听得起了一身的鸡皮疙瘩,忽而眉头一皱,发现事情并不是这么简单。思索片刻后,你对 NPR 说道:“我隐隐觉得现在的流程还不够完美,ReentrantLock 好像还得再优化一下。”

NPR 饶有兴趣的看着你,问道:“何出此言?”

你:“打个比方,将读操作比作浏览一个网页,写操作比作修改这个网页的内容。当多个用户浏览一个网页时,由于读操作被加了锁,大家必须排队依次浏览,这会严重影响效率。”

NPR 再次竖起大拇指:“很不错,我的勇士!这正是可以优化的地方。我们回到之前讨论过的问题:读操作真的必须锁吗?”

你:“必须锁啊,我们刚才做了实验,如果读操作不锁的话,会导致无法及时读取到最新值。”

NPR:“梳理一下我们的需求,其实我们想要的是这样的效果。”

  • 当有写操作时,其他线程不能读取也不能写入。
  • 当没有写操作时,允许多个线程同时读取,以提高并发效率。

你:“对对对,这就是我想要的优化。可是要怎么做呢?我没有想到一个好的实现思路。”

NPR:“Doug Lea 早已考虑到这一点,并且为我们提供了一个使用非常方便的工具类,名字叫 ReadWriteLock。”

// 标志是否写入完成 // 如果还没有写入完成,循环等待直到写入完成 // 等待,并且不要阻塞写入 // 睡眠一秒保证线程执行完成

NPR:“只需定义一个 ReentrantReadWriteLock 变量,读取时使用 readLock,写入时使用 writeLock,这个工具就会帮我们完成剩下的所有工作,达到的就是我们之前讨论的效果:单独写、一起读。”

你:“666,ReadWriteLock,啊!这就是大名鼎鼎的读写锁!”

NPR:“没错,ReadWriteLock 为我们优化了读与读之间互斥的问题。

这时,你露出了羞赧的神色,挠着脑袋说道:“其实我以前也听过读写锁,可总觉得它是一个很难的东西,所以自己一直没有掌握。”

NPR:“嗯,我见过很多来我这里的多线程新手,他们畏难情绪太重,总是被我们王国的各种专业名词吓到,实际上这些可爱的工具类们都不难。毕竟工具是用来服务大众的,API 设计之初就要考虑到使用时称不称手。”

第九回 做人最重要的就是开心

NPR:“不是的,我们还可以更进一步优化。”

你:“竟然还可以优化?我实在是想不到哪里还能优化了。”

NPR:“同样以你刚才的例子打比方,使用 ReadWriteLock 会出现一个问题,当多个用户一起浏览网页时,如果有网页修改操作,必须等待所有人浏览完成后,才能修改。”

你:“使用 ReadWriteLock 会导致写线程必须等待读线程完成后才能写?这是个坑啊。”

NPR:“没错,读的过程中不允许写,我们称这样的锁为悲观锁。”

你:“程序又没有感情,怎么还悲观起来了。”

NPR:“悲观锁是和乐观锁相对的,如果不是乐观锁的出现,人们也不会发觉现在的锁是悲观的。”

你:“乐观锁又是什么?”

NPR:“乐观锁的特点是,读的过程中也允许写。”

你:“啊?这不是会出问题吗?就像我们刚才测试的那样,万一读的过程中写线程拿到写锁后将值修改了,读的数据就错了啊。”

NPR:“你说得没错,但只要我们乐观地估计读的过程中不会有写入,就不会出问题了。几乎所有的数据库,读操作比写操作都要多得多,所以乐观锁可以进一步提高效率。”

你:“编程哪能靠乐观地估计,万一出问题了,造成多大的损失啊。果然人不能太乐观,古人都说生于忧患,死于安乐。”

NPR 嬉皮笑脸地说:“害,那都是几千年前的思想了,现在提倡做人最重要的就是开心。”

你:“可别得意忘了形,我想是个正常的公司都会使用悲观锁吧。性能和稳定二选一的话,只能舍弃性能选择稳定。”

NPR:“呵,小孩子才做选择。”

你:“你是说我们可以全都要?”

NPR:“没错,只要我们乐观地读取数据后做一个检查,判断读的过程中是否有写入发生。如果没有写入,说明我们乐观地获取到的数据是正确的,乐观为我们提高了效率。如果检查发现读的过程中有写入,说明读到的数据有误,这时我们再使用悲观锁将正确的数据读出来。这样就可以做到性能、稳定兼顾了。”

你:“听起来还不错,不过要怎么判断读的过程中是否有写入发生呢?”

NPR:“比如我们可以在读取前,给数据设置一个版本号,写入后修改此版本号,读取完成后通过判断这个版本号是否被修改,就可以做到这一点了。”

你:“这个版本号也不好实现啊,Doug Lea 大师有给我们提供什么工具类吗?”

NPR:“当然有,这个类叫做 StampedLock,Stamp 译为戳,戳就是我给你提到的版本号。”

// 检查乐观读取到的数据是否有误 // 睡眠一秒保证线程执行完成

NPR:“就是这么简单,先用 tryOptimisticRead 尝试乐观读取,再使用 lock.validate(stamp) 验证版本号是否被修改。如果被修改了,说明读取有误,则换用悲观锁重新读取即可。”

你:“之前我看到 lock 和 unlock 都是成对出现的,这段代码里如果 lock.validate(stamp) 验证结果为 true,乐观锁就执行不到 unlock 方法了啊!会不会导致没有解锁?”

NPR:“你多虑了,tryOptimisticRead 只是返回一个版本号,不是锁,根本没有锁,所以不需要解锁。这也是乐观锁提升效率的秘诀所在。”

你:“原来如此,这么说来,当需要频繁地读取时,使用乐观锁可以大大的提升效率。”

NPR:“没错,如果读取频繁,写入较少时,使用乐观锁可以减少加锁、解锁的次数;但如果写入频繁,使用乐观锁会增加重试次数,反而降低了程序的吞吐量。所以总的来说,读取频繁使用乐观锁,写入频繁使用悲观锁。”

你:“大师果然是大师,针对各种场景的优化都替我们考虑到了,我已经路转粉了!伟大的 Doug Lea!”

NPR 不禁感叹道:“是啊,伟大的 Doug Lea!世界上 2% 的顶级程序员写出了 98% 的优秀程序,我们平常不过是使用他们造好的轮子而已。”

你:“要用好轮子也需要懂得轮子从创造到发展的过程。谢谢你教我这么多,NPR 先生。”

NPR:“哈哈,先别谢我,其实我给你展示的代码是有一点问题的,为了给你讲解,我简化了一句代码,你能看出是什么吗?”

你:“啊?你个坑货!让我想想,嗯....”

你能想出 NPR 简化了哪一句代码吗?在留言区告诉我们吧!

码字不易,觉得不错的话给力扣君点个赞呀~

我要回帖

更多关于 江湖救急 下载 的文章

 

随机推荐