CoreJava第十天 2007年5月10日
一、复习内部类
1、成员内部类
可以访问外部类的私有成员,外部类类名.this.属性
构造成员内部类对象,必须先构造一个外部类对象,外部类对象.new 构造内部类对象
2、静态内部类
只能访问外部类的静态成员
构造静态内部类对象时,不再需要构造外部类对象
3、局部内部类
在外部类方法内定义的内部类
不仅能访问外部类的私有成员,而且还能访问外部类的局部变量,但是要求局部变量是final的
4、匿名内部类
局部内部类,用于实现一个借口或者继承一个类,只会构造一次
内部类的作用:
访问外部类的私有成员,不破坏封装。可以给编程带来一些方便
我们可以把接口公开,把接口的实现类以内部类的形式隐藏起来。强制用户通过接口来实现弱耦合
接口+内部类实现多继承
二、List接口的实现类
1、ArrayList
底层使用数组实现
2、Vector
ArrayList 轻量级 快 线程不安全
Vector 重量级 慢 线程安全的
3、LinkedList
底层使用双向循环链表实现
ArrayList 数组 查询快 增删操作慢
LinkedList 链表 查询慢 增删操作快 栈
使用组合复用实现栈
三、Set接口
1、HashSet
Set的实现类的集合对象中不能够有重复元素,HashSet也一样是使用了一种标识来确定元素的不重复,是元素内容不重复
HashSet用一种算法来保证集合中的元素是不重复的,HashSet的底层实现还是数组。
Object类中的hashCode()的方法是所有子类都会继承这个方法,这个方法会用Hash算法算出一个Hash(哈希)码值返回,HashSet会用Hash码值去和数组长度取模,
模(这个模就是对象要存放在数组中的位置)相同时才会判断数组中的元素和要加入的对象的内容是否相同,如果不同才会添加进去。
Hash算法是一种散列算法。
注意:所有要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素容不重复。
在覆盖hashCode()方法时,要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,所以在覆盖hashCode()方法时,也要尽量使不同对象尽量返回不同的Hash码值。
如果数组中的元素和要加入的对象的hashCode()返回了相同的Hash值(相同对象返回相同整数),才会用equals()方法来判断两个对象的内容是否相同(不同对象返回不同整数)。
练习:
把若干Employee对象放在Set中并遍历,要求没有重复元素
2、SortedSet接口是Set的子接口。
TreeSet是SortedSet接口的实现类,他可以对集合中的元素进行排序。
要存放在TreeSet中自定义类的对象,这个类要么是已经实现了Comparable接口,要么是能给出Comparator比较器,
TreeSet可以自动过滤掉重复元素所以不用重载hashCode()方法,TreeSet会根据比较规则判断元素内容是否相同,TreeSet会在元素存入时就进行了排序。
判断对象重复的依据:compareTo()方法的返回值为,就是重复元素
(在TreeSet给出排序规则时,一定要注意对象内容相等的条件,一定要注意在主观的认为两个对象内容相同时,才可以使用比较少的条件来进行判断)
在要排序时才使用TreeSet类(存储效率比较低),HashSet的存储效率比较高,在需要为HashSet的对象排序时,就可以把HashSet中的元素放入TreeSet。
四、Map
Map中只可以存放键值对(Key,value),其中Key是不可以重复的。Key和value是一一对应的。
HashMap,是Map接口的实现类,Key时无序存放的,其中Key是不可以重复的,它也是通过Hash码值来保证Key不重复的,Key和value是一一对应的。
如果要加入的键值对和HashMap中键值对的Key是相同的就会将这个集合中的Key所队应的value值进行覆盖,在使用自定义类型作为Key时,那就是要覆盖hashCode(),equals()方法,也就是和HashSet中要放入自定义类型是的处理方法相同。
这个类的对象是线程不安全的。
遍历:(1)vlaues() 返回所有值(value)的集合,是一个Collection
(2)keySet() 返回所有键对象的集合,是一个Set
过遍历这个Set,用get()方法来获得Key所对应的value,也就遍历了Map。
Hashtable,也是Map接口的实现类,他和HashMap比较相似,只不过这个类对象是重量级的,也是线程安全的。他不允许Key和value为null。
Properties,这个类是Hashtable的子类,他的Key和value只能是字符串。
SortedMap是Map的子接口
TreeMap,是SortedMap的实现类,他会按照Key进行排序。和TreeSet类一样,在使用自定义类作Key时,要用自定义类实现Comparable接口。
练习:
达内希望在学生毕业的时候统计出学生在校期间考试成绩的排名,写一个Student类,其中用集合来管理每个学生的各个科目的考试成绩,
将多个Student对象放在集合中,打印出学生的总分以及排名
(集合)
改写Bank类,采用集合的方式来管理多个Account对象
为Bank类添加一个方法
打印所有用户的总资产排名
说明:一个用户可能会有多个账号,以身份证号为准.总资产指多个账户余额的总和,不需要考虑贷款账户的贷款额
考试系统
Exam类 考试类
属性: 若干学生 一张考卷
提示:学生采用HashSet存放
Paper类 考卷类
属性:若干试题
提示:试题采用HashMap存放,key为String,表示题号,value为试题对象
Student类 学生类
属性:姓名 一张答卷 一张考卷
Question类 试题类
属性:题号 题目描述 若干选项 正确答案(多选)
提示:若干选项用ArrayList
AnswerSheet类 答卷类
属性:每道题的答案
提示:答卷中每道题的答案用HashMap存放,key为String,表示题号,value为学生的答案
问题:为Exam类添加一个方法,用来为所有学生判卷,并打印成绩排名(名次、姓名)
CoreJava第十一天 2007年5月11日
一、复习
集合:用一个对象储存管理多个对象
Collection:元素都是对象
遍历:迭代遍历
List:元素有顺序,可以重复
遍历:还可以用for循环(下标)
排序:Collections.sort(list)
实现类:
ArrayList:底层数组实现,查询快,而增删慢;轻量级,线程不安全
Vector:底层数组实现,重量级,线程安全
LinkedList:底层链表实现,查询慢,增删快
在Java中用LinkedList实现一个栈,不用数组,因为栈的主要功能就是增删,数组慢;不用Vector,因为效率低
Set:元素无序,元素内容不重复
SortedSet:按照各种排序规则给Set排序
实现类:
HashSet:采用哈希算法保证元素不重复,覆盖hashCode()保证哈希码相同,equals()保证true
TreeSet:元素一定要实现了Comparable接口的
根据排序规则,compareTo()返回,说明是重复元素
Map:元素是键值对
key 无序,不重复 value 可以重复
SortedMap:按照key排序
遍历:values() 遍历所有的值对象
keySet() 遍历所有的键对象
实现类:
HashMap:线程不安全 , 允许null作为key和value
Hashtable:线程安全,不允许
TreeSet:SortedSet的实现类
二、java中的图形界面
GUI,图形化的用户接口,为了人机交互使用的。
BS与CS的联系与区别。
C/S是Client/Server的缩写。服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如Oracle、Sybase、Informix或 SQL Server。客户端需要安装专用的客户端软件。
B/S是Brower/Server的缩写,客户机上只要安装一个浏览器(Browser),如Netscape Navigator或Internet Explorer,服务器安装Oracle、Sybase、Informix或 SQL Server等数据库。
在这种结构下,用户界面完全通过WWW浏览器实现,一部分事务逻辑在前端实现,但是主要事务逻辑在服务器端实现。浏览器通过Web Server 同数据库进行数据交互。
C/S 与 B/S 区别:
1.硬件环境不同:
C/S 一般建立在专用的网络上, 小范围里的网络环境, 局域网之间再通过专门服务器提供连接和数据交换服务.
B/S 建立在广域网之上的, 不必是专门的网络硬件环境,例与电话上网, 租用设备. 信息自己管理. 有比C/S更强的适应范围, 一般只要有操作系统和浏览器就行
2.对安全要求不同
C/S 一般面向相对固定的用户群, 对信息安全的控制能力很强. 一般高度机密的信息系统采用C/S 结构适宜. 可以通过B/S发布部分可公开信息.
B/S 建立在广域网之上, 对安全的控制能力相对弱, 可能面向不可知的用户。
3.对程序架构不同
C/S 程序可以更加注重流程, 可以对权限多层次校验, 对系统运行速度可以较少考虑.
B/S 对安全以及访问速度的多重的考虑, 建立在需要更加优化的基础之上. 比C/S有更高的要求 B/S结构的程序架构是发展的趋势, 从MS的.Net系列的BizTalk 2000 Exchange 2000等,
全面支持网络的构件搭建的系统. SUN 和IBM推的JavaBean 构件技术等,使 B/S更加成熟.
4.软件重用不同
C/S 程序可以不可避免的整体性考虑, 构件的重用性不如在B/S要求下的构件的重用性好.
B/S 对的多重结构,要求构件相对独立的功能. 能够相对较好的重用.就入买来的餐桌可以再利用,而不是做在墙上的石头桌子
5.系统维护不同?
C/S 程序由于整体性, 必须整体考察, 处理出现的问题以及系统升级. 升级难. 可能是再做一个全新的系统
B/S 构件组成,方面构件个别的更换,实现系统的无缝升级. 系统维护开销减到最小.用户从网上自己下载安装就可以实现升级.
6.处理问题不同
C/S 程序可以处理用户面固定, 并且在相同区域, 安全要求高需求, 与操作系统相关. 应该都是相同的系统
B/S 建立在广域网上, 面向不同的用户群, 分散地域, 这是C/S无法作到的. 与操作系统平台关系最小.
7.用户接口不同
C/S 多是建立的Window平台上,表现方法有限,对程序员普遍要求较高
B/S 建立在浏览器上, 有更加丰富和生动的表现方式与用户交流. 并且大部分难度减低,减低开发成本.
8.信息流不同
C/S 程序一般是典型的中央集权的机械式处理, 交互性相对低
B/S 信息流向可变化, B-B B-C B-G等信息、流向的变化, 更像交易中心。
构造图形界面的步骤
1,选择一个容器
2,设置容器的布局管理器
3,向容器添加组件
4,事件的监听
容器(Container)用于管理其他的组件的对象。组件必须放到容器里。
JFrame,这是一个最顶层的窗体容器,所有其他的组件必须放在顶层容器里。
JFrame frame = new JFrame("Hello Swing"); //创建窗体,字符串为窗体的标题
frame.setSize(500,300); //设置窗口大小,500像素长,300像素高
frame.setVisible(true); //设置可见性
JPanel,他不是顶层容器,必须放在顶层容器中,任何一个容器都有add()方法,Panel面板是透明的(默认)。他也是一个组件。
JDialog 对话框容器,他要依附于其父组件,他不是一个顶层容器。
布局管理:对于任何一个容器类中都有setLayout()方法,用容器对象调用这个方法,来设置容器的布局管理器(LayoutManager这是一个接口,所有布局管理器都实现了这个接口)。
可用的布局管理器:
所有的布局管理器实现了一个接口java.awt.LayoutManager
FlowLayout,流式布局管。尝试在一行中按增加顺序摆放组件,窗体大小改变时,组件位置会相应发生改变
Panel的默认布局管理就是FlowLayout。
FlowLayout flow = new FlowLayout();
frame.setLayout(flow);
BorderLayout,按方位进行布局管理,(North,South,East,West,Middle)不明确指定,就会默认加载在中间(Middle),每个部分只能放一个组件
frame.add(Component comp,String place);这个方法是在指定的位置添加组件。
JFrame的默认布局管理器
GridLayout,网格布局,通过行列,间距,来用网格分割,把组件放入如网格中,先行后列摆放组件。可以保证每个组件的大小都是一样的
frame.setLayout(new GirdLayout(3,2)); //把容器平均的分为3行2列,先左后右,先上到下的顺序排列
CardLayout,卡片布局,组件重叠放置。
GridBagLayout,组件可以跨行跨列的网格布局。
*** 注意:一定要在图形界面都其他功能都设置好之后才能设置可见性。
JButton :按钮
JTextField:单行文本域
JTextArea:多行文本区
JPasswordField:密码输入框
JScrollPane:滚动窗体 使用一个多行文本域作为参数创建滚动窗体
JComboBox:下拉选择框
JRadioButton:单选按钮
JCheckBox:多选按钮
JList:多行列表
JLabel:标签
JEditorPane:显示结构化文档
Border:边框
JMenuBar:菜单条
JMenu:菜单
JMenuItem:菜单项
JPopupMenu:弹出式菜单
JSlider:滑动条
JProgressBar:进度条
JTabbedPane:分层面板
JSplitPane:分隔面板
JToolBar:工具条
JFileChooser:文件选择器
JColorChooser:颜色选择器
显示对话框
JoptionPane 里面有很多静态方法可以弹出对话框
注意:具体的方法可以去参看Java2 SE的API文档。
三、awt事件模型(观察者模式)(重点)
事件模型中,包括事件源对象,事件处理者(事件监听者对象),事件对象。
事件源和事件处理者之间建立了授权注册关系,也就是在事件源类中有一个事件处理者的对象作为属性,也可能是一个事件处理者的集合。
事件对象
事件源————————〉事件处理者
这就是事件模型的机制,也就是由事件源对象发送一个消息(事件对象),然后事件处理者调用相应的方法处理事件。
在事件监听器接口中定义的方法,都要以事件对象为参数。
*** 一个事件源可以注册多个同类型的监听器,也可以注册多种多个事件监听器,
一个事件监听器也可以为多个事件源服务。
事件对象继承自EventObject类,并可以通过getSource()方法获得事件源对象,当然需要在构造事件对象时将事件源对象传入,来区分是哪个事件源发出的事件,所以要用事件对象作为参数。
事件源,事件对象,监听接口,在java.awt包中提供了很多已经定义好的,只需要实现监听接口就好了。
什么是发消息:
A,B,C三个类,分别作为事件源,事件处理者,事件对象。
在A类中有一个B类的属性或者是一个内容为B类对象的集合,也就是事件源和事件处理者之间的建立了授权关系,
在B类需要实现一个自定义的接口,这个自定义的接口继承了EventListener,EventListener接口中没有定义任何方法,这只是一个标记接口。
实现在自定义接口中定义好的用于事件处理的方法,C类要继承EventObject类。
这些方法是以事件对象为参数的b(C c),而后在A类a(C c)方法中使用B类的对象调用B类中的b(C c)方法,
并把事件对象作为参数,并在main方法中用A类的对象调用了a(c)方法,这也就叫做A类对象给B类发送了消息。
也就是说事件源对象间接调用了事件监听器的方法,并以事件对象为实参传到事件监听器的方法中,要就叫事件源给事件监听器的方法发了一个消息(事件对象)。
例子如下:
import java.util.*;
//事件源类
class A{
private String test;
private List li=new ArrayList();
public A(String test){
this.test=test;
}
public String getTest(){return this.test;}
public void addB(B b){
this.li.add(b);
}
public void removeB(B b){
this.li.remove(b);
}
/*
* 所谓的事件源给事件监听器,发送事件对象。
* 其实就是事件源用事件为参数,调用时间监听器的相应方法
*/
public void fire(){
C c=new C(this);
Iterator it=li.iterator();
while(it.hasNext()){
B b=(B)it.next();
b.b(c);
}
}
}
//事件监听器的接口,要继承EventListener标记接口
//监听接口中的每一个方法,都应该以对应的时间对象作为参数
interface Blistener extends EventListener{
void b(C c);
}
//事件监听器,实现接口
class B implements Blistener{
public void b(C c){
A a=(A)c.getSource();
System.out.println(a.getTest()+" "+c.getMessage());
}
}
/*
* 事件对象类
* 事件对象类中要封装事件源对象
*/
class C extends EventObject{
private String message;
public C(Object src){
super(src);
}
public void setMessage(String message){
this.message=message;
}
public String getMessage(){return this.message;}
}
public class Test{
public static void main(String[] args){
A a1=new A("Event");
B b1=new B();
c1.setMessage("Test");
a1.addB(b1);//注册监听器
a1.fire();//发送事件
}
}
以上代码只是事例,在引入包之后可以运行。
在Java的图形编程中,所有动作(事件)都已经提供了相应的事件对象和事件监听接口,例如:实现窗口的关闭按钮,点击关闭按钮会发出相应的事件对象,相应的调用监听器中实现好的方法。
相应的方法清参阅Java2 SE API帮助文档。
缺省适配模式,通过一个抽象类实现接口,抽象类中的接口方法实现,都是一个无意义的空实现,可以继承这个抽象类,只覆盖向覆盖的方法就可以了。
在java.awt.event包中,会有一些适配类,也就是把相应的XXXListener,换成XXXAdapter就是适配类。
在java.awt.event包中的ActionEvent类,在以下操作中会发送这个事件,
1,JButton组件,按钮被点击
2,JTextField组件,在单行文本域中按Enter键。
3,JCheckBox组件,选中了复选框。
4,JRadioButton组件,选中了单选按钮。
5,JMenu组件,选中菜单项。
作业:
写一个股市类作为事件源,事件源会随机产生波动,写两个监听类,一个会在股市跌的时候卖出,涨的时候买入,另一个投资逻辑刚好相反
CoreJava第十二天 2007-5-14
一、复习AWT事件模型(Observer模式)
1、事件源
2、事件对象
3、事件监听器
事件源和监听器事先进行授权注册,当事件条件满足时,事件源会给注册的监听器发送一个事件对象,由事件监听器作出相应的处理。
一个事件源可以是多种事件的事件源
一个事件源就同一类事件可以注册多个监听器
一个监听器可以同时注册在多个事件源当中
事件源和监听器是独立,弱耦合的,是各司其职的
事件对象中会封装事件源对象
事件监听接口中的每一个方法都要以事件对象为参数
事件源中要保存和它有监听关系的监听器
事件源给事件监听器发送事件对象:事件源以事件对象作为参数,调用监听器接口的相应方法,通过回调,调用的是不同监听实现类的方法
二、
在Java的图形编程中,所有动作(事件)都已经提供了相应的事件对象和事件监听接口,
例如:实现窗口的关闭按钮,点击关闭按钮会发出相应的事件对象,相应的调用监听器中实现好的方法。
相应的方法清参阅Java2 SE API帮助文档。
缺省适配模式,通过一个抽象类实现接口,抽象类中的接口方法实现,都是一个无意义的空实现,可以继承这个抽象类,只覆盖向覆盖的方法就可以了。
在java.awt.event包中,会有一些适配类,也就是把相应的XXXListener,换成XXXAdapter就是适配类。
适配类是抽象类,其中对接口XXXListener中的方法进行了空实现,实现这个类,覆盖对自己有用的方法
在java.awt.event包中的ActionEvent类,在以下操作中会发送这个事件,
1,JButton组件,按钮被点击
2,JTextField组件,在单行文本域中按Enter键。
3,JCheckBox组件,选中了复选框。
4,JRadioButton组件,选中了单选按钮。
5,JMenu组件,选中菜单项。
添加事件监听:
1、实现监听接口
2、将监听器对象注册在组件(事件源)中
ActionEvent
事件源 --- 组件 JButton 按钮 点击触发ActionEvent
JTextField 单行文本域 输入内容以后回车触发ActionEvent
jtf.getText(); //得到文本域中的内容
练习:
1、写一个图形界面,采用BorderLayout布局,中间的部分放置一个可以滚动不可编辑的JTextArea,南面放置一个可以编辑的JTextField,
但在TextField中输入文字并按下回车的时候,文字会添加到TextArea中
2、为BAM添加用户界面
需要以下几个类:
BAMClient 其中会包含一个Frame,这是用户主界面
MainPanel:主界面,用户可以选择开户或者登录
RegisterPanel:用户开户具体用到的界面
LoginPanel:用户登录需要的界面
BusinessPanel:界面上会显示账户的功能 至少包括存款和取款,对于可透支的用户,还允许用户修改透支额度,对于贷款用户,还允许用户贷款和还贷款
注:本练习的界面布局不做要求,请阅读现有代码,添加事件处理代码
提示:在开户或者登录之后都会跳到BusinessPanel,而用户点击了交易之后,界面停留在BusinessPanel
要随时注意在BusinessPanel上根据数据的变化更新显示信息
三、多线程
C++的多进程是OS系统并发的一个任务
Java中没有多进程,一个JVM就是一个进程
线程是在进程中并发的一个顺序的执行流程
多进程:划分时间片,宏观上并行,微观上串行
多线程:cpu在进程内部再划分时间片
CPU ,代码 ,数据
进程:进程间数据独立
线程:数据空间共享,堆空间的共享(堆空间中存放的是对象),栈空间是独立的
所以线程间切换容易,称为轻量级进程
一个线程对象代表了一个线程,并非就是一个线程
线程是操作系统中负责维护的资源
java.lang.Thread类的一个对象就代表一个线程
线程是底层OS维护的资源,JVM跑在OS上,在JVM中创建一个Thread对象,调用其start()方法,底层OS会申请一个线程资源,线程对象可到底层管理一个线程
创建好线程之后,把要让线程执行的代码封装到线程对象中(覆盖run()方法)
实现线程代码的方式:
1、继承Thread 类,覆盖run()方法
去底层申请线程并运行,对线程对象调start()方法,main方法是一个主线程
宏观并行,微观串行
2、实现Runnable接口
使用多态获得Runnable对象,成为目标对象
再利用目标对象构造线程对象 Thread t = new Thread(target);
四、多线程的状态转换图(7状态图)
见另一文件,名为Thread.pdf
作业:
用两种方式实现两个线程,一个线程负责打印1-2600,另一个线程打印A-Z,反复打印100遍
CoreJava Day13
13.1
■ 阻塞状态
▲ 初始状态 ▲阻塞状态 ▲终止状态
\ / ^ 1 ^
\ / \ 2sleep /
\start / \ 3join /stop
\ / \ /
V V \ /
▲ 可运行状态 _ _ _ _ OS选中 _ _ _ _\ ▲运行状态
(只缺CPU) \ CPU到期或调用yield
下面为线程中的7中非常重要的状态:(有的书上也只有认为前五种状态:而将“锁池”和“等待池”都看成是“阻塞”状态的特殊情况:这种认识也是正确的,但是将“锁池”和“等待池”单独分离出来有利于对程序的理解)
1,初始状态,线程创建,线程对象调用start()方法。
2,可运行状态,也就是等待Cpu资源,等待运行的状态。
3,运行状态,获得了cpu资源,正在运行状态。
4,阻塞状态,也就是让出cpu资源,进入一种等待状态,而且不是可运行状态,有三种情况会进入阻塞状态。
1)如等待数据输入(输入设备进行处理,而CPU不处理),则放入阻塞,直到输入完毕,阻塞结束后会进入可运行状态。
2)线程休眠,线程对象调用sleep()方法,阻塞结束后会进入可运行状态。
public static void sleep(long millis)
throws InterruptedException
括号中以毫秒为单位, 使线程停止一段时间,间隔期满后,线程不一定立即恢复执行。
当main()运行完毕,即使在结束时时间片还没有用完,CPU也放弃此时间片,继续运行其他程序。
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace(e);
}
线程中有异常,只能trycatch,子类中不能抛出比父类更多的异常,父类run方法没有抛出异常。
3)线程对象2调用线程对象1的join()方法,那么线程对象2进入阻塞状态,直到线程对象1中止。
public final void join() throws InterruptedException
表示其他运行线程放弃执行权,进入阻塞状态,直到调用线程结束。
实际上是把并发的线程变为串行运行。
t1 num
t2 char if(c=='m') -> t1.join()
//t2对t1调用join,t2进入了阻塞状态
//当条件成立时,t1加入打印数字,一直到打印完,此时t2继续运行
5,中止状态,也就是执行结束。
6,锁池状态
7,等待队列
13.2 共享数据的并发处理
13.2.1
■ 数据的错误发生
多线程并发访问同一个对象(临界资源)
破坏了原子操作,就会发生数据不一致的情况
13.2.2 共享数据的并发处理
■ 多线程同时并发访问的资源叫做临界资源。
多个线程同时访问对象并要求操作相同资源时分割了原子操作就会出现问题。(原子操作,不可再分的操作)会出现数据的不一致或数据不完整,为避免这种现象采用对访问的线程做限制的方法。
■ 互斥锁机制,利用每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个琐是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程。
■ Synchronized用法
1.Synchronized修饰代码块(同步代码块),
public void push(char c){
synchronized(this)//只有持有当前对象锁标记的线程才能访问这个代码块
{
...
}
}
对括号内的对象加锁,只有拿到锁标记的对象才能执行该代码块
2.Synchronized修饰方法
public synchronized void push(char c) {
...
}
在整个方法里,对当前对象的加锁,只有拿到锁标记的对象才能执行该方法。
■
▲ 初始状态 ▲阻塞状态 ▲终止状态
\ / ┍ 1 ┓
\ / \ 2sleep /
\start / \ 3join /stop
\ / \ /
┙ ┕ \ /
▲ 可运行状态 _ _ _ OS选中 _ _ _\ ▲运行状态
(只缺CPU) \ CPU到期或调用yield
┍ /
\ /
\ Synchronized/
\ /
\ ┕
▲ 锁池状态
锁池:一个空间,每个对象都有一个,用来存放等待锁标记的线程
当一个对象中有了锁标记,不会释放其它对象的锁标记。
当t1线程正在访问对象O的同步方法时,别的线程t2不能访问O的任何同步方法,但还是可以访问其它的非同步方法
■ ArrayList Vector
list
△ △
/ \
/ \
/ \
ArrayList Vector(所有方法都做成了同步)
■ 构造方法 ×对象没有完全构造好了,没有当前对象概念
抽象方法 ×抽象方法没有代码块,没用
静态方法 √是对类对象的加锁
☆注意:构造方法不能Synchronized修饰
静态方法可以用Synchronized修饰(是对类对象加锁,类对象会在反射时讲到)
抽象方法不能用Synchronized修饰,不影响子类覆盖,子类在覆盖这个方法是可以加Synchronized,也可以不加Synchronized,所以根据Java不允许写废代码的特点是不能写在一起。
■ 练习:
1、用数组实现个栈;一个线程负责入栈一个线程负责出栈;长度为6
char[6],
T1, Push A~Z
T2, Pop
长度为6,并且不允许扩充
-----------------
注意:对当前对象加锁,一个代码块或者方法是同步的(Synchronized),当前对象的锁标记没有分配出去时,有一个线程来访问这个代码块时,就会的到这个对象的锁标记,直到这个线程结束才会释放着个锁标记,其他想访问这个代码块或者是方法线程就会进入这个对象锁池,如果没有得到当前对象的锁标记,就不能访问这个代码块或者是方法。当一个线程想要获得某个对象锁标记而进入锁池,这个线程又持有其他对象的锁标记,那么这个线程也不会释放持有的锁标记。
注:方法的Synchronized特性本身不会被继承,只能覆盖。
线程因为未拿到锁标记而发生阻塞进入锁池(lock pool)。每个对象都有自己的一个锁池的空间,用于放置等待运行的线程。由系统决定哪个线程拿到锁标记并运行。
使用互斥锁的注意事项
举例:男孩和女孩例子,每个女孩是一个对象,每个男孩是个线程。每个女孩都有自己的锁池。每个男孩可能在锁池里等待。
Class Girl{
Public void hand(){
}
Public syncronized void kiss(){
}
}
Class Boy extends Thread{
Public void run(){
}
}
注意:只读不用加同步,只写也不用加同步,只有读写操作兼而有之时才加同步。
注意:在java.io包中Vector 和 HashTable 之所以是线程安全的,是因为每个方法都有synchronized修饰。Static 方法可以加synchronized , 锁的是类对象。但是Vector 是 jdk 1.0 的 ArrayList 是 jdk1.2 所以实际应用还是使用ArrayList。
注意:内同步,外同步,内同步,即,类内的方法加同步(synchronized)修饰,外同步即,在需要控制只能由一个线程进行访问时,把需要控制的方法写在同步代码块里。