当前课程知识点:JAVA程序设计进阶 > 第二章 线程(中) > 2.2 线程同步的实现方式—Synchronization > Video
在上一节当中
我们已经介绍了线程同步的思想
那在这一节当中
我们将介绍采用何种方式
来实现线程同步
那怎么样才能够避免这个情况呢
就会用到我们线程同步的这个知识
线程同步往往是和我们线程的互斥
这个在一起的
什么叫互斥呢
许多线程在同一个共享上操作
而互不干扰
这就要求同一时刻
只有一个线程访问该共享数据
那因此有些方法或程序
在同一时间只能被一个线程执行
我们把它称之为监视区
那其实线程之间也是可以协作的
也就是说多个线程
可以有条件的同时操作
有一些共享数据
执行监视区代码的线程
在条件满足的情况下
它也可以允许其他线程
进入到这个监视区
那我们怎么实现这种互斥与同步
我们就需要用到关键字synchronized
synchronized的作用
是用于指定需要同步的代码段
或者方法
那这个synchronized
它是实现了一个
这个对象锁的这个交互
那一般它的这个代码格式是这样的
你先写synchronized
后面一个小括号
里面是你要交互的这个对象
也就是你要访问的这个对象
然后再把代码段放进去
也就是你访问这个对象的时候
你要做一些操作
那这个从语法上这么写完了以后
那synchronized的功能是干嘛呢
它首先要判断
你这个对象的锁是否存在
如果这个锁存在的话
就可以执行后面这个代码段
如果这个锁不存在
为什么不存在呢
因为这个锁已经被其他线程拿到了
然后其他线程正在访问这个对象
所以你这个当前对象
想访问这个数据的话
你又访问不了
那你这个线程就得进入等待状态
一直到其他线程释放出这个锁以后
你才能得到锁
那什么情况下会释放锁呢
也就是说我这个线程
在synchronized那个代码段
一执行完了以后
就会把这个对象的锁给释放出来
那有了synchronized这个关键字的
这种作用
我们就考虑说
怎么把它运用到我们刚才
这个售票存票的这个过程当中
那我们就希望说
把我们的存票线程和售票线程
让它设置成一种互斥关系
也就是说这两个线程
任意一个去访问ticket这个对象的时候
就不允许另外一个线程
去访问ticket这个对象
那对于ticket这样的对象的话
它就有一个锁
其他线程想访问ticket这样对象的话
它就必须争夺这个锁
那比如说当某个线程A
获得这个对象的锁后
线程B就必须等待线程A完成
自己规定的操作
释放出锁以后
这个线程B才能获得这个对象的锁
然后它才会执行线程B中的
自己希望的操作
那我们利用synchronized这个关键字
来去改造一下
刚才我们上述的这个存票售票的过程
我们看一下这个类
class producer extend Thread
然后tickets t=null
然后这个producer的构造方法
就是把这个tickets t对象传进去
然后我们看一下public void run方法
然后while t.number小于t.size
也就是我们产生了这个票号
比我们的这个票的总量小
然后这个时候
我们看看这个语句怎么改进来
synchronized(t)也就是说
我在我这个线程producer当中
我现在要访问我的这个tickets
这个对象t
我用synchronized那个加上以后
我要申请对象t的锁
而假定运行过程中
我申请到了 我做什么事情呢
就是这system.out.println
然后producer puts ticket
然后++t.number
也就是说我生产出一张票来
然后t.available=true
然后说有票可卖了
其实说白了最重要就是
把这两行代码变成一个(原子)操作
就是在执行过程中
不可能被打散执行
然后当这个synchronized范围
这些语句执行完了以后
它自然而然就会释放对象t的锁
再往下那句代码
system.out println(producer ends)
所以大家看看我们这里已经
用synchronized来改造了一下
我们producer的这个代码
那我们再看看怎么用synchronized
来改造一下这个consumer这个代码
好 我们直接就看改造的那部分了
while i小于t.size
也就是说我要卖的票
比我们的这个票的这个总数
这个还是要小
说明我可以卖
好 我卖票的时候我synchronized(t)
也就是说申请对象t的锁
也就是申请对象t的锁的话
就要求我这时候就是我这些人
去访问这个ticket这个对象
别的进程不允许访问在这个时刻
那我访问ticket对象
我要做什么事情呢
大家看里面的代码
if(t.available=true&&
i小于等于t.number
也就是说有票可卖
而且我想要卖的这个票
比我们那个生产出来的票号
要小于等于它
那我就可以卖票了
那怎么卖呢
那system.out.println
consumer buys ticket+(++i)
也就是打印出来我把票卖掉了
然后if(i)==t.number
然后就意味着我们要卖到
这个最后一张票了
那这个时候我们就是休眠一毫秒
try Thread sleel
然后最后t.available=false
也就是说无票可卖了
然后这些代码执行完以后
它自然而然会释放对象t的锁
那其实提醒大家的是
用synchronized
后面大括号括起来其实是代码
实际上它把它变成一个原子操作
也就是说当我拿到这个
对象t的锁的时候
我这里面的这些代码
是肯定都会被执行的
不会说我执行某一句以后
就被这个打断
然后那个插入别的线程去执行
去访问这个对象t
所以这个是synchronized
它的很重要的作用
那经过这样的一个改造了以后
同学们可以再自己运行一下
我们这个改造后的程序
你就会发现我们的这个存票售票程序
应该不会出现死循环的这个现象了
那这里面最重要的就是我们的
synchronized关键字
实际上我们是通过这个关键字
把存票这个线程和售票线程
对同样一个对象
也就是ticket这个对象的任务访问
把它变成一种互斥操作
也就是说不能让两个线程
都同时去访问这个对象
而必须是说其中一个线程
访问这个ticket这个对象
别的就必须等它访问完
把所有的操作都做完以后
才允许它访问
那当我们的线程执行到
synchronized的时候
它的功能是这样
它要检查传入的实参对象
并申请得到该对象的锁
如果得不到的话
那这个线程就会被放到
与该对象锁所对应的等待池线程当中
直到这个对象的锁被归还
那这个池中的等待线程
才能重新去获得锁
然后才继续执行
那我们除了可以指定
对指定的代码段进行同步控制外
还可以定义某些方法
在同步控制下执行
只要在方法定义前面
加上synchronized关键字就可以了
那我们把上面的例子
再稍微改进一下
我们最重要的是改进
是把这个ticket这个类进行改进
然后给它增加两个方法
而且都设置成为同步的方法
我们看一下这个改进后的类
class tickets
那前面这些它的这个成员变量
和它的购票方法都没有变化
我们看一下新增的两个方法
第一个方法叫
public synchronized void put
同步方法
就是往里面实现存票的功能
我们看一下它的功能是什么
system.out.println
producer puts ticket++number
往里存一张票
所以available=true
那实际在这个方法中
它有两句代码
那由于加上同步synchronized
这个功能以后
那这个方法它里边的代码
就变成原子操作了
这两张代码肯定是要
能被同时执行的
我们再看一下另外一个方法
public synchronized void sell
它也是一个同步方法
它实现售票的功能
那它售票功能的话
它就会判断说if available=true
就是说是不是有票可卖
并且i小于等于number
就要我要卖票和你生产出来票的
那个票号是小于等于它的
可以卖票了
卖票的过程就模拟一下
就打印出consumer buysticket
加上++i
然后最后来判断一下
是不是票都卖没了
如果卖没了
那就available=false
所以我们现在通过这么一个改造的话
就把ticket它增加两个方法
而且是同步方法
这样的话当其他线程
要访问ticket这个类的时候
就可以通过它这两个方法来去访问
那我们看一下这个producer
和consumer又有什么修改
class producer extends Thread
然后我们看一下
主要看它的run方法
run方法的话
比以前有简化了
就是while(t.number 然后t.put 也就是说如果我们存票数 小于要限定的总量的话 我们就不断的去产生票 就把它存进去 通过调用t的put方法去存票 我们再看看consumer怎么改造的 Class consumer extends Thread 然后我们看看run方法 while(t.i 也就是说我们要售票的数量 小于你限定的总量 那我就不停的售票 那怎么售票呢 就是t.sell 用sell的方法 需要注意说我们这个sell方法 也是个同步方法 和刚才那个put方法都是同步方法 那通过这么样一个控制以后 我们这个程序这个线程执行起来 就会避免它出错 那我们再看看就是说这一部分 线程的同步和锁之间 都有什么重要的要点 首先我们只能同步方法 不能同步变量 也就是说我们synchronized的话 你去同步的话 是我们这个 (就是)我们一个方法 另外每个对象只有一个锁 我们当提到同步的时候 应该清楚在什么对象上同步 因为同步的话是因为我们 原因是我们要对某个对象进行访问 就是若干个线程对于某一个对于 进行访问 那这个时候其实锁是和这个对象 在一起的 第三点是我们类可以同时拥有 同步和非同步方法 非同步方法可以被多个线程自由访问 而不受锁的限制 如果两个线程同时使用 相同的实例来调用synchronized方法 那么一次只能有一个线程执行方法 另一个需要等待锁 这个是很自然这个事情 再提醒大家有一点 线程休眠的时候 它所持有的任何锁都不会释放 这一点就是非常有意思的一点 大家不要以为说 我这线程休眠的时候 我会释放锁 就像刚才我们那个例子 我们在这个售票线程里面 每售出来票的时候 它就会休眠一毫秒 但休眠一毫秒的时候 它不会释放出它所占有的 这个ticket对象的锁的 它一直会持有 所以这是一个独特的一个地方 我们线程还可以获得多个锁 比如说在一个对象的同步方法里面 我又调用了另外一个对象的同步方法 然后获取了两个对象的同步锁 这是允许的 另外一个原则是这样 同步它会损害并发性 应该尽可能的缩小同步范围 大家也看到了就是说 比如说两个线程都访问 同样一个对象 如果这个对象上好些方法 都给它加上同步的话 那基本上就是说我一个 一个线程访问这个对象的时候 另外一个线程只能等着 那如果等的情况多的话 那你就会损害这个 这两个线程之间的并发的性能 所以大家在(编程)区要考虑清楚说 我这一个数据对象 到底哪些方法是需要同步方法 哪些不需要 如果你能缩小这个同步方法的话 你就是可以让我们的这个 整个程序的这个并发性得到提高 那我们在使用同步代码块的时候 应该指定是在哪个对象同步 也就是说要说明获取的是 哪个对象的锁 所以我们java线程中的同步和锁 是息息相关的 希望大家能够很好的理解 这部分的内容 在这一节当中 我们通过介绍synchronized 来阐述了如何实现线程同步
-1.0 导学
--Video
-1.1 线程的基本概念
--Video
-1.1 线程的基本概念--作业
-1.2 通过Thread类创建线程
--Video
-1.2 通过Thread类创建线程--作业
-1.3 线程的休眠
--Video
-1.3 线程的休眠--作业
-1.4 Thread类详解
--Video
-1.5 通过Runnable接口创建线程
--Video
-1.5 通过Runnable接口创建线程--作业
-1.6 线程内部的数据共享
--Video
-2.0 导学
--Video
-2.1 线程同步的思路
--Video
-2.2 线程同步的实现方式—Synchronization
--Video
-2.3 线程的等待与唤醒
--Video
-2.4 后台进程
--Video
-2.5 线程的生命周期与死锁
--Video
-2.6 线程的调度
--Video
-3.0 导学
--Video
-3.1 线程安全与线程兼容与对立
--Video
-3.2 线程的安全实现-互斥同步
--Video
-3.3 线程的安全实现-非阻塞同步
--Video
-3.4 线程的安全实现-无同步方案
--Video
-3.5 锁优化
--Video
-4.0 导学
--Video
-4.1 URL对象
--Video
-4.2 URLConnection对象
--Video
-4.3 Get请求与Post请求
--Video
-4.4 Socket通信原理
--Video
-4.5 Socket通信实现
--Video
-5.0 导学
--Video
-5.1 Socket 多客户端通信实现
--Video
-5.2 数据报通信
--Video
-5.3 使用数据报进行广播通信
--Video
-5.4 网络聊天程序
--Video
-6.0 导学
--Video
-6.1 Java虚拟机概念
--Video
-6.2 Java虚拟机内存划分
--Video
-6.3 Java虚拟机类加载机制
--Video
-6.4 判断对象是否存活算法及对象引用
--Video
-6.5 分代垃圾回收
--Video
-6.6 典型的垃圾收集算法
--Video
-6.7典型的垃圾收集器
--Video
-7.0 导学
--Video
-7.1 集合框架与ArrayList
--Video
-7.2 LinkedList
--Video
-7.3 HashMap与HashTable
--Video
-7.4 TreeMap与LinkedHashMap
--Video
-7.5 HashSet
--Video
-8.0 导学
--Video
-8.1 Java反射机制
--Video
-8.2 Java静态代理
--Video
-8.3 Java动态代理
--Video
-8.4 Java 反射扩展-jvm加载类原理
--Video
-8.5 Java进阶课程总结
--Video