Java多线程学习(3)线程同步与线程通信

本文详细介绍了Java中线程安全问题的解决方案,包括同步代码块、同步方法、Lock接口及其实现ReentrantLock,解释了死锁的产生条件及预防措施。此外,还探讨了线程通信机制,如wait/notify、Condition以及阻塞队列的使用,最后介绍了线程组、线程池的概念和应用。

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

当多个线程访问同一个数据时会出现线程安全问题。

1 同步代码块

synchronized(obj){
    
}
复制代码

obj是同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。

2 同步方法

对于用synchronized修饰的实例方法(非static方法)而言,无需显示指定同步监视器,同步方法的同步监视器是this,也就是调用该参数的对象。

同步代码块和同步方法要符合“加锁——修改——释放锁”的逻辑:在任何线程修改指定资源之前,首先对该资源加锁,加锁期间其他线程无法修改该资源,当该线程修改完成后,该线程释放对该线程的锁定。

以下几种情况,当前线程会释放同步监视器:
1 同步代码块执行结束;
2 同步代码块中遇到了break,return终止了该代码块;
3 在同步代码块中出现了未处理的Error或Exception;
4 当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器。

注:sleep(),yield(),suspend()不会释放同步监视器。

3 同步锁 Lock

从Java5开始,可以通过显示定义同步锁对象来实现同步,同步锁由Lock对象充当。
public interface Lock
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock 的读取锁。
比较常用的是ReentrantLock。

private final ReentrantLock lock = new ReentrantLock();
...
lock.lock();
...
lock.unlock();
复制代码

如果获取了多个锁,必须以相反的顺序释放;

4 死锁

死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

java 死锁产生的四个必要条件:

1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用 2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。 3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。 4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。 当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。

线程通信:

1 传统线程通信

wait():导致当前线程等待,知道其他线程调用该同步监视器的notify()或notifyAll()方法来唤醒该线程。
notify():随机唤醒此同步监视器上的一个线程;
notifyAll():唤醒此同步监视器上的所有线程。

2 使用Condition控制线程通信

如果程序使用的是Lock对象同步,则不存在隐式监视器,也就不能使用wait(),notify(),notifyAll()来进行线程同步了。
当使用Lock对象来保证同步是,java提供了一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象却无法执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程。
Condition实例被绑定在一个Lock对象上,要获得特定的Lock实例的Condition实例。调用Lock对象的newCondition()方法即可。Condition类提供了如下三个方法:
1 wait():导致当前线程等待;wait()方法有很多变体;
2 signal():唤醒在此Lock对象上等待的单个线程,,只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程;
3 signalAll():唤醒在此Lock对象上等待的所有线程,只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程。

3 使用阻塞队列(BlockingQueue)控制线程通信

BlockingQueue也是Queue的子接口,但是它的作用不是作为容器,而是作为线程同步的工具。
当生产者线程试图向BlockingQueue中放入元素时,如果队列已满,则线程被阻塞;
当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
BlockingQueue提供如下两个支撑阻塞的方法。
put(E e):尝试把E元素放入BlockingQueue中,如果队列已满,则线程被阻塞;
trake():尝试从BlockingQueue的头部取出元素,,如果该队列已空,则该线程被阻塞。

BlockingQueue包含如下5个实现类:
1 ArrayBlockingQueue; 2 LinkedBlockingQueue; 3 PriorityBlockingQueue,取元素时,并不是取出在队列中存在时间最长的数,而是队列中最小的元素。
4 SynchronounsQueue:同步队列。对该队列的存取必须交替进行。
5 DelayQueue

4 线程组和未处理的异常

线程组ThreadGroup,可以对一批线程进行分类管理,java允许程序直接对线程组进行控制。对线程组的控制相当于同时控制这批线程,用户创建的所有线程都属于指定线程组,如果程序没有指定线程属于哪个线程组,则该线程属于默认线程组。
Thread(ThreadGruop, Runnable target);
Thread(ThreadGruop, Runnable target, String name);
Thread(ThreadGruop, String name);

ThreadGruop(String name); ThreadGruop(ThreadGruop target, String name);

ThreadGroup类提供了如下几个常用方法来操作整个线程组里的所有线程。
int activeCount():返回此线程组中活动线程的数目;
interrupt(): 中断此线程组中的所有线程;
isDaemon():判断该线程组是否是后台线程组;
setDaemon():把该线程组设置成后台线程组;
setMaxPriority(int pri):设置线程组的最高优先级。

5 线程池

系统启动一个线程的成本是比较高的,因为它涉及与操作系统的交互,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生命周期很短的线程时。

java5增加了Executers工厂类来产生线程池。

使用线程池来执行线程任务的步骤:
1 调用Executors类的静态方法创建一个ExecutorService对象,该对象代表一个线程池。
2 创建Runnable实现类或Callable实现类的实例,作为线程执行任务。
3 调用ExecutorService对象的submit对象来提交Runnable实例或Callable实例。
4 当不想提交任何线程时,使用shutdown()方法来关闭线程池。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值