1.多线程
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”
优点:
1、使用线程可以把占据时间长的程序中的任务放到后台去处理。
2、用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度。
3、程序的运行速度可能加快。
4、在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等。
5、多线程技术在IOS软件开发中也有举足轻重的作用。
缺点:
1、如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。
2、更多的线程需要更多的内存空间。
3、线程可能会给程序带来更多“bug”,因此要小心使用。
4、线程的中止需要考虑其对程序运行的影响。
5、通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生。
1、多线程的创建
方式一:继承Thread类
① 定义一个子类MyThread继承线程类Java.lang.Thread,重写run()方法
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出: "+i);
}
}
}
② 创建MyThread类对象
③ 调用线程的start()方法启动线程
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行输出 :" + i);
}
}
}
优缺点:
优点: 编码简单
缺点: 已经继承Thread类,无法继承其他类,不利于扩展。
思考?
(1)为什么不调用run(),而直接调用start启动线程?
答:直接调用run方法会被当成普通方法执行,此时还是相当于单线程,只有调用start方法才被当成多进程
(2)为什么不能把主线程的任务放在子线程之前?
答:这样主线程就会一直先跑完,还是相当于单进程的效果。
方式二:实现Runnable接口
① 定义一个子类MyRunnable实现Runnable接口,重写run()方法
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("子线程执行输出: "+i);
}
}
}
② 创建MyRunnable任务对象
③ 把Runnable对象交给Thread处理
④ 调用线程的start()方法启动线程
public class ThreadTest {
public static void main(String[] args) {
Runnable target = new MyRunnable();
Thread thread = new Thread(target);
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("主线程执行输出 :" + i);
}
}
}
优缺点
优点:线程类只实现接口,可以继续继承类和实现接口,可扩展性强。
缺点:线程多一层对象包装,如果线程有执行结果是不可以返回的
方式三:利用Callable、FutureTask接口实现
① 得到任务对象
1.定义类实现Callable接口,重写Call方法,封装要做的事情
2.用FutrueTask把Callable对象封装成线程任务对象
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return "子线程执行的结果: " + sum;
}
}
② 把任务交给Thread线程处理
③ 调用start方法启动线程执行任务
④ 线程执行完毕后、通过FutureTask的get方法去获取任务的执行结果
public class ThreadTest {
public static void main(String[] args) {
Callable<String> callable = new MyCallable(100);
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
try {
//如果futureTask任务没有执行完毕,这里会进行等待,不会出现数据错误的现象
final String s = futureTask.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
优缺点
优点:线程类只实现接口,可以继续继承类和实现接口,可扩展性强。可以在线程执行完毕后去获取线程执行的结果
缺点:编码复杂一点
2、Thread类的常用方法
1.String getName() 获取当前线程的名字
2.void setName(String name) 设置线程名字
3.public static Thread currentThread();返回当前 正在执行线程对象的引用
4.public static void sleep(long time) 让线程休眠指定时间,单位毫秒
5.public void run() 线程任务方法
6.public void start() 线程启动方法
构造器
1.public Thread(String name) 可以为当前线程指定名字
2.public Thread(Runnable target) 把Runnable对象交给Thread对象
3.public Thread(Runnable target,String name) 把Runnable对象交给线程对象并指定名字
3.线程安全问题
多线程环境中 , 且存在数据共享 , 一个线程访问的共享 数据被其他线程修改了, 那么就发生了线程安全问题 , 整个访问过程中 , 无一共享的数据被其他线程修改了 就是线程安全的程序中如果使用成员变量, 且对成员变量进行数据修改 , 就存在数据共享问题, 也就是线程安全问题。
原因:多个线程同时访问同一个资源且存在修改资源。
方式一:同步代码块
synchronized(锁对象){
操作共享资源代码;
}
锁对象的规范要求:
- 规范上:建议使用共享资源作为锁对象
- 对于实例方法建议使用this作为锁对象
- 对于静态方法建议使用字节码(类名.class)作为锁对象
方式二:同步方法
修饰符 synchronized 返回值类型 方法名称(形参列表){
操作共享资源代码;
}
原理:
- 对于实例方法默认使用this作为锁对象
- 对于静态方法默认使用类名.class作为锁对象
方式三:Lock锁
- Lock实现比使用synchronized方法和语法可以获得更广泛的锁定操作
- Lock是接口不能直接实例化,采用它的实现类ReentrantLock来构建Lock锁对象
//final修饰后,该所对象唯一,不可修改
private final Lock lock = new ReentrantLock();
//使用try finally防止操作异常后造成死锁
修饰符 返回值类型 方法名(){
lock.lock();//加锁
try{
操作共享资源代码;
}finally{
lock.unlock();//解锁
}
}
4.线程通信
线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺。
常见模型:生产者和消费者模型;生产者线程生产完数据后,唤醒消费者,让自己等待;消费者消费完数据后,唤醒生产者,等待自己。
线程通信的三个常见方法
- void wait()当前线程等待,直到另一个线程调用notify或者notifyAll时被唤醒
- void notify()唤醒正在等待锁对象的单个线程
- void notifyAll()唤醒正在等待锁对象的所有线程
注意
- 上述方法应该使用当前同步锁进行调用
5.线程池
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池就是一个可以复用线程的技术。
不使用线程池的问题:用户每发起一个请求就会创建一个线程,开销很大,严重影响系统性能。
线程池参数
public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//空闲线程的保留时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//拒绝策略
) {
}
使用线程池处理Runnable任务的案例:
public class ThreadPool {
public static void main(String[] args) {
//1.创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,6,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2.给任务给线程池处理
Runnable target = new MyRunnable();
pool.execute(target);
pool.execute(target);
pool.execute(target);
//进入任务队列
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
//任务队列已经满了,开始创建新线程
pool.execute(target);
pool.execute(target);
//已经超过最大线程数量,拒绝该任务
pool.execute(target);
}
}
创建Runnable任务:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出了:HelloWorld ==》 "+ i);
}
}
}
使用线程池处理Callable任务的案例:
public class ThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建线程池对象
ExecutorService pool = new ThreadPoolExecutor(3,5,6,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
//2.给任务给线程池处理
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
Future<String> f5 = pool.submit(new MyCallable(500));
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
System.out.println(f5.get());
}
}
创建Callable任务对象
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return Thread.currentThread().getName()+"线程执行 1 - "+n+"的和,结果: " + sum;
}
}
Executors工具类
- 通过调用方法返回不同类型的线程池对象
常用方法: - public static ExecutorService newCachedThreadPool();线程数量随着任务的增加而增加,执行完毕,空闲一段时间被回收。
- public static ExecutorService newFixedThreadPool();创建固定线程池,如果某一个线程挂了,会立即创建新线程。
- public static ExecutorService newSingleThreadExecutor();创建一个线程池对象,异常则会继续创建新线程。
- public static ExecutorService newScheduledThreadPool();创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。
注意
- Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的。
- 大型场景不建议使用Executors类创建线程池对象,会导致进程资源耗尽,或者内存溢出,建议使用线程池的实现类来创建线程池对象,控制参数,避免资源耗尽的风险。
6.定时器
Timer定时器
Timer定时器的特点和存在的问题
- Timer是单线程,处理多个任务按顺序执行,存在延时与设置定时器的时间有出入。
- 可能因为其中的某个任务的试Timer线程死掉,从而影响后续的任务执行。
ScheduledExecutorService定时器
- 优点:基于线程池,某个任务的执行不会影响其他定时任务的执行。
public class TimerTest {
public static void main(String[] args) {
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
//开启定时任务
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ "执行了 AAA ==>" + new Date());
}
},0,2, TimeUnit.SECONDS);
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ "执行了 BBB ==>" + new Date());
}
},0,5, TimeUnit.SECONDS);//0表示延迟0秒后执行,2表示每两秒执行一次
pool.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+ "执行了 CCC ==>" + new Date());
}
},0,4, TimeUnit.SECONDS);
}
}
2.单元测试
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证,Java程序中最小的功能单元是方法,因此对方法进行检测。
JUnit单元测试框架
优点:
- JUnit可以灵活的选择执行的哪些测试方法,可以一键执行哪些测试方法
- JUnit可以生成全部测试方法的报告
- 单元测试中的某个方法测试失败了,不会影响其他测试方法的运行
使用: 在测试的方法上加上@Test注解
3.反射
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。
反射的第一步
- 获取class对象,才可以解析类的全部成分。
获取类对象的三种方法:
public class Test {
public static void main(String[] args) {
//方式一
try {
Class c = Class.forName("com.cheng.calss.Student");
System.out.println(c);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//方式二
Class c1 = Student.class;
System.out.println(c1);
//方式三
Student s = new Student();
Class c2 = s.getClass();
System.out.println(c2);
}
}
1.利用反射获取构造器对象
常用方式:
public class Test {
public static void main(String[] args) throws Exception {
//方式一
Class c1 = Student.class;
//获得所有构造器,无论是private还是public都可以获得
c1.getDeclaredConstructors();
//方式二
//获得到指定参数列表的构造器
Constructor constructor = c1.getDeclaredConstructor();
//创建对象
//如果构造器为private,则破坏封装性来创建对象
constructor.setAccessible(true);
Student s = (Student) constructor.newInstance();
System.out.println(s);
}
}
注: 反射可以破坏封装性,私有的也可以执行了
2.利用反射获取成员变量
public class Test {
public static void main(String[] args) throws Exception {
//方式一
Class c1 = Student.class;
//获得所有成员变量,无论是private还是public都可以获得
Field[] fields = c1.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName()+"===>"+ field.getType());
}
//方式二
//获得到指定参数列表的成员变量
Field field = c1.getDeclaredField("age");
//创建对象
//如果成员变量为private,则打开权限
field.setAccessible(true);
Student s = new Student();
field.set(s,18);
System.out.println(s);
}
}
3.利用反射获取方法
public class Test {
public static void main(String[] args) throws Exception {
//方式一
Class c1 = Student.class;
//获得所有方法,无论是private还是public都可以获得
Method[] methods = c1.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
//方式二
//获得到指定参数列表的方法
Method sleep = c1.getDeclaredMethod("sleep");
Method sleep1 = c1.getDeclaredMethod("sleep", String.class);
//创建对象
Student s = new Student();
Object invoke = sleep.invoke(s);
Object invoke1 = sleep1.invoke(s,"李四");
System.out.println(invoke +"--------"+invoke1);
}
}
4.反射的作用
- 可以在运行时得到一个类的全部成分然后操作
- 可以破坏封装性(很突出)
- 也可以破坏泛型的约束性(很突出)
- 更重要的用途是适合:做Java高级框架
4.动态代理
代理: 一个对象,用来被对代理对象的行为额外做一些辅助工作。
动态代理的执行流程:
1.定义一个接口:
public interface Skill {
void dance();
void sing();
}
2.定义一个实际对象来实现该接口:
public class Star implements Skill{
@Override
public void dance() {
System.out.println("正在跳舞~~~~~~");
}
@Override
public void sing() {
System.out.println("正在唱歌~~~~~~");
}
}
3.定义一个代理类来返回代理对象:
public class StarProxy {
//设计一个方法来返回动态代理对象
public static Skill getProxy(Star obj){
//生成一个代理对象
//参数一:类加载器 加载代理类 参数二:真实业务对象的接口 参数三:代理的核心处理程序
return (Skill) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("收首付");
method.invoke(obj,args);
System.out.println("收尾款");
return null;
}
});
}
}
4.测试:
public class Test {
public static void main(String[] args) {
//创建一个实际对象
Star s = new Star();
//为该对象生成一个代理对象
Skill s1 = StarProxy.getProxy(s);
s1.dance();
s1.sing();
}
}
动态代理的步骤:
- 必须存在接口
- 被代理对象需要实现接口
- 使用Proxy类提供的方法生成对象的代理对象
动态代理的优点:
- 可以在不改变方法源码的情况下,实现对方法功能的增强,提高了对代码的复用
- 简化了编程工作、提高来发效率,同时提高了软件系统的可扩展性。
- 可以为被代理对象的所有方法做代理
- 非常的灵活,支持任意接口类型的实现类做代理,也可以直接为接口本身做代理
5.IO流
在 Java 中所有数据都是使用流读写的。流是一组有序的数据序列,将数据从一个地方带到另一个地方。根据数据流向的不同,可以分为输入(Input)流和输出(Output)流两种。
(1)什么是输入/输出流
输入就是将数据从各种输入设备(包括文件、键盘等)中读取到内存中,输出则正好相反,是将数据写入到各种输出设备(比如文件、显示器、磁盘等)。例如键盘就是一个标准的输入设备,而显示器就是一个标准的输出设备,但是文件既可以作为输入设备,又可以作为输出设备。
数据流是 Java 进行 I/O 操作的对象,它按照不同的标准可以分为不同的类别。
- 按照流的方向主要分为输入流和输出流两大类。
- 数据流按照数据单位的不同分为字节流和字符流。
- 按照功能可以划分为节点流和处理流。
输入流: Java 流相关的类都封装在 java.io 包中,而且每个数据流都是一个对象。所有输入流类都是 InputStream 抽象类(字节输入流)和 Reader 抽象类(字符输入流)的子类。其中 InputStream 类是字节输入流的抽象类,是所有字节输入流的父类。
其中InputStream的子类有:
- FileInputStream 文件输入流
- PipedInputStream 管道输入流
- ObjectInputStream 对象输入流
- FilterInputStream 过滤器输入流
- PushBackInputStream 回压输入流
- BufferedInputStream 缓冲输入流
- DataInputStream 数据输入流
- SequenceInputStream 顺序输入流
- ByteArrayInputStream 字节数组输入流
- StringBufferInputStream 缓冲字符串输入流
InputStream 类中所有方法遇到错误时都会引发 IOException 异常。
InputStream 类常用方法
- int read(): 从输入流读入一个 8 字节的数据,将它转换成一个 0~ 255 的整数,返回一个整数,如果遇到输入流的结尾返回 -1
- int read(byte[] b):从输入流读取若干字节的数据保存到参数 b 指定的字节数组中,返回的字节数表示读取的字节数,如果遇到输入流的结尾返回 -1
- int read(byte[] b,int off,int len):从输入流读取若干字节的数据保存到参数 b 指定的字节数组中,其中 off 是指在数组中开始保存数据位置的起始下标,len 是指读取字节的位数。返回的是实际读取的字节数,如果遇到输入流的结尾则返回 -1
- void close():关闭数据流,当完成对数据流的操作之后需要关闭数据流
Java 中的字符是 Unicode 编码,即双字节的,而 InputerStream 是用来处理单字节的,在处理字符文本时不是很方便。这时可以使用 Java 的文本输入流 Reader 类,该类是字符输入流的抽象类,即所有字符输入流的实现都是它的子类,该类的方法与 InputerSteam 类的方法类似。
输出流:在 Java 中所有输出流类都是 OutputStream 抽象类(字节输出流)和 Writer 抽象类(字符输出流)的子类。其中 OutputStream 类是字节输出流的抽象类,是所有字节输出流的父类。
其中OutputStream的子类有:
- FileOutputStream 文件输出流
- Pipe的OutputStream 管道输出流
- ObjectOutputStream 对象输出流
- FilterOutputStream 过滤器输出流
- PrintStream 打印输出流
- BufferedOutputStream 缓冲输出流
- DataOutputStream 数据输出流
- ByteArrayOutputStream 字节数组输出流
OutputStream 类是所有字节输出流的超类,用于以二进制的形式将数据写入目标设备,该类是抽象类,不能被实例化。OutputStream 类提供了一系列跟数据输出有关的方法。
- int write(b):将指定字节的数据写入到输出流
- int write (byte[] b):将指定字节数组的内容写入输出流
- int write (byte[] b,int off,int len):将指定字节数组从 off 位置开始的 len 字节的内容写入输出流
- close():关闭数据流,当完成对数据流的操作之后需要关闭数据流
- flush():刷新输出流,强行将缓冲区的内容写入输出流
字节输入流例子:
使用字节数组:
public class IOStreamTest1 {
public static void main(String[] args) throws Exception {
InputStream is = new FileInputStream("C:\\Users\\hp\\IdeaProjects\\java\\FristJavaProject\\src\\data.txt");
byte[] buffer = new byte[3];//每次读入三个字节
int len;//m每次读取到的字符长度
while ((len = is.read(buffer)) != -1){
System.out.print(new String(buffer,0,len));
}
}
}
一次性读取完毕: 根据文件大小读取文件的所有字节
public class IOStreamTest1 {
public static void main(String[] args) throws Exception {
File f = new File("C:\\Users\\hp\\IdeaProjects\\java\\FristJavaProject\\src\\data.txt");
InputStream is = new FileInputStream(f);
byte[] buffer = new byte[(int) f.length()];//一次性读取文件的所有字节
int len;//每次读取到的字符长度
while ((len = is.read(buffer)) != -1){
System.out.print(new String(buffer,0,len));
}
}
}
字符输入流例子:
public class IOStreamTest1 {
public static void main(String[] args) throws Exception {
File f = new File("C:\\Users\\hp\\IdeaProjects\\java\\FristJavaProject\\src\\data.txt");
Reader reader = new FileReader(f);
char [] buffer = new char[3];
int len;
while ((len = reader.read(buffer)) != -1){
System.out.print(new String(buffer,0,len));
}
reader.close();
}
}
字节输出流例子:
使用字节数组:
public class IOStreamTest1 {
public static void main(String[] args) throws Exception {
File f = new File("C:\\Users\\hp\\IdeaProjects\\java\\FristJavaProject\\src\\data01.txt");
OutputStream os = new FileOutputStream(f);
byte[] buffer = {'a','b',99};//要写入的字节数组数据
os.write(buffer);
os.close();
}
}
字符输出流例子:
public class IOStreamTest1 {
public static void main(String[] args) throws Exception {
File f = new File("C:\\Users\\hp\\IdeaProjects\\java\\FristJavaProject\\src\\data01.txt");
Writer writer = new FileWriter(f);
String buffer = "ncsjnjcsnckdndk";
writer.write(buffer);
writer.close();
}
}
(2)对象序列化
- 作用:以内存为基准,把内存中的对象存储到磁盘文件中去,称为对象序列化
- 使用到的流是对象字节输出流:ObjectOutputStream
**注:**要给该对象实现序列化接口Serializable,使用transient关键字不参与对象的序列化,可以声明序列化版本号,及时更新序列化信息。
(3)对象反序列化
- 作用:以内存为基准,把磁盘文件中的对象存储到内存中去,称为对象反序列化
- 使用到的流是对象字节输出流:ObjectOutputStream
6.网络通信
(1)TCP通信
- TCP是一种面向连接、安全、可靠的传输数据协议
- 传输前采用TCP“三次握手”的方式,点对点方式,是可靠的。
- 在连接中可进行大数据量的传输。
客户端Socket类:
//构造器
public Socket(String host,int post)//创建发送端的Socket对象与服务端连接,参数为服务端的IP和端口
//类成员方法
OutputStream getOutputstream()//获得字节输出流对象
InputStream getInputStream()//获得字节输入流对象
一发一收例子:
public class ClientSocket {
public static void main(String[] args) {
try {
System.out.println("客户端启动成功");
// TODO 1.创建Socket通信管道请求服务端的连接
Socket socket = new Socket("127.0.0.1",7777);
// TODO 2.从Socket管道中获取一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
// TODO 3.把低级的字节流包装成高级的打印流
PrintStream ps = new PrintStream(os);
// TODO 4.发送数据
ps.println("你好,我是TCP客户端,我已经与你对接,并发出邀请,约吗?");
ps.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端ServerSocket类:
public ServerSocket(int post)//注册服务端口
public class ServerSocketDemo {
public static void main(String[] args) {
System.out.println("服务端启动成功");
try {
// TODO 1.注册服务端口
ServerSocket serverSocket = new ServerSocket(7777);
// TODO 2.必须调用accept()方法,等待接收客户端的连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// TODO 3.从socket通信管道中获取一个字节输入流
InputStream is = socket.getInputStream();
// TODO 4.把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// TODO 5.按照行读取消息
String msg;
if((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + " : " + msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
多个客户端同时向服务器发送消息的例子:
客户端代码:
public class ClientSocket {
public static void main(String[] args) {
try {
System.out.println("客户端启动成功");
// TODO 1.创建Socket通信管道请求服务端的连接
Socket socket = new Socket("127.0.0.1",7777);
// TODO 2.从Socket管道中获取一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
// TODO 3.把低级的字节流包装成高级的打印流
PrintStream ps = new PrintStream(os);
// TODO 4.发送数据
Scanner sc = new Scanner(System.in);
while (true){
String str = sc.nextLine();
ps.println(str);
ps.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端代码:
public class ServerSocketDemo {
public static void main(String[] args) {
System.out.println("服务端启动成功");
try {
// TODO 1.注册服务端口
ServerSocket serverSocket = new ServerSocket(7777);
while(true){
// TODO 2.必须调用accept()方法,等待接收客户端的连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// TODO 3.开启创建独立线程处理socket
new ServerReaderThread(socket).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
定义线程处理不同客户端:
public class ServerReaderThread extends Thread{
private Socket socket;
public ServerReaderThread(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// TODO 3.从socket通信管道中获取一个字节输入流
InputStream is = socket.getInputStream();
// TODO 4.把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// TODO 5.按照行读取消息
String msg;
while((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + " : " + msg);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
使用线程池优化多个客户端同时访问一个服务器:
客户端代码:
public class ClientSocket {
public static void main(String[] args) {
try {
System.out.println("客户端启动成功");
// TODO 1.创建Socket通信管道请求服务端的连接
Socket socket = new Socket("127.0.0.1",7777);
// TODO 2.从Socket管道中获取一个字节输出流,负责发送数据
OutputStream os = socket.getOutputStream();
// TODO 3.把低级的字节流包装成高级的打印流
PrintStream ps = new PrintStream(os);
// TODO 4.发送数据
Scanner sc = new Scanner(System.in);
while (true){
String str = sc.nextLine();
ps.println(str);
ps.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端代码:
public class ServerSocketDemo {
// TODO 使用静态变量记住一个线程池对象
private static ExecutorService pool = new ThreadPoolExecutor(3,5,6, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) {
try {
System.out.println("服务端启动成功");
// TODO 1.注册服务端口
ServerSocket serverSocket = new ServerSocket(7777);
while(true){
// TODO 2.必须调用accept()方法,等待接收客户端的连接请求,建立Socket通信管道
Socket socket = serverSocket.accept();
// TODO 3.开启创建独立线程任务
ServerReaderRunnable serverReaderRunnable = new ServerReaderRunnable(socket);
// TODO 4.交给线程池处理
pool.execute(serverReaderRunnable);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Runnable任务:
public class ServerReaderRunnable implements Runnable{
private Socket socket;
public ServerReaderRunnable(Socket socket){
this.socket = socket;
}
@Override
public void run() {
try {
// TODO 3.从socket通信管道中获取一个字节输入流
InputStream is = socket.getInputStream();
// TODO 4.把字节输入流包装成缓冲字符输入流进行消息的接收
BufferedReader br = new BufferedReader(new InputStreamReader(is));
// TODO 5.按照行读取消息
String msg;
while((msg = br.readLine()) != null){
System.out.println(socket.getRemoteSocketAddress() + " : " + msg);
}
}catch (Exception e){
e.printStackTrace();
}
}
}