1)自定义一个类继承自Thread,"线程类"
2)重写Thread类的run方法
3)在main用户线程中,创建当前类对象,
4)start()启动线程----->jvm调用run方法,结果---多个线程并发执行!
class MyThread extends Thread{
public void run(){
//完成耗时的操作
}
}
public class TestTread{
public static void main(String[] args){
//创建线程类对象
MyThread my1= new MyThread() ;
MyThread my2= new MyThread() ;
///设置线程名称
my1.setName("t1") ;
my2.setName("t2") ;
//启动线程
my1.start() ;
my2.start();
}
}
1)自定义一个类实现Runnable接口,
2)实现接口里面的run方法--->完成耗时操作
3)在main用户线程中创建当前这个类的实例--->"资源类对象"
4)创建线程类Thread对象,然后将3)资源类对象 作为参数传递.
5)启动线程!start();
class MyRunnable implements Runnable{}//实现接口
MyRunnable my = new MyRunnable() ; ---->真实角色
Thread t1 = new Thread(my,"t1") ; ---->Thread代理角色
t1.start();
1)自定义一个类实现Callable接口,实现里面call方法,完成异步任务的操作
2)创建线程池 Executors.newFixedThreadPool(线程数) ;---->ExecutorService
submit(Callable call)
submit(Runnable run)
public class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个线程池对象,控制要创建几个线程对象。
// public static ExecutorService newFixedThreadPool(int nThreads)
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
pool.shutdown();//结束线程池
}
}
代理设计模式
代理核心思想:
真实角色专注自己的事情(开发中,针对自己的业务)
代理角色帮助真实完成一件事情
静态代理:
代理角色和真实角色要实现同一个接口
动态代理:
jdk动态代理-―-前提必须有接口
cglib动态代理--->基于类就可以
校验多线程安全问题的标准 (使用标准来看多线程环境是否存在问题,以及解决方案)
1)是否是多线程环境 --->是
2)是否有共享数据 ---> 是存在的
3)是否有多条语句对共享数据操作
tickets票:多条语句同时操作
将3)多条语句多共享数据的操作使用同步代码块包起来---解决线程安全问题
synchronized(锁对象){
将多条语句多共享数据的包裹起来
}
锁对象:可以是任意Java类对象(多个线程必须使用同一个锁!否则锁不住)
可能出现死锁死锁:
使用synchronized解决线程安全问题,安全问题解决了,效率低,可能死锁现象
死锁:两个线程出现了一种互相等待的情况,A线程等待B线程释放锁,B线程等待A线程释放锁,造成死锁现象!
解决死锁问题--->线程之间的通信必须使用的同一个资源! (生产者和消费者模式思想)
消费者生产者模式思想------"信号灯法"
多个线程在并发的使用公共的某个变量(被共用),,保证同步(保证安全),某个线程在当前这个数据进行持有的时候,其他线程是不能访问的----通过synchronized同步代码块就可以解决线程安全问题
synchronized(监视器){
}
1)需要将多条语句对共享数据进行操作使用同步代码块包裹起来
2)同步机制里面的监视器,俗称"锁",可以是任意Java类对象,多个线程必须使用同一个监视器
或者使用同步方法(非静态)---锁对象this
静态的同步方法--->锁对象当前类名.class字节码文件对象
同步机制:
包括wait()和notify()
这两个方法都是监视器(锁)有关系,锁对象可以是任意Java类型(jdk提供 任何类,自定义的类),Object代表所有类的父类,任意Java对象可以使用Object,所以这些都定义在Object类中.
volatile关键字 保证当前实例是线程安全
1)来源不同
wait()来自于Object,被锁对象调用的
sleep()来自于Thread,线程睡眠,通过线程对象调用的
2)是否会释放锁
wait()方法的调用,会立即释放锁,才能通过notify()唤醒对方线程,达到同步安全
sleep()只是Thread类的普通方法,跟锁没有关系,睡眠中,线程处于阻塞状态,当线程睡眠时间到,线程继续执行
3)共同点都会抛出异常 throws InterruptedException:中断异常
等待唤醒机制--->使用"信号灯法"解决线程安全前提下,造成死锁,多个线程使用同一个资源对象
使用生成者消费者模式思想:
使用生成者不断的产生数据,消费者不断的使用数据(展示数据)
当生成者等待先产生数据之后,需要通知消费者使用数据
消费者线程等待 先去使用数据,当前没有数据了,需要通知生成者产生数据!
在synchronized同步代码块/同步方法,需要使用监视器(锁)调用wait()和notify(),调用wait()会立即释放锁,完成唤醒的操作!
throws和throw共同点都是抛出的异常!
throws:
1)抛出是在方法声明上抛出
public static Date string2Date(String source,String pattern) throws ParseException,Exception{
return new SimplateDateFormat(pattern).parse(source) ;
//String dateStr ="2022-11-22" ;
//使用SimplateDateFormat里面传递的模式 "yyyy年MM月dd日",跟上面的格式不一致
}
2)throws的后面跟的异常类名,可以跟多个异常类名,中间逗号隔开
3)针对throws抛出的方法处理,谁调用这个方法,谁必须处理! (处理方式---交给调用者,继续throws或者try..catch...finally)
4)throws它表示抛出异常的可能性
throw:
1)抛出是在方法体语句中
2)后面跟是异常对象名 throw new XXXEception(),只能某个异常对象名
3)throw抛出的处理---交个方法中逻辑语句处理 if语句
4)它表示抛出异常的肯定性,执行某段代码一定会有这个异常!
Lock接口
LockLock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作.
实现类:
可重入的互斥锁 java.util.concurrent.locks.ReentrantLock
获取锁:指定的某个时刻 public void lock()
释放锁 : public void unlock()
格式:
Lock l = ...;
l.lock();
try {
//访问受此锁保护的资源
} finally {
l.unlock(); //释放锁(系统相关的资源)
}
步骤:
1)//可重入的互斥锁
private Lock lock = new ReentrantLock() ;
2)//获取锁
lock.lock();
3)//释放锁
lock.unlock();
什么是线程池:
会创建一些固定的可重复使用的线程数,会在线程池中,循环利用.当某些线程使用完毕,不会被释放掉,而是归还线程池中,等待下一次再去利用!
线程池的特点:
1)降低资源销毁,因为线程使用完毕的时候,归还到线程中继续使用!
2)提高线程的维护,还是借助线程池的参数
corePoolSize:核心线程数量
maxmumPoolSize:最大核心线程数
workQueue:阻塞队列
keepAliveTime:生存时间
TimeUnit unit:时间计量单位
handler:拒绝策略:当线程数已经到最大线核心线程池并且同时workeQueue里面队列到达满的状态,线程池会启用拒绝策略! ---->里面也是子实现类---->都是Exceutors的静态内部类
ThreadFactory: 创建线程的工厂------>创建默认的线程池的名称以及后面的序列编号/同时创建线程
----->子实现类DefaultThreadFactory--->Exceutors的静态内部类
pool-1-Thread-1
pool-1-Thread-2
....
使用java.io.File来描述路径形式
描述 "D:\EE_2211\day23\code\Employee.java"
File(File parent, String child)
File(String pathname) 推荐第二个
File(String parent, String child)
创建文件/文件夹
public boolean createNewFile()throws IOException:创建文件,如果不存在,创建,返回true
public boolean mkdir():创建文件夹,如果存在了,则返回false;否则true
public boolean mkdirs():创建多级目录,当父目录不存在的时候创建
判断
public boolean isFile():是否是文件 使用居多
public boolean isDirectory():是否是文件夹 使用居多
public boolean isAbsolute():是否为绝对路径
public boolean exists():判断文件或者目录是否存在
删除
public boolean delete():删除由此抽象路径名表示的文件或目录 (删除目录,目录必须为空)
获取指定目录下的所有的文件夹以及文件的Fiie数组
例题:获取E/D盘下的所有的以.jpg结尾的文件
java.io.File:高级功能
//public File[] listFiles():获取指定抽象路径表示下的所有的File数组 推荐---->使用File的功能进行判断
//public String[] list():抽象路径名表示的目录中的文件和目录。
public class FileTest {
public static void main(String[] args) {
//1)描述磁盘上抽象路径的表示d://
File file = new File("D://") ;
//public String[] list():抽象路径名表示的目录中的文件和目录。
/* String[] strs = file.list();
for(String s:strs){
System.out.println(s) ;
}*/
//public File[] listFiles():获取指定抽象路径表示下的所有的File数组 推荐---->使用File的功能进行判断
File[] files = file.listFiles();
//遍历之前:非空判断,防止空指针异常
if(files!=null){
for(File f :files){
//f---->有文件/文件夹
//判断是文件
if(f.isFile()){
//以.jpg结尾
if(f.getName().endsWith(".jpg")){//.endsWith以...结尾
System.out.println(f.getName());//String getName():获取File指定的路径的文件或者文件的名称
}
}
}
}
}
}
按流的方向划分两大类:
输入流
输出流
按类型划分:
字节流
字节输入流:InputStream
基本的字节输入流:FileInputStream
字节缓冲输入流:BufferedInputStream
字节输出流:OutputStream
基本的字节输出流:FileOutputStream
字节缓冲输出流BufferedOutputStream
字符流
字符输入流:Reader
FileReader
字符缓冲输入流:BufferedReader
字符输出流:writer
FileWriter
字符缓冲输出流 :BufferedWriter
当使用记事本打开能读懂的就使用字符;打开读不懂,用字节!(读图片文件/视频/音频)
//public FileOutputStream(String name,boolean append) throws FileNotFoundException
//创建字节文件输出流对象,实现文件的末尾追加,而不将之前覆盖,第二个参数必须为true
FileOutputStream fos = new FileOutputStream("fos2.txt",true) ;
//for循环
for(int x = 0 ; x < 10;x++){
fos.write(("hello"+x).getBytes());//getBytes()获取字节
//写一次数据,换一次行
fos.write("\r\n".getBytes());
}
//释放资源
fos.close();
步骤:
1)创建文件输入流对象
2)读文件
public int read() throws IOException:一次读取一个字节,返回字节数
public int read(byte[] b) throws IOException:一次读取一个字节数组
3)释放资源
实现过程:
方式一:使用基本字节流一次读取一个字节:
//1)创建文件字节输入流对象
FileInputStream fis = new FileInputStream("IODemo3.java") ;
//2)读文件
//有一个字节数:从0开始
int len = 0 ;
while((len=fis.read())!=-1){ //read()阻塞式方法 //len赋值给流对象调用的读的方法,判断一块在这里用
System.out.print((char)len) ;//将数据展示控制台上
}
//3)释放资源
fis.close();
方式二:使用基本字节流一次读取一个字节数组:
//创建一个字节文件输入流对象
FileInputStream fis = new FileInputStream("IODemo3.java" ) ;
//一次读取一个字节数组,字节数组长度,1024或者1024的整数倍
byte[] bytes = new byte[1024] ;
//总字节数
int len = 0 ;
while((len=fis.read(bytes))!=-1){ //赋值,判断一块使用
//将数据展示控制台上
System.out.println(new String(bytes,0,len));
}
//释放资源
fis.close() ;
方式一:使用基本字节流一次读取一个字节:
//使用文件字节输入流操作源文件
FileInputStream fis = new FileInputStream(srcFile) ;
//使用文件字节输出流操作目的地文件
FileOutputStream fos = new FileOutputStream(destFile) ;
//一次读取一个字节
int len = 0 ;
while((len=fis.read())!=-1){
//读一个字节,给fos流对象中写一个字节,写入到目标文件中
fos.write(len) ;
}
//释放资源
fos.close();
fis.close();
方式二:使用基本字节流一次读取一个字节数组:
//使用文件字节输入流操作源文件
FileInputStream fis = new FileInputStream(srcFile) ;
//使用文件字节输出流操作目的地文件
FileOutputStream fos = new FileOutputStream(destFile) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
//实际字节数
int len = 0 ;
while ((len=fis.read(bytes))!=-1){
//一次读取一个字节数组从字节输入流中,
//一次写一个字节数组,通过fos写入到destFile中
fos.write(bytes,0,len);
}
//释放
fos.close();
fis.close();
Bufferered键盘录入
InputStream in = System.in ; //标准输入流---等待录入
//创建字符输入流:Reader抽象类
//InputStreamReader是具体的子类 构造方法InputStreamReader(InputStream in )
Reader reader = new InputStreamReader(in) ;
//创建一个字符缓冲输入流对象
BufferedReader br = new BufferedReader(reader) ;
//提示并录入
System.out.println("请您输入一个字符串:");
//利用BufferedReader一次读取一行
String line = br.readLine() ;
System.out.println("您录入的数据是:"+line) ;
字节缓冲流的方式
使用字节缓冲流的方式一次读取一个字节
使用字节缓冲流的方式一次读取一个字节数组
public class CopyFileTest {
public static void main(String[] args) throws IOException {
//D:\EE_2211\day24\a.mp4---复制到 当前项目copy.mp4
long start = System.currentTimeMillis() ;
// copyFile("D:\\EE_2211\\day24\\a.mp4","copy.mp4") ;
copyFile2("D:\\EE_2211\\day24\\a.mp4","copy.mp4") ;
long end = System.currentTimeMillis() ;
System.out.println("共耗时:"+(end-start)+"毫秒");
}
//字节缓冲流一次读取一个字节数组
public static void copyFile2(String srcFile, String destFile) throws IOException {
//创建字节缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(srcFile)) ;
//创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(destFile)) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1){
bos.write(bytes,0,len);
}
bos.flush() ;//强制刷新缓冲字节 (网络中 TCP传输协议,这种场景刷新,客户端文件---传给服务器端)
//释放资源
bos.close();
bis.close();
}
//字节缓冲流一次读取一个字节
public static void copyFile(String srcFile, String destFile) throws IOException {
//创建字节缓冲输入流对象
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(srcFile)) ;
//创建字节缓冲输出流对象
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(destFile)) ;
//一次读取一个字节
int by = 0 ;
while((by=bis.read())!=-1){
//写一个字节
bos.write(by) ;
}
//释放资源
bos.close();
bis.close();
}
}
Writer:具体的子类
public OutputStreamWriter(OutputStream out):
字符转换输出流(可以将基本字节输出流转换成字符输出流),平台默认的字符集编码(idea,utf-8)
public OutputStreamWriter(OutputStream out,String charsetName):
字符转换输出流 ,指定一个编码字符集
写的功能:
void write(char[] cbuf)写入一个字符数组。
abstract void write(char[] cbuf, int off, int len) 写入字符数组的一部分。
void write(int c) 写一个字符
void write(String str)写一个字符串
void write(String str, int off, int len) :写入字符串的一部分
Reader:抽象类,具体子类
public InputStreamReader(InputStream in):创建字符转换输入流,以平台默认字符集解码
public InputStreamReader(InputStream in,String charsetName):创建字符转换输入流对象,指定字符集解码
read的功能
public int read(char[] cbuf):读取字符数组
public int read():读一个字符
InputStreamReader/OutputStreamWriter:字符转换流弊端:代码格式复杂,不能直接操作文件!
序列化:前提条件:当前这类型必须实现java.io.serializable接口
将Java对象(成员信息等) 变成 "流数据",在服务器之间数据传输
反序列化:
即将序列化流中的数据---->ObjectInputStream---->还原成对象!
大部分的常用类都已经实现了java.io.serializable接口,后期Java后端程序和前端的中间连接层---->底层框架
Servlet(Server Applet)---->里面给产生固定的serialVersionUID
Properties类表示一组持久的属性。
Properties可以保存到流中或从流中加载。(重要的地方)
属性列表中的每个键及其对应的值都是一个字符串。
继承Hashtable---->实现Map接口---->存储数据,取出数据----都可以使用Map的方式
特有方法:
java.util.Properites属性列表有自己的遍历方式---底层基于Map实现的
添加元素:
public Object setProperty(String key, String value)
遍历属性列表
public Set stringPropertyNames()获取属性列表中的所有的键
public String getProperty(String key)使用此属性列表中指定的键搜索属性
数据在网络中不断进行传输
网络编程三要素:
ip: 使用点分十进制法,中间使用.隔开
A类 国家大部门---->前一个号段是网络号段,后面三个主机号段
B类 校园/大公司服务器机房/:前面两个网络号段,后面使用两个注解号段
C类 私人地址:前面三个为网络号段,后面是主机号段
port端口
port端口 360软件都可以查看你电脑所有客户端软件的端口号
范围:0~65535 里面0~1024属于保留端口
传输协议
UDP协议--->数据报包(数据包的报文)
1)不需要建立连接通道
2)不可靠协议,不安全的
3)发送数据大小有限制
TCP协议--->最基本的字节流的方式发送数据
1)就必须连接通道
2)可靠协议,一种安全
3)发送数据大小无限制
UDP发送端的代码实现:
//1)创建发送端的socket
DatagramSocket ds = new DatagramSocket() ;
// 2)创建数据报包DatagramPacket
/** public DatagramPacket(byte[] buf, 要发送数据---转换成字节数组
* int length, 实际字节数长度
* InetAddress address, ip地址对象
* int port) 端口号
*/
byte[] bytes = "hello,UDP我来了".getBytes() ;
int length = bytes.length ;
InetAddress inetAddress = InetAddress.getByName("192.168.1.5");//无线ip不断变的--->本地回环地址127.0.0.1
int port = 10086 ;
DatagramPacket dp = new DatagramPacket(bytes,length,inetAddress,port);
//3)使用发送端的Socket将数据存储数据包中, 发送(本质存储数据包)
ds.send(dp) ;
//4)释放资源
ds.close() ;
UDP接收端的代码实现:
//1)创建接收端的Socket对象,绑定端口
DatagramSocket ds = new DatagramSocket(10086) ;
//2)创建一个接收容器--->数据包--->自定义字节缓冲区,将发送的数据包
byte[] bytes = new byte[1024] ;//1024或者1024整数倍
int length = bytes.length ;
DatagramPacket dp = new DatagramPacket(bytes,length) ; //将发送端数据缓冲到这个接收容器中
//3)接收,以上面这个接收容器来接收
ds.receive(dp);
//4)从接收容器中解析数据包的实际内容数据
//从接收容器中获取public byte[] getData() 实际缓冲区的对象(从上bytes分段取数据)
byte[] bytes2 = dp.getData();
//获取里面实际缓冲区的长度
// public int getLength()
int length2 = dp.getLength();
//展示数据---分段取数据,每次从0开始取实际长度
String msg = new String(bytes2,0,length2) ;
//数据包里面获取哪一个ip地址发来的--->ip地址字符串形式
String ip = dp.getAddress().getHostAddress() ;
System.out.println("data from --->"+ip+",发送内容是:"+msg);
//释放资源
ds.close();
方法调用方法本身的一种现象!(并非是方法嵌套方法)
递归的使用:
1)需要定义一个方法
2)有规律
3)必须有出口条件----->"死递归"
需求:求5的阶乘
使用求阶乘思想完成
使用递归思想
解决问题的思路:
将问题1--->进行分解若干小问题
问题11
问题12
...
public class DiGuiDemo {
public static void main(String[] args) {
//使用求阶乘思想完成
//定义一个结果变量
int jc = 1 ;
for(int x = 2 ;x<=5 ; x ++){
jc*=x ;
}
System.out.println("5的阶乘是:"+jc);
System.out.println("-------------------------------------") ;
System.out.println("5的阶乘是:"+jieCheng(5));
}
//定义求5的阶乘的方法
private static int jieCheng(int n) {//5
if(n==1){
return 1 ;
}else {
//如果不是1
//5*调用方法名(5-1)
return n * jieCheng(n-1) ;
//5 *4*3*2*jiecheng(1)
}
}
}
/*
有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问第二十个月的兔子对数为多少?
*/
public class Test {
public static void main(String[] args) {
//数组的方式去实现
//创建一个数组:动态初始化
int[] arr = new int[20] ; //第二十个月
//第一个月和第二个都是1
arr[0] = 1 ;
arr[1] = 1 ;
//从第三个月开始,每一月兔子对数是前两个月兔子对数之和!
for(int x = 2 ;x方法递归的出口条件
if(n==1 || n==2){
return 1;
}else {
//从第三个月开始,每一个月兔子对数是前两个月兔子对数之和!
return getRabbit(n-1)+getRabbit(n-2) ;
}
}
}