Java编程思想-并发-基本的线程机制/共享受限资源

本文深入探讨Java并发编程的关键概念和技术,包括线程创建、异常处理、锁机制、volatile关键字的作用及synchronized与显式Lock的区别。此外,还讨论了同步块的使用以及如何正确地实现线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、编程思想电子版699页:
public class SelfManaged implements Runnable{
    .....
    public SelfManaged(){ t.start();}
    .....
}
1)创建线程,这与从Thread继承并没有什么特别的差异,只是语法稍微晦涩一些。但是,实现接口还可以继承不同的类,而从Thread继承并不行。
2)注意,在构造器中调用start(),可能会有问题,因为另一个任务可能会在构造器结束之前开始执行,这意味着该任务可能访问处于不稳定状态的对象。这是优选Executor而不是显示的创建Thread对象的另一个原因。

2、编程思想电子版707页:
由于线程的本质特性,使得不能捕获从线程中逃逸的异常。一旦异常逃出任务的run()方法,它就会向外传播到控制台。
Java SE5之后,可以用Executor来解决。
Thread.UncaughtExceptionHandler.uncaughtException()会在线程因未捕获异常而临近死亡时被调用。

3、编程思想电子版709页:
    基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问其共享资源的方案。这意味着在给定时刻只允许一个任务访问共享资源。通常这是通过在代码前面加上一条锁语句来实现的,这就使得在一段时间内只有一个任务可以运行这段代码。因为锁语句产生了一种互相排斥的效果,所以这种机制常常称为互斥量。
    Java以提供关键字synchronized的形式,为了防止资源冲突提供了内置支持。当任务要执行被synchronized关键字保护的代码片段的时候,它将检查锁是否可用,然后获取锁,执行代码,释放锁。
    共享资源一般是以对象形式存在的内存片段,但也可以是文件、输入\输出端口,或者是打印机。要控制对共享资源的额方位,得先把它包装进一个对象。然后把所有要访问这个资源的方法标记为synchronized。如果某个任务处于一个对标记为synchronized的方法的调用中,那么在这个线程从该方法返回之前,其他所有要调用类中任何标记为synchronized方法的线程都会被阻塞。
synchronized void f(){}
synchronized void g(){}
    所有对象都自动含有单一的锁(也称为监视器)。当在对象上调用其任意synchronized方法的时候,此对象都被加锁,这时该对象上的其他synchronized方法只有等到前一个方法调用完毕并释放了锁之后才能被调用。例如前面,如果某个任务对象调用了f(),对于同一个对象而言,就只能等到f()调用结束并释放了锁之后,其他任务才能调用f()和g()。
    将域设置为private是非常重要的,否则synchronized关键字就不能防止其他任务直接访问域,这样就会产生冲突。
    一个任务可以多次获得对象的锁。如果一个方法在同一个对象上调用了第二个方法,后者又调用了同一个对象上的另一个方法,就会发生这种情况。JVM负责跟踪对象被加锁的次数。如果一个对象被解锁(即锁被完全释放),其计数变为0。在任务第一次给对象加锁时,计数变为1.每当这个相同的任务在这个对象上获得锁时,计数都会递增。显然,只有首先获得锁的任务才能允许继续获取多个锁。每当任务离开一个synchronized方法,计数递减,当计数为零的时候,锁被完全释放,此时别的任务就可以使用此资源。
    针对每个类,也有一个锁(作为类的Classs对象的一部分),所以synchronized static可以在类的范围内防止对static数据的并发访问。
    该什么时候同步呢?可以运用Brain的同步规则:
    如果你正在写一个变量,它可能即将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。
    如果你的类中有超过一个方法在处理临界数据,那么你必须同步所有相关的方法。如果只同步一个方法,那么其他方法将会随意的忽略这个对象锁,并可以在无任何惩罚的情况下被调用。这是很重要的一点:每个访问临界共享资源的方法都必须被同步,否则它们就不会正常的工作。

4、编程思想电子版711页:
    加锁的第二种办法---显示的Lock
    private Lock lock = new ReentrantLock();
    public int next(){
        lock.lock();
        try {
            ++num;
            Thread.yield();
            ++num;
            return num;
        }finally{
            lock.unlock();
    }
    使用Lock的try-finally比使用synchronized写的代码要多,但这也是优点。如果在使用synchronized关键字时,某些事物失败了,就会抛出异常而没有机会做任何清理工作,但是Lock的finally就可以将系统维护在正确的状态。
    整体上,synchronized需要写的代码量少,并且用户出现错误的可能性也会降低,所以通常在解决特殊问题时,才会显示的使用Lock对象。比如,获取锁一段时间然后放弃。
    相对于synchronized,Lock有更细粒度的控制力,对于实现专有同步结构是很有用的,例如,遍历链表中的节点的传递加锁机制(锁耦合),这种遍历代码必须在释放当前节点的锁之前捕获下一个节点的锁。

5、编程思想电子版713页:
    关于volatile:原子性可以应用于除了long和double之外的所有基本类型之上的“简单操作”。但是JVM可以将64位(long和double变量)的读写当做两个分离的32位操作来运行,这就产生了在一个读取和写入操作之间发生上下文切换,导致不同的任务可能看到不正确结果(字撕裂)。但是使用volatile关键字,就会获得原子性。
    因此,原子操作可由线程机制来保证其不可中断,专家级程序员可以利用这一点来编写无锁的代码,这些代码不需要被同步。有时,看起来越是安全的原子操作,实际上也可能不安全。尝试移除同步通常是一种不成熟的优化,并且会招致大量麻烦,而你却没有多少好处,甚至是压根没有好处。
    volatile关键字确保应用中的可视性。如果你将一个域声明为volatile,那么只要对这个域产生了写操作,那么所有的读操作都可以看到这个修改。即使用了本地缓存,情况也是如此,volatile域会立即被写入到主存中,而读取操作就发生在主存中。
    这对理解原子性和易变性很重要。在非volatile域上的原子操作不必刷新到主存中去,因此其他读取该域的任务不必看到这个新值。如果多个任务同时访问某个域,那么这个域就该是volatile的,否则这个域只能经由同步来访问。同步也会导致主存刷新,所以,一个域如果完全由synchronized方法或语句来防护,那就不必将其设置为是volatile的。
    使用volatile而不是synchronized只有一种情况:就是类中只有一个可变的域。该着重提醒的是,最安全的方式是使用synchronized,而尝试其他方式都是有风险的!

6、编程思想电子版721页:
    使用同步块控制优于对整个方法进行同步控制,可以使更多的线程访问(在安全的情况下尽可能多)
    临界区:有时只是希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法,通过这种方式分离出来的代码段被称为临界区。
    使用同步块:(对store()的调用在临界区外部)
    public void increment(){
        Pair temp;
        synchronized(this){
            p.incrementX();
            p.incrementY();
            temp = getPair();
        }
        store(temp);
    }
    对整个方法进行同步控制:
    public synchronized void increment(){
        p.incrementX();
        p.incrementY();
        store(getPair());
    }

7、编程思想电子版722页:
    synchronized块必须给定一个在其上进行同步的对象,而且最合理的方式是,使用其方法正在被调用的当前对象:synchronized(this)。如果获得了synchronized块上的锁,那么该对象其他的synchronized方法和临界区就不能被调用了。因此,如果在this上同步,临界区的效果就会直接缩小在同步的范围内。
    如果一个对象上的两个方法,要同时被两个不同的线程运行。即两个任务可以同时进入同一个对象,只要这个对象上的方法是在不同的锁上同步即可。(一个用this,另一个创建一个object对象即可。)








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值