因为学过了C#,跟java从外观上长得差不多,故而我就以看java文档的方式进行学习了
1.final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义(可以被继承),修饰的变量为常量,是不可修改的。
被 final 修饰的实例变量必须显式指定初始值。
final 修饰符通常和 static 修饰符一起使用来创建类常量。
被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final
2.String类
3.Java 程序的 main() 方法必须设置成公有的,否则,Java 解释器将不能运行该类。
4.
类的继承不用子:父的形式,而是子 extends 父。类中方法重写不用override修饰
5.protected 需要从以下两个点来分析说明:
- 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
- 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。
6.
请注意以下方法继承的规则:
- 父类中声明为 public 的方法在子类中也必须为 public。
- 父类中声明为 protected 的方法在子类中要么声明为 protected,要么声明为 public,不能声明为 private。
- 父类中声明为 private 的方法,不能够被继承。
7.
java不同于C#,没有属性。对于私有字段的获取,是手动写get、set方法
8.
不然默认左移补0,右移补1
9.
判断一个实例是否是某个类,C#用的是james is Person,java则用的是james instanceof Person
10.
java的foreach循环
11.
这点跟C#似乎差不多
12.C#用的是parame修饰
13.
在对象被销毁之前调用。
c2和c3被设置成了null,因而一开始的那两个对象变成了没人牵着的垃圾,所以被销毁了。而且销毁顺序是系统栈,即后创建先销毁。
14.java的Scanner类
用于获取输入。
一般使用hasNext方法或者hasNextLine方法看是否输入结束
hasNext会忽略前面的空白,遇到空格回车等分隔符后结束输入。
nextLine()方法返回的是输入回车之前的所有字符
输入其他数据类型,hasNext后面安排上即可。
15.
java的包似乎相当于C#的名称空间。
C#使用using+namespace来引用名称空间,java使用的是import+package。
16.这些什么玩意没看懂
17.继承
继承特性:
- 子类拥有父类非 private 的属性、方法。
- 子类可以对父类进行扩展。
- 子类可以对父类方法重写
- 提高了类之间的耦合性(继承的缺点
java继承有implement关键字和extends关键字。前者可以用于继承多个接口,后者只能用于继承一个父类
在子类中,可以使用super来实现对父类成员的访问。相应的C#中是base。
关于继承的构造器。
java跟C#一样,子类不继承父类的构造器,只是调用。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
super也可以调用父类的方法。
18.重写
重写的返回值和形参都不能改变
重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
而且java跟C#一样,都是只能调用到变量的引用类型所具有的最新版本的方法(当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。)
java的重写不用加什么override、virtual,只需与原来的一样就行。C#需要在父类被重写方法中加virtual,在子类方法中加override。
重写规则
- 参数列表必须完全与被重写方法的相同。
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
- 子类和父类在同一个包中,那么子类可以重写父类所有可以被重写的方法
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。(失去了default)
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
19.安装Eclipse遇到的问题
20.多态的实现方法
重写、接口、抽象类
21.抽象类
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。
在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。
22.接口
- 接口不能包含成员变量,除了 static 和 final 变量。
这样是可以的
- 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
接口可以继承多个接口,用extends,逗号分隔
23.标记接口
最常用的是标记接口,它里面没有方法和属性,用途就是给类打个戳分个类。它的使用目的:
-
建立一个公共的父接口:
正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。
-
向一个类添加数据类型:
这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。
24.包
包声明应该在源文件的第一行,每个源文件只能有一个包声明
包的名字通常使用小写。
好像意思是,package语句后面跟着的是你这个类所属的包。import语句跟着的是你这个类要用的其他包
25.java
26.接口的应用
通常建议把静态常量都定义到一个或几个接口中。
应用于工厂模式,解耦合
生产接口实例,防止userdaojdbcimpl这个用不了替换后要改太多代码
应用于命令模式
通过接口传递逻辑(方法)
27.内部类
一般较多使用静态内部类和匿名内部类
内外部类部分成员的访问要求两个类的静态与非静态属性一致
内部类可以通过外部类.this.变量名 来访问外部类的成员(不论是私有还是共有都行)
同理,外部类也可以访问到内部类的私有成员
在外部类之外实例化非静态内部类需要先实例化外部类:
静态内部类的实例化方法
28.匿名内部类
格式
例如上面这段代码,comparator只用一次,所以可以使用匿名类。
29.枚举类
在实例上实现接口方法
对于所有实例统一实现接口方法
实例:
遍历枚举类
枚举类的构造器
package Helloworld;
public enum Example {
EXAM1("一")
{
public void Print() {
System.out.println("I'M "+this.getName());
}
},
EXAM2("二")
{
public void Print() {
System.out.println("I'M "+this.getName());
}
};
public abstract void Print() ;
private final String name;
Example(String name){
this.name=name;
}
public String getName() {
return this.name;
}
}
30.java的各种api
31.注释规范
导出文档注释
32.System类
system指的就是操作系统。
System类的构造器是private的,所以不能实例化
33.Runtime类
代表java程序的运行环境(JRE)
采用单例模式,只能用getRuntime()获得该类的唯一实例。
具体实现方法:
可以有用cmd差不多的效果
33.Scanner类
记得结束
可以从文件、输入流、字符串中等等扫描
可以修改分隔符
34.Object类
方法都可以重写
其中的克隆
如果要让某个类能被克隆,一定要实现该接口
注意,克隆的时候,对于类里的引用类型成员地址还是一样的
35.包装类
可以用于多态,面向对象更加彻底
【通过方法类型转换】
但是C#似乎没有这个说法,包括int等等在内,所有都派生自object类。这点可能是C#的语法糖,因为点go to definition后跳到的struct名称叫作Int32.
api:
一是转字符串
值转字符串也只能声明Integer包装类。这点确实不如C#方便
二是比较大小
三是创建:
使用语法糖或者valueOf方法
36.不可变类
37.String
String是一个不可变类,因而其实例创建后内部的字符序列不能改变
常用:
1.length 2.equals(引用类型,非引用类型喜欢用“==”)
原理同C语言,两图不同。
但是C# 就不一样了,不会有比地址的蛋疼想法。语法糖++;
3.equalsIgnoreCase忽略大小写比较
4.
38.打印数组
39.StringBuilder类
39.异常
异常类继承体系
Throwable代表所有非正常情况,包含错误和异常。错误指的是虚拟机相关问题,一般无法处理,不管了
Exception分为两类。
RuntimeE可以显式处理,也可以不处理而交给顶层调用者(比如main)
非运行异常(checked异常)必须显式处理,要么捕获要么抛出。
捕获异常
捕获异常可以用try-catch语句
回收资源在finnally语句块进行。但java7后try语句功能增强,可以自动关闭try括号块内声明的资源。
抛出异常
throws 声明时抛出异常的类
用于标识某方法可能抛出的异常,位于方法签名之后。
throw 抛出异常实例
注意是throws
自定义异常
public class helloworld {
public static void main(String[] args) {
}
public static String getUserName() throws UserNameException{
System.out.println("请输入:");
try (Scanner scanner = new Scanner(System.in);)
{
String userName = scanner.nextLine();
if (userName.matches("^[0-9a-zA-Z_]{6,20}$"))
return userName;
else
throw new UserNameException("用户名输入错误!");
}
catch(Exception i)
{
throw new UserNameException("输入失败!",i);
}
}
}
class UserNameException extends Exception{
public UserNameException() {
super();
}
public UserNameException(String message, Throwable cause) {
super(message, cause);
}
public UserNameException(String message) {
super(message);
}
}
40.集合
紫色为接口,蓝色为类
外面套的阴影表示实现类很常用
41.Iterator迭代器
是个接口,用于遍历Collection
在迭代过程对集合元素进行改动会影响迭代。
public static void main(String[] args) {
Collection c=new ArrayList();
c.add("唐僧");
c.add("悟空");
c.add("八戒");
c.add("沙僧");
c.add("白龙马");
Iterator i=c.iterator();
while(i.hasNext()) {
Object o=i.next();
//将指针移到下一个元素,同时返回该下一个元素
if(o.equals("沙僧")) {
i.remove();
//只能通过iterator迭代器删
}
}
i=c.iterator();
//遍历完一次后必须更新
while(i.hasNext()) {
System.out.println(i.next());
}
}
C#也是一样,不能边遍历边删除。
C#中的迭代器为IEnumerator接口。并且似乎直接ban掉了接口的remove方法
ICollection<string> c = new List<string>();
c.Add("唐僧");
c.Add("悟空");
c.Add("八戒");
c.Add("沙僧");
c.Add("白龙马");
IEnumerator<string> i = c.GetEnumerator();
while (i.MoveNext())
{
if (i.Current.Equals("悟空"))
{
//接口无remove方法
}
}
i = c.GetEnumerator();
while (i.MoveNext())
{
Console.WriteLine(i.Current);
}
42.set
set通常记不住元素添加的顺序,是无序的,不能包含同样的元素(add的返回值为false)。
不能根据索引访问
HashSet
实现类
1.HashSet 无序 元素值可以是null 非线程安全
2. LinkedHashSet 有序(添加顺序) 采用链表结构维护元素的顺序 +hashset特点
3.TreeSet 有序(内部排序) 支持两种排序,比2灵活
比HashSet多了方法:返回最后一个(第一个元素)、返回位于指定元素之前(之后)的元素、返回某个范围中的元素子集
TreeSet似乎要求集合里的元素只能是同一种,因为它会内部排序。
要么全部字符串,要么是Integer诸如此类,就连int也不能自动转化为double
TreeSet内部采用红黑树的数据结构,支持自然排序或者定制排序。
自然排序:
添加时调用添加元素类型的compareTo方法,升序排序
定制排序:
创建TreeSet时,传入Comparator接口的实例
两头出
反转
匿名内部类+自制比较函数
public class helloworld {
public static void main(String[] args) {
TreeSet c=new TreeSet(new Comparator(){
@Override
public int compare(Object o1, Object o2) {
Number n1=(Number) o1;
Number n2=(Number) o2;
if(n1.intValue()==n2.intValue()) return 0;
if(n1.intValue()==1) return 1;
if(n2.intValue()==1) return -1;
if(n1.intValue()>n2.intValue()) return 1;
return -1;
}
});
for(int i=0;i<10;i++) {
c.add(i);
}
System.out.println(c);
}
}
选择
1.HashSet的性能永远比TreeSet好,因为TreeSet内部通过红黑树算法维护元素顺序
2.插入删除HashSet快,查询遍历LinkedHashSet快
43.List
代表有序(添加顺序,部分子类也有sort方法)集合,提供了根据索引访问集合的方法。
插入、删除、返回指定索引元素、返回元素索引值、返回两个索引间的子集
ListIterator方法返回ListIterator对象,继承于Iterator接口(有next、hasnext、remove方法),还有hasPrevious、previous、add的对应方法。允许双向遍历。
add方法可以添加数字到索引末尾,可以在指定位置添加一个Object。加啥都行
public static void main(String[] args) {
ArrayList list=new ArrayList();
for(int i=0;i<10;i++) {
list.add(i);
}
list.add(5, 100);
list.add(5, list);
ArrayList temp=new ArrayList();
temp.add(1000);
temp.add(123);
list.add(5,temp);
list.add(5,"aha");
list.add(5,null);
System.out.println(list);
}
ArrayList内部封装了一个长度可变的Object类型的数组。默认初始数组长度为10,也可以在构造器显式指定初始长度。
ArrayDeque
Arrays工具类有个内部类ArrayList。
不能是int,得是Integer。int数组不能自动转成Integer,会被当成一整个对象。
44.Queue
一般使用offer
java的优先队列 PriorityQueue
引用自Java优先队列使用_jasonkwan12的博客-CSDN博客_java优先队列offer
Deque
还增加了栈方法
LinkedList 既实现了List又实现了Deque。具有集合和集合的特点
因而,LinkedList相比于ArrayList和ArrayDeque,访问效率低,增删效率高
45.Map
Set的底层用Map实现
HashMap
HashMap是典型的Map实现类。它无序,线程不安全。它的子类LinkedHashMap有序(添加顺序)
遍历操作
46.工具类
排序
List list=new ArrayList();
addNums(list);
print(list);
//随机乱序重排(洗牌)
Collections.shuffle(list);
print(list);
//升序排序
Collections.sort(list);
print(list);
//转化为降序
Collections.reverse(list);
print(list);
查找、替换
List list=new ArrayList();
addNums(list);
print(list);
//找到最大、最小对象,适用所有Collection
System.out.println(Collections.max(list));
//只适用List
//必须先排序才能二分查找
Collections.sort(list);
//找到对应value,返回下标索引
System.out.println(Collections.binarySearch(list, 99));
//替换
Collections.replaceAll(list, 60, 100);
print(list);
47.hashCode()
48.泛型的意义
限制集合元素类型、可以不在遍历时强制类型转换
允许创建集合时规定元素类型
后面尖括号没有东西
自定义泛型
这个Number不能限定类型,只能算是T、E这种参数名
49.类型通配符
解决,定义一个方法,方法参数需要用到泛型类型(其中,类型内的Ttype可能需要重载),这样的问题
这样可读性差
这样不行
因为String是Object的子类,但是List<String>不是List<Object>的子类
应该这样写
但注意不能这样,因为Integer类型和?类型不匹配
类型限制
50.泛型方法
比如说,一个arrayToList方法,参数为array和要填进东西的List<>。List<>不能定死为Object;也不能使用通配符,因为通配符了后就不能使用add方法。此处就需要用泛型方法。
public static <T> void arrayToList(T[] arr,List<T> goal) {
for(int i=0;i<arr.length;i++) {
goal.add(arr[i]);
}
}
调用时无需指定T,会自动推断
51.泛型方法与通配符的区别与联系
泛型方法可以指定参数间的关系
如果以上都不需要,用通配符会比较方便。
52.擦除与转换 使用泛型的两个隐含规则
53.File类
File可以表示文件,也可以表示目录
文件操作:
平台无关:操作系统无关
文件的创造、删除、改名操作
public static void main(String[] args) {
File file=null;
file=new File("C:\\Users\\17765\\Desktop\\AHA.txt");
try {
file.createNewFile();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//file.delete();
file.renameTo(new File("C:\\Users\\17765\\Desktop\\AA.txt"));
}
目录操作:
public static void main(String[] args) {
File file=new File("C:\\Users\\17765\\Desktop");
//打印目录下所有文件名.listFile()方法获取文件对象的数组
System.out.println(Arrays.toString(file.list()));
//file.mkdir();//创建新目录
System.out.println(Arrays.toString(file.getParentFile().list()));
}
上面对文件的操作有些也适用
当listFile时,可以传入参数对文件进行过滤。该参数其实就是一个接口。
File file=new File("C:\\Users\\17765\\Desktop");
//打印目录下符合要求的文件名.listFile()方法获取文件对象的数组
System.out.println(Arrays.toString(file.listFiles(new FilenameFilter(){
@Override
public boolean accept(File dir, String name) {
if(name.contains("乱写"))
return true;
return false;
}
})));
遍历文件目录
//递归打印所有文件夹名
public static void print(File file,int depth) {
if(!file.exists()) throw new IllegalArgumentException("文件不存在。");
for(int i=0;i<depth;i++) {
System.out.print(" ");
}
if(!file.isDirectory()) System.out.print('-');
System.out.println(file.getName());
if(file.isDirectory()) {
for(File f : file.listFiles()) {
print(f,depth+1);
}
}
}
54.
红色为节点流【低级流】,蓝色为处理流【高级流】
但是用节点流效率低
用缓冲流会好一些。
55.
重定向
56.RAF 其实可控,可以指定位置
但是这样会是乱码
这样才行
57.序列化
这玩意我的eclipse用不了,非常难绷
反序列化不会调用构造器制造新的对象
所以这时候反序列化会出错。这时候就需要序列化的版本
数字会用工具随机生成得很长,不重复
有种散列那味了
反序列化时,只有能匹配的上的成员变量可以被赋值。
只能修饰成员变量
read和write写的过程要呈反义关系,不能乱写
灵活度高,可以指定要序列化的对象、序列化对象的方式(如对密码加密的方式)
58.NIO
即new IO,比IO效率高,尽量使用
NIO不是用流的方式,采用的是内存映射方式
红字频繁
mark就是一个标记,标志某处特殊
图解示意
clear后,数据没被清理,只归位指针
channel是个接口
实例化channel类的方法:调用channel接口统一实现的open()方法
59.字符集
charset
public class helloworld {
public static Charset charset=Charset.forName("UTF-8");
public static CharsetEncoder charsetEncoder=helloworld.charset.newEncoder();
public static CharsetDecoder charsetDecoder=helloworld.charset.newDecoder();
public static void main(String[] args) {
TestEncoder();
}
public static void TestEncoder() {
try (
FileChannel channel=new FileOutputStream("C:\\Users\\17765\\Desktop\\haha.txt").getChannel();
){
//字符Buffer
CharBuffer charBuffer=CharBuffer.allocate(1024);
charBuffer.put("白日依山尽\n");
charBuffer.put("黄河入海流\n");
charBuffer.flip();
//字符缓冲转字节缓冲
ByteBuffer byeBuffer=charsetEncoder.encode(charBuffer);
channel.write(byeBuffer);
} catch (Exception e) {
// TODO: handle exception
}
}
}
60.工具类Path、Files
61.线程
进程具有独立性、动态性、并发性
线程
电脑可以有多个进程,一个进程内可以有多个线程。每个进程之间占用的系统资源不相互干扰,而线程不占有系统资源,多个线程共享进程的系统资源
进程可以并发并行,进程内的线程也能并发并行
62、java的线程模型
java的线程类叫Thread。所有线程对象必须是Thread的子类或Thread的实例
线程的使用过程:定义线程执行体——创建线程对象——调用线程对象的方法以启用线程
!!!定义线程体
1、继承Thread类
2.使用runnable接口
3.实现Callable接口(只有该方法线程可以有返回值、可以抛出处理异常)
返回值建议在程序末尾获得,不要在某一线程开始之前获得。因为它是阻塞方法,执行到它会停住等待其对应线程执行完再继续。这会损耗多线程并发的优势。
63.线程的生命周期与状态转换
main方法最后,线程死亡,调start会报错
阻塞
单位是微秒
64.join
调用方停在那,等待该线程执行完再继续执行。
或者停在那,等线程参数时间,如果还没执行完就不等了
而且正好是停在10
65
66.线程的优先级
优先级越高,运行机会越多。
在哪创建线程,哪就是线程的父线程。比如在main里创造thread1,那么thread1的父线程就是main
线程的优先级默认与父线程相同
主线程是普通优先级
67.线程同步
手段是加锁
使用synchronized
具体做法可以同步代码块
synchronized(obj){..需要同步的代码..}
也可以同步方法
把synchronized作为方法修饰符
Lock接口解决了问题
使用Lock加锁
public class helloworld {
public static void main(String[] args) throws InterruptedException {
Ticket ticket=new Ticket(100);
Sell sellTaskSell=new Sell(ticket);
//线程不同,线程体相同
new Thread(sellTaskSell,"John").start();
new Thread(sellTaskSell,"Marry").start();
new Thread(sellTaskSell,"Lucyl").start();
new Thread(sellTaskSell,"Lily").start();
new Thread(sellTaskSell,"Aha").start();
}
}
class Sell implements Runnable {
private Ticket kindOfTicket;
public Sell(Ticket ticket) {
kindOfTicket=ticket;
}
public Ticket getKindOfTicket() {
return kindOfTicket;
}
public void run() {
kindOfTicket.buy(1);
}
}
class Ticket{
private Lock lock=new ReentrantLock();
private int amount;
public Ticket(int amount) {
this.amount=amount;
}
public int getAmount() {
return amount;
}
public void buy(int amount) {
if(amount>this.amount) {
throw new IllegalArgumentException(Thread.currentThread().getName()+" : 购买失败!余量不足。余量 : "+this.amount);
}
lock.lock();
try {
this.amount-=amount;
//每个人是一个线程
System.out.println(Thread.currentThread().getName()+" : 购买成功!"+"购买数量 : "+amount+",余量 : "+this.amount);
} finally {
lock.unlock();
}
}
}
68.死锁
当线程AB都锁住同一个资源,A等B解锁B等A解锁就会卡住。
为了避免死锁,我们应该遵循以下原则:
1.尽量避免同一个线程对多个同步监视器锁定
2.按相同的顺序加锁。如果多个线程需要对多个同步监视器加锁,应该保证它们以相同的顺序请求加锁
3.使用可以超时释放的锁。
69.线程通信
很像c#的事件
执行到wait会释放锁,会停住,等到notify之后才会收到通知继续执行
用synchronized:
调Object的方法
等待状态和阻塞状态很像,区别在于阻塞状态依然是锁住的,等待状态却会释放锁
public class helloworld {
public static void main(String[] args) throws InterruptedException {
Product product=new Product(0);
SellTask sellTask=new SellTask(product);
for(int i=0;i<20;i++) {
new Thread(new BuyTask(product),"顾客"+i).start();;
}
Thread.sleep(3000);
new Thread(sellTask,"maijia").start();
}
}
class Product{
private int amount;
public Product(int amount) {
this.amount=amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount=amount;
}
}
class BuyTask implements Runnable{
private Product product;
public BuyTask(Product product) {
this.product=product;
// TODO Auto-generated constructor stub
}
@Override
public void run() {
synchronized (product) {
while(product.getAmount()==0) {
System.out.println(Thread.currentThread().getName()+" is waiting!");
try {
product.wait();
//wait时已经丢了锁,再往下会先去抢锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
product.setAmount(product.getAmount()-1);
System.out.println(Thread.currentThread().getName()+"抢到了,库存剩下"+product.getAmount());
}
}
}
class SellTask implements Runnable{
private Product product;
public SellTask(Product product) {
this.product=product;
}
@Override
public void run() {
synchronized (product) {
product.setAmount(10);
System.out.println(Thread.currentThread().getName()+"上架了十件商品。剩余"+product.getAmount());
product.notifyAll();
}
}
}
用Lock:
public class helloworld {
public static void main(String[] args) throws InterruptedException {
Product product=new Product(0);
SellTask sellTask=new SellTask(product);
for(int i=0;i<20;i++) {
new Thread(new BuyTask(product),"顾客"+i).start();;
}
new Thread(sellTask,"maijia").start();
}
}
class Product{
private int amount;
public Product(int amount) {
this.amount=amount;
}
public int getAmount() {
return amount;
}
public void setAmount(int amount) {
this.amount=amount;
}
}
class BuyTask implements Runnable{
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
private Product product;
public BuyTask(Product product) {
this.product=product;
// TODO Auto-generated constructor stub
}
@Override
public void run() {
lock.lock();
try {
while(product.getAmount()==0) {
try {
System.out.println(Thread.currentThread().getName()+" is waiting!");
condition.await();
} catch (Exception e) {
// TODO: handle exception
}
}
product.setAmount(product.getAmount()-1);
System.out.println(Thread.currentThread().getName()+"抢到了,库存剩下"+product.getAmount());
} finally {
lock.unlock();
}
}
}
class SellTask implements Runnable{
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
private Product product;
public SellTask(Product product) {
this.product=product;
}
@Override
public void run() {
lock.lock();
try {
product.setAmount(10);
System.out.println(Thread.currentThread().getName()+"上架了十件商品。剩余"+product.getAmount());
condition.signalAll();
//此处不要用错
} finally {
lock.unlock();
}
}
}
注意,await,wait,notifyAll,signalAll不要用错,不然会寄
70、阻塞队列
生产者消费者模式
包含了生产者、消费者、缓冲区这三个概念。
生产者生产产品到缓冲区,消费者消费产品从缓冲区。
缓冲区有固定大小,可以防止过度生产
java中,阻塞队列可以用来解决这个模式。
BlockingQueue(阻塞队列)是Queue的一个子接口。它主要作用是线程通信
主要实现类:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue
我还是不大明白,这玩意不用跟锁一起用吗?
public class helloworld {
public static void main(String[] args) throws InterruptedException {
BlockingQueue blockingQueue=new ArrayBlockingQueue(5);
Product product=new Product();
SellTask sellTask=new SellTask(blockingQueue);
for(int i=0;i<20;i++) {
new Thread(new BuyTask(blockingQueue),"顾客"+i).start();;
}
new Thread(sellTask,"maijia").start();
}
}
class Product{
}
class BuyTask implements Runnable{
private BlockingQueue blockingQueue;
public BuyTask(BlockingQueue queue) {
blockingQueue=queue;
// TODO Auto-generated constructor stub
}
@Override
public void run() {
try {
blockingQueue.take();
System.out.println(Thread.currentThread().getName()+"抢到了,库存剩下"+blockingQueue.size());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class SellTask implements Runnable{
private BlockingQueue blockingQueue;
public SellTask(BlockingQueue queue) {
blockingQueue=queue;
}
@Override
public void run() {
try {
for(int i=0;i<10;i++) {
blockingQueue.put(new Product());
}
System.out.println(Thread.currentThread().getName()+"上架了十件商品。剩余"+blockingQueue.size());
//此处不要用错
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
71.线程组
ThreadGroup类
每个线程有线程组,没显式指定则属于默认线程组。
默认线程组:子线程与父线程属于同一个线程组
一旦加入线程组就固定了,不许改
创建线程组
创建线程时指定线程组
线程组常用方法:
得到名字、得到父线程组名字、当前活跃的线程数量、列举所有线程、中断线程组中所有线程、返回最高优先级、设置最高优先级、返回是否为后台线程、设置为后台线程、对线程抛出异常进行统一处理
关于最后一个功能的详解:
Thread有一个set方法可以指定该线程异常处理器。当一个线程抛出异常,jvm会找到该线程的异常处理器,若找到则用该处理器处理,否则就调用所在线程组的处理器处理。(注:ThreadGroup类已经默认实现了UncaughtException这个异常处理器接口)
public class helloworld {
@SuppressWarnings("removal")
public static void main(String[] args) throws InterruptedException {
//主线程线程组
ThreadGroup threadGroup=Thread.currentThread().getThreadGroup();
System.out.println(threadGroup.getName());
System.out.println(threadGroup.getParent().getName());
System.out.println(threadGroup.getMaxPriority());
System.out.println(threadGroup.isDaemon());
threadGroup.list();
//子线程
Thread thread=new Thread(new MyThread());
System.out.println(thread.getThreadGroup().getName());
//指定线程组
ThreadGroup threadGroup2=new ThreadGroup("MyThreadGroup");
threadGroup2.setDaemon(true);
Thread thread2=new Thread(threadGroup2,new MyThread(),"haha");
System.out.println(thread2);
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(System.currentTimeMillis());
}
}
//异常处理器
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
//或(UncaughtExceptionHandler) new UncaughtExceptionHandler() {
//只写new UncaughtExceptionHandler() {会报错
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("ah");
}
}
);
System.out.println(3/0);
72、线程池
线程池可以提高线程复用,减少损耗。线程池还可以控制系统线程数量,避免超出系统负荷
1.创建线程池
ExecutorService接口代表线程池,ScheduledExecutorService是其子接口,代表可执行定时任务的线程池【比如说每隔一分钟如何如何】
Executors是工厂类(工具类的一种,专门用来创建对象)
用于创建线程池的静态方法:
2.使用
一般只用第一个和第三个
delay 延迟几秒执行 timeunit 时间单位
前两个只执行一遍
scheduleAtFixedRate周期性执行 period 周期
最后一个,以固定的延迟执行 第一次延迟的时间 以后延迟的时间
与上一个不同的是,最后一个不会考虑线程的执行时间,比如延迟固定3s,你不论执行1s还是2s,一定要再等3s才能下一次。倒二个则考虑,比如周期固定3s,你线程执行了1s,只需再等2s就可以执行下一个线程
public class helloworld {
//线程池一般是public且为成员变量
private static ExecutorService executorService=Executors.newFixedThreadPool(3);
@SuppressWarnings("removal")
public static void main(String[] args) throws InterruptedException {
//匿名创建线程体
Runnable threadTaskRunnable=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 完成了");
}
};
//让线程池帮忙执行任务
for(int i=0;i<10;i++) {
executorService.submit(threadTaskRunnable);
}
}
}
public class helloworld {
//线程池一般是public且为成员变量
private static ScheduledExecutorService scheduledExecutorService=Executors.newScheduledThreadPool(3);
@SuppressWarnings("removal")
public static void main(String[] args) throws InterruptedException {
//匿名创建线程体
Runnable threadTaskRunnable=new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 完成了");
}
};
for(int i=0;i<5;i++) {
scheduledExecutorService.scheduleAtFixedRate(threadTaskRunnable, 5, 3, TimeUnit.SECONDS);
}
}
}
一轮五个,等于i的数量
我的理解是,线程体执行完后,它在线程池里对应的那个线程就不active可以复用了,虽然i=5大于线程体数量,但胜在可复用。
73.ForkJoinPool
Fork/Join是一种思想,充分利用计算机多核资源,并行执行任务。具体做法是,分割大的任务为小的任务,执行小任务再把结果拼起来
ForkJoinPool是ExecutorService的实现类
使用构造器即可。
ForkJoinTask是Future的实现类,代表一个可执行的并行任务
计算和
public class helloworld {
@SuppressWarnings("removal")
public static void main(String[] args) throws InterruptedException, ExecutionException {
int[] nums=new int[100];
for (int i = 1; i <= 100; i++) {
nums[i-1]=i;
}
ForkJoinPool forkJoinPool=new ForkJoinPool();
Future<Integer> future=forkJoinPool.submit(new FJTask(nums, 0, 99));
System.out.println(future.get());
}
private static class FJTask extends RecursiveTask<Integer>{
//不拆的阈值
private static final int THRESHOLD=10;
private int[] nums;
private int start;
private int end;
public FJTask(int[] nums, int start, int end) {
this.nums = nums;
this.start = start;
this.end = end;
}
@Override
protected Integer compute() {
System.out.printf("%-25s\t%2d,,%2d\n",
Thread.currentThread().getName(),start,end);
int sum=0;
//满足条件:做事
if(end-start<=THRESHOLD) {
for(int i=start;i<=end;i++) {
sum+=nums[i];
}
}
else {
//不满足:拆
int mid=(start+end)/2;
FJTask lefTask=new FJTask(nums, start, mid);
FJTask righTask=new FJTask(nums, mid+1, end);
lefTask.fork();
righTask.fork();
sum+=lefTask.join()+righTask.join();
}
return sum;
}
}
}
74.工具类 ThreadLocal
public static void third() {
System.out.println(Thread.currentThread().getName()+" Third");
}
public static void second() {
System.out.println(Thread.currentThread().getName()+" Second");
third();
}
public static void first() {
System.out.println(Thread.currentThread().getName()+" First");
second();
}
private static class ThreadTask implements Runnable{
private Object value;
@Override
public void run() {
first();
}
public ThreadTask(Object valueObject) {
this.value = valueObject;
}
}
当层层调用方法,都需要value这个变量,或者只有最后一层需要value,并且需要并行并发,每个线程的value都独立,我们不可能每一层之间都传递一次value,因为这样太麻烦了,而且可能造成函数参数的膨胀,导致程序清晰度降低。这时候就需要用到ThreadLocal类了
public class helloworld {
private static ThreadLocal<Object> threadLocal=new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException, ExecutionException {
for(int i=0;i<5;i++) {
new Thread(new ThreadTask(i)).start();
}
}
public static void third() {
System.out.println(threadLocal.get().toString());
System.out.println(Thread.currentThread().getName()+" Third");
}
public static void second() {
System.out.println(Thread.currentThread().getName()+" Second");
third();
}
public static void first() {
System.out.println(Thread.currentThread().getName()+" First");
second();
}
private static class ThreadTask implements Runnable{
private Object value;
@Override
public void run() {
threadLocal.set(value);
first();
}
public ThreadTask(Object valueObject) {
this.value = valueObject;
}
}
}
75、线程安全的集合
可以通过工具类把线程不安全的集合包装成线程安全的集合
等等等
也可以直接用线程安全的集合
concurrent开头 支持多线程并发、通过加锁处理,保证了并发写入的安全性,且并发读取无需加锁
copyonwrite开头 并发读取时不加锁、并发写入会复制一份新数组,对新数组进行操作