线程同步
▪ 同步问题的提出
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。
比如:教室里,只有一台电脑,多个人都想使用。天然的解决办法就是,在电脑旁边,大家排队。前一人使用完后,后一人再使用。
▪ 线程同步的概念
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。 这时候,我们就需要用到“线程同步”。 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
没有线程同步会造成什么问题?
代码演示火车站卖票的例子:
运行结果,可以看到,出现了一张火车票,同时多个窗口在卖,造成的数据错乱。
造成这个的原因是:好比是,两个人,去两个窗口买票,1号买家去1号窗口问还剩几张票,此时只剩了一张票,1号窗口说还剩一张,于此同时,2号买家去2号窗口问还剩几张票,此时应该还剩一张票(此时1号还在询问,并未买走),但是随后,1号买家,从1号窗口买走了票。在1号买票的同时,2号买家也付款了,要买票,此时实际应该是没有票了;就会造成这样数据错乱;
解决此类问题的方法,是可以加一个锁,比如说:还是两个人一张票,1号买家在咨询时,剩余票数就暂时被锁定,先被1号售票窗口先拿着,2号买家去2号窗口咨询时,此时应该是没有票,这样就不会造成数据错乱的问题,
我们在JAVA程序中,这种锁叫同步锁 synchronized
同步锁synchronized的使用方法:
▪ synchronized 方法
通过在方法声明中加入 synchronized关键字来声明,语法如下:
public synchronized void accessVal(int newVal);
▪ synchronized块
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
Java 为我们提供了更好的解决办法,那就是 synchronized 块。 块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
synchronized 块:通过 synchronized关键字来声明synchronized 块,
语法如下:synchronized(syncObject) { //允许访问控制的代码 }
演示代码
加入了synchronized 方法 ,就解决了一张票,重复出售的问题。
没有出现问题,剩余票,一张一张的出售
死锁及解决方案
死锁的概念
“死锁”指的是:
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。
因此, 某一个同步块需要同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
代码演示
运行程程序之后,发现程序无法正常运行下去,代码演示案例中,“化妆线程”需要同时拥有“镜子对象”、“口红对象”才能运行同步块。那么,实际运行时,“小丫的化妆线程”拥有了“镜子对象”,“大丫的化妆线程”拥有了“口红对象”,都在互相等待对方释放资源,才能化妆。这样,两个线程就形成了互相等待,无法继续运行的“死锁状态”。
解决方法
思路很简单:同一个代码块,不要一把锁中,加入另外一把锁。
程序正常运行结束;
线程并发协作(生产者/消费者模式)
多线程环境下,我们经常需要多个线程的并发和协作。这个时候,就需要了解一个重要的多线程并发协作模型“生产者/消费者模式”。
Ø 什么是生产者?
生产者指的是负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。
Ø 什么是消费者?
消费者指的是负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。
Ø 什么是缓冲区?
消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。
缓冲区是实现并发的核心,缓冲区的设置有3个好处:
Ø 实现线程的并发协作
有了缓冲区以后,生产者线程只需要往缓冲区里面放置数据,而不需要管消费者消费的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不需要管生产者生产的情况。 这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离。
Ø 解耦了生产者和消费者
生产者不需要和消费者直接打交道。
Ø 解决忙闲不均,提高效率
生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里面放置数据 。
下面以生产馒头和消费馒头做个代码演示:
线程并发协作总结:
线程并发协作(也叫线程通信),通常用于生产者/消费者模式,情景如下:
1. 生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
2. 对于生产者,没有生产产品之前,消费者要进入等待状态。而生产了产品之后,又需要马上通知消费者消费。
3. 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费。
4. 在生产者消费者问题中,仅有synchronized是不够的。
· synchronized可阻止并发更新同一个共享资源,实现了同步;
· synchronized不能用来实现不同线程之间的消息传递(通信)。
5. 那线程是通过哪些方法来进行消息传递(通信)的呢?见如下总结:
6. 以上方法均是java.lang.Object类的方法;
都只能在同步方法或者同步代码块中使用,否则会抛出异常。