java多线程学习十二::::Synchronized及其实现原理

嘿嘿,说到并发,这是并发Java编程的一大技巧。
我已经使用它很多年并且觉得它很有趣。

说实话,当我第一次接触Synchronized时,我以为它可以解决线程安全问题。
例如,我有一个共享变量,需要被多个线程修改。
如果不加以保护,数据肯定会被损坏。
我记得曾经在论坛上看到过一个例子。
这是一个简单的银行账户课程。
有一个线程用于存款,另一个线程用于取款。
如果不锁定,账户余额将为负数。
很简单。

那么,同步可以保证同一时刻只有一个线程可以执行同步的代码块或方法,从而避免数据不一致的问题。
在之前的项目中,我有一个线程安全的队列,它使用同步来确保队列操作的原子性。

我们来谈谈确保可见性,这非常重要。
当线程释放锁时,会将本地内存中的共享变量刷新到主内存;当它获取锁时,它会清除本地内存并从主内存重新加载更改的值。
您之前在分布式系统中遇到过由于可见性问题导致的数据不一致的情况。
添加同步后,问题得到解决。

至于防止重排序,我了解不多,但大概涉及到引入内存屏障指令来防止编译器和处理器对并发代码块中的指令进行重排序和优化,以确保执行顺序符合预期。

同步锁对象类型也很有趣。
常规同步方法锁定当前实例对象(this),静态同步方法锁定当前类的类对象,同步代码块锁定括号对象。

说到基本实现,监控对象机制是关键。
每个Java 对象都与一个监视器对象相关联,该对象存储在对象标头的MarkWord 中。
同步代码块是通过输入监视器和退出监视器指令显式控制锁的获取和释放来实现的。
优化 JVM 的同步也很有趣。
比如锁升级机制,从无锁状态到偏向锁、轻量级锁,再到重量级锁,根据锁竞争程度动态升级。
还有自旋锁,可以在锁时间较短的情况下减少线程上下文开销,提高性能竞争并不激烈。

最后,同步还与唤醒等待机制密切相关。
wait()、notify()和notifyAll()应该与同步一起使用,因为它们需要触发watch对象的等待队列。

总的来说,同步虽然简单易用,但是应该注意锁争用对性能的影响。
在低争用场景下,偏向锁和轻量级锁效率较高;在高争用场景中,挂起/激活重量级锁的成本很高,因此您可以考虑使用其他编排工具。

Java 线程池中的线程复用是如何实现的?

Java线程池的复用机制,核心就在Worker类。
Worker连接任务和线程,循环从队列接收任务执行。

1 . Worker类:​​封装了线程和任务,初始化时将firstTask及其自身绑定到Thread上。
2 、启动线程:通过addWorker添加后启动,实际运行Worker.run()。
3 、任务执行:runWorker方法循环获取任务执行。
FirstTask第一次直接执行,然后从队列中取出。
4 、线程复用:循环运行任务,避免频繁创建和销毁线程,并将任务与线程分离。
5 、线程池管理:控制主线程数量,队列满时扩容,回收空线程。

优点:减少线程开销,控制线程数量,缓冲任务队列,适合高并发场景。

net 中多线程有几种实现方法?

老实说,现在编写代码时理解线程非常重要。
我们以 WinForms 为例。
UI 线程由操作系统配置。
当您绘制表单时它会自动运行。
当我第一次编写WinForm程序时,我弄清楚了如何添加计时器控件,程序似乎突然变得更快了。
实际上,这个计时器是在后台秘密运行的,但是更新界面的进程必须返回到UI线程。
如果一个非UI线程想要改变界面,它必须使用它的委托,这是非常麻烦的。
但在计时器线程中,您可以直接更新,而不需要所有的曲折。

有趣的是,像定时器这样的东西的精度只有5 0ms。
要执行真正精确的多线程,您需要构建自己的线程。
以前,我有一个正在处理大量文件的项目,我能够打开单个线程来处理它们。
代码看起来很简单: Thread thread = new Thread(obj.functionName);线程.start();然后就可以运行了。
但有一个问题。
如果我们有参数怎么办?当时我不太明白,后来我发现了两种方法。
一种是 MyObject obj = new MyObject(int a, int b);将其放在 thread.Start() 之前,以便 a 和 b 可以在 obj.functionName 中使用。
另一种是使用这样写的委托:
csharp [ComVisible 属性(假)] 公共委托无效 ParameterizedThreadStart(Object obj); 公共线程线程=新线程(myStaticParamThreadMethod); thread.Start("通过委托参数传递值");
这种写法确实很灵活,但是需要委托和静态方法的仔细协调。

说到多线程,ThreadPool更有价值。
我之前用过它在后台处理电子邮件发送任务,而且效果很好。
其实原理就是用完也不会消失,就像游泳池里的小船一样。
完成一项任务后,再完成下一项任务。
您只能使用两个技巧:QueueUserWorkItem 和 RegisterWaitForSingleObject。
正如您所说,前者具有与 Thread 类似的功能,但没有问题。
我有打印“Hello from the threadpool”的测试代码。
无论什么时候接到电话都非常方便。

对于RegisterWaitForSingleObject,这有点像Timer,但不是在UI线程上。
上次我做计划任务时,我将它与 AutoResetEvent 一起使用,效果很好。
关键是这个产品可以自行重置,下次间隔到来时应该再次触发。
如果是ManualResetEvent,则最初不会触发或继续运行。
你必须记住这一点。
不然调试的时候会很头疼。
ManualResetEvent 还可以一次唤醒多个线程。
我不经常使用这个功能,但它确实很强大。

说到这里,我提一个不受欢迎但很实用的东西:Thread.IsBackground=true。
这设置了后台日志线程。
结果在测试过程中我们发现程序终止时日志并不完整。
也就是说,事实证明后台线程不会自动终止。
您必须提醒自己这一点并将重要任务移至后台线程。
不要设置它。

简而言之,如果使用得当,线程可以挽救你的生命,但如果使用不当,它们也可能会杀死你。
特别是,存在一些细微的差异,例如计时器和 UI 线程之间的交互,或者 AutoResetEvent 和 ManualResetEvent 之间的选择。
这些细节决定了你的代码能否可靠运行。