0. 定时器
0.1 概述:
定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行,在jaa中,可以通过Timew和TimerTask类来实现定义调度的功能
0.2 Timer
(1)public Timer() 得到Timer对象
(2)public void schedule(TimerTask, long delay):延迟多少秒后执行定时任务
public class TimerDemo { public static void main(String[] args) { Timer t = new Timer(); TimerTask timerTask = new TimerTask() { @Override public void run() { // TODO Auto-generated method stub System.out.println("关于郑州的记忆"); } }; t.schedule(timerTask, 2000); } }
运行结果:
由图可知,打印完成后,定时任务并不会取消,程序还在运行,这就需要调用cancel方法取消任务,如下
System.out.println("关于郑州的记忆"); t.cancel(); //一般在任务执行完调用
注意:同一个定时器的任务只能被调度一次
(3)public void schedule(TimerTask task, Date date) :安排在指定的时间执行指定的任务,若此时时间已过,则立即执行任务
public class TimerDemo1 { public static void main(String[] args) { Timer timer = new Timer(); // 创建一个匿名对象 TimerTask timerTask = new TimerTask() { @Override public void run() { // 用来定义定时器任务 // TODO Auto-generated method stub System.out.println("2019-08-28 19:14:50,炸弹爆炸"); } }; try { timer.schedule(timerTask, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2019-08-28 19:14:50")); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
(4)public void schedule(TimerTask, long delay, long period):延迟时间执行指定的任务,指定间隔后循环执行
public class TimerDemo2 { public static void main(String[] args) { Timer timer = new Timer(); TimerTask timerTask = new TimerTask() { @Override public void run() { System.out.println("北京时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } }; timer.schedule(timerTask,2000,1000); } }
(5)public void cancel():取消定时任务
0.3 TimeTask (abstractclass)
public abstract void run();
0.4 自定义定时器
此处只实现以下两种方法:
(1)public void schedule(TimerTask, long delay):延迟多少秒后执行定时任务
(2)public void schedule(TimerTask, long delay, long period)
实现第一种方法
首先定义自己的任务类
public abstract class MyTimerTask{ public abstract void run(); }
其次定义一个定时器
public class MyTimer { public void schedule(MyTimerTask task, long delay) { // 通过匿名内部类的形式创建一个线程 new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(delay); } catch (InterruptedException e) { e.printStackTrace(); } task.run(); } }).start(); } }
测试类
public class MyTimerDemo { public static void main(String[] args) { MyTimer t = new MyTimer(); t.schedule(new MyTimerTask() { @Override public void run() { System.out.println("你好,再见"); } }, 2000); } }
运行结果:延迟一秒,打印出“你好, 再见”
实现第二个方法
通过重载,重写一个schedule方法,如下
public void schedule(MyTimerTask task, long delay,long period) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(delay); } catch (InterruptedException e) { e.printStackTrace(); } while(true) { task.run(); try { Thread.sleep(period); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }
测试类:
public class MyTimerDemo { public static void main(String[] args) { MyTimer t = new MyTimer(); t.schedule(new MyTimerTask() { @Override public void run() { System.out.println("北京时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } },5000, 1000); } }
若要加上cancel方法,则修改如下如下
多线程面试题:
1. 多线程有几种实现方案,分别是哪几种?
2. 同步有几种方式,分别是什么?
3.启动一个线程是run()还是start()?它们的区别是什么?
4. sleep()和wait()方法的区别
5. 线程的生命周期图
1. 网络编程
1.1 InetAddress
InetAddress类没有公共构造函数。实际上,Inet有一些静态工厂方法,可以连接到DNS服务器来解析主机名。最常用的是InetAddress.getByName().例如,可以如下查找www.baidu.com.(https://my.oschina.net/fhd/blog/371997)
InetAddress address = InetAddress.getByName("www.baidu.com");
java中用于获取IP地址的类,里面封装了ip地址,主机名等信息(通过调用静态方法得到ip对象):
public static InetAddress getByName(String host):根据ip地址或者主机名得到一个ip对象,通过ip地址大概率找不到主机名
public static InetAddress getLocalHost() :得到本机的ip对象
public String getHostAddress() :获取主机地址(ip)
public String getHostName() :获取主机名
public class InetAddressDemo { public static void main(String[] args) throws UnknownHostException { InetAddress ip = InetAddress.getLocalHost();//获取本机ip对象 System.out.println(ip); // DESKTOP-EMN1A9U/192.168.91.1 System.out.println(ip.getHostAddress());//192.168.91.1 System.out.println(ip.getHostName());//DESKTOP-EMN1A9U InetAddress ip1 = InetAddress.getByName("www.baidu.com"); //获取百度的ip对象 System.out.println(ip1);//www.baidu.com/220.181.38.150 System.out.println(ip1.getHostAddress());//220.181.38.150 System.out.println(ip1.getHostName());//www.baidu.com InetAddress ip2 = InetAddress.getByName("220.181.38.150"); System.out.println(ip2);// /220.181.38.150,可知,其主机名没找到 } }
1.2 UDP(User Datagram Protocol)编程
UDP:是一种无状态的传入协议,无需建立连接,效率快
UDP协议的客户端和服务端用的都是DatagramSocket类,传输的目的地在DatagramPacket数据包中指定
1.2.1 Socket套接字
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。Socket是应用层和传输层之间的桥梁。
当进入传输层,可以调用操作系统中的API,来构建socket。socket是操作系统提供的一个编程接口,它用来代表某个网络通信。应用程序通过socket来调用系统内核中处理网络协议的模块,而这些内核模块会负责具体的网络协议的实施。这样,我们可以让内核来接受网络协议的细节,而我们只需要提供所需要传输的内容就行了,内核会帮我们控制格式,并进一步向底层封装。因此,在实际应用中,我们并不需要知道具体怎么构成一个UDP包,而只需要提供相关信息(比如IP地址,比如端口号,比如要传输的信息),操作系统内核会在在传输层会根据我们提供的相关信息构成一个合格的UDP包(以及下层的包和帧)(此段话来源:协议森林)
1.2.2 UDP传输
DatagramSocket与DatagramPacket
建立发送端,接收端--->建立数据包--->调用Socket的发送以及接收方法---->关闭Socket---->发送端与接收端是两个独立运行的程序
案例
客户端
public class UdpClient { public static void main(String[] args) { try { //1. 创建一个Socket,用于发送数据 DatagramSocket ds = new DatagramSocket(); //2. 准备要发送的数据包(数据,数据长度,InetAddress,port) byte[] bs = "热河路有一家,5块钱的理发店".getBytes(); InetAddress ip; ip = InetAddress.getLocalHost(); DatagramPacket dp = new DatagramPacket(bs,bs.length,ip,8888); //3. 使用ds发送数据 ds.send(dp); //4.关闭资源 ds.close(); } catch (Exception e) { e.printStackTrace(); } } }
服务端
public class UdpServer { public static void main(String[] args) { try { //1. 创建DatagramSocket对象,用来接收客户端发过来的数据,并监听一个端口号 DatagramSocket ds = new DatagramSocket(8888); //2. 创建一个DatagramPacket对象,用来接收客户端发过来的数据 byte[] bs = new byte[64*1024]; // udp发送的数据最大为64kb DatagramPacket dp = new DatagramPacket(bs, bs.length); //3. 使用ds对象接收数据,调用完该方法,数据进入到dp对象中了 ds.receive(dp); //4. 拆包,解析数据 byte[] data = dp.getData(); int length = dp.getLength(); InetAddress ip = dp.getAddress(); System.out.println(ip.getHostAddress()+":"+new String(data,0,length)); //5. 关闭资源
ds.close(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
改进版:客户端从键盘录入一条数据,就发送这条数据至服务端,服务端再打印出来
客户端:
public class UdpClient { public static void main(String[] args) { try { //1. 创建一个Socket,用于发送数据 DatagramSocket ds = new DatagramSocket(); //2. 准备要发送的数据包(数据,数据长度,InetAddress,port) Scanner sc = new Scanner(System.in); String line; while((line=sc.nextLine()) != null) { byte[] bs = line.getBytes(); InetAddress ip; ip = InetAddress.getLocalHost(); DatagramPacket dp = new DatagramPacket(bs,bs.length,ip,8888); //3. 使用ds发送数据 ds.send(dp); if("over".equals(line)) { break; } } //4.关闭资源 ds.close(); } catch (Exception e) { e.printStackTrace(); } } }
服务端:
public class UdpServer { public static void main(String[] args) { try { //1. 创建DatagramSocket对象,用来接收客户端发过来的数据,并监听一个端口号 DatagramSocket ds = new DatagramSocket(8888); //2. 创建一个DatagramPacket对象,用来接收客户端发过来的数据 byte[] bs = new byte[64*1024]; // udp发送的数据最大为64kb DatagramPacket dp = new DatagramPacket(bs, bs.length); //3. 使用ds对象接收数据,调用完该方法,数据进入到dp对象中了 while(true) { ds.receive(dp); //4. 拆包,解析数据 byte[] data = dp.getData(); int length = dp.getLength(); InetAddress ip = dp.getAddress(); System.out.println(ip.getHostAddress()+":"+new String(data,0,length)); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
终极版聊天室的实现(将客户端和服务端放到同一个程序中,使用多线程实现,发送到网段所有人,使用广播地址)
客户端
public class Client implements Runnable{ @Override public void run() { try { System.out.println("客户端已开启"); //1. 创建DatagramSocket对象用来发送数据 DatagramSocket ds = new DatagramSocket(); //2. 准备要发送的数据包(数据,数据长度,InetAddress,port) Scanner sc = new Scanner(System.in); String line; while((line= sc.nextLine()) != null) { DatagramPacket dp = new DatagramPacket(line.getBytes(),line.getBytes().length,InetAddress.getByName("192.168.91.255"),8858); //3. 发送数据 ds.send(dp); if("over".equals(line)) { break; } } //4. 关流 ds.close(); System.out.println("客户端已关闭"); } catch (Exception e) { e.printStackTrace(); } } }
服务端
public class Server implements Runnable{ @Override public void run() { try { System.out.println("服务端已启动"); //1 创建DatagramSocket对象,用来接收客户端发过来的数据,并监听一个端口 DatagramSocket ds = new DatagramSocket(8858); //2.创建一个空的数据包,用于接收客户端发过来的数据 DatagramPacket dp = new DatagramPacket(new byte[1024*64],1024*64); while(true) { //3. 使用ds接收数据,接收完以后,数据就自动进入了dp ds.receive(dp); //4. 拆包 byte[] data = dp.getData(); //客户端发送过来的数据 int length = dp.getLength();//发送过来数据的长度 InetAddress ip = dp.getAddress();//客户端的ip信息 System.out.println(ip.getHostAddress()+":"+new String(data,0,length)); } } catch (Exception e) { e.printStackTrace(); } } }
启动类
注意:此处用线程池(能用线程池的形式就用线程池,自己创建线程的方式耗费资源)
public class ChatRoomSystem { public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(10); pool.execute(new Server()); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } pool.execute(new Client()); } }
1.3 Tcp(Transmission Control Protocol )编程
案例1:使用tcp协议向服务器发送一句话,并在服务器上打印出来
客户端
public class TcpClient { public static void main(String[] args) { try { //1. 创建一个socket对象,用于和服务器做连接,要指定ip额端口号 Socket s = new Socket("127.0.0.1",8555); //2. 使用s对象获取输出流向服务端写数据 OutputStream out = s.getOutputStream(); //3. 将字节流包装成缓冲字符流 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out)); bw.write("大家好才是真的好"); bw.newLine(); bw.flush(); //4. 关流 bw.close(); out.close(); s.close(); } catch (Exception e) { e.printStackTrace(); } } }
服务端
public class TcpServer { public static void main(String[] args) { try { //1. 创建ServerSocket,并监听端口号 ServerSocket ss = new ServerSocket(8555); //2. 获取客户端的socket Socket s = ss.accept(); //3.获取s对象的输入流,用来读取数据 InputStream inputStream = s.getInputStream(); //4.包装成缓冲字符流 BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); String str; while((str=br.readLine()) != null) { System.out.println(str); } //4. 关流 br.close(); inputStream.close(); s.close(); } catch (Exception e) { e.printStackTrace(); } } }
案例2:客户端发送服务端一句话后,服务端在返回一句话
客户端
public class TcpClient1 { public static void main(String[] args) { try { //1. 创建一个socket对象,用于连接服务端 Socket s = new Socket("127.0.0.1",8999); //2.获取输出流,用于写数据(字节流) OutputStream out = s.getOutputStream(); out.write("理想你今年几岁?".getBytes()); s.shutdownOutput();// 告诉服务端,我已经写完了,否则服务端会一直阻塞在等待读取客户端数据的状态,弊端是下面再也用不了输出流了 //3 客户端读取服务端的响应 InputStream in = s.getInputStream(); byte[] bs = new byte[1024]; int len; while((len = in.read(bs)) != -1) { System.out.println("来自服务端的会话:"+new String(bs,0,len)); } //4 关闭资源 in.close(); out.close(); s.close(); } catch (IOException e) { e.printStackTrace(); } } }
服务端
public class TcpServer1 { public static void main(String[] args) { try { //1. 创建服务端的Socket对象,并监听端口号 ServerSocket ss = new ServerSocket(8999); //2. 建立连接,并得到客户端的Socket对象 Socket s = ss.accept(); //3. 获取输入流,用来读取数据 InputStream in = s.getInputStream(); byte[] bs = new byte[1024]; int len ; while((len= in.read(bs)) !=-1) { System.out.println("来自客户端的会话:"+new String(bs,0,len)); } // 服务端回话 OutputStream out = s.getOutputStream(); out.write("今年我100岁了".getBytes()); //关闭资源 out.close(); in.close(); s.close(); ss.close(); } catch (IOException e) { e.printStackTrace(); } } }
注意事项: 这个程序中存在阻塞的问题,就是服务端在打印完客户端的信息后,没有把信息写回客户端,原因是程序在服务端的循环体中卡主了,因为客户端并没有写给服务端一个结束的标志,所以,服务端没有读到-1,代码不会向执行(上一个程序能读到-1 是因为客户端指定到了close 方法),解决方案是客户端告诉服务端已经写完了,调用s.shutdownOutput();该方法也存在弊端,就是一旦shutdown,下面就用不了OutputStream 了(s.getOutputStream()就会报错:Socket output is shutdown),我们可以通过自定义结束标志的方式解决.(比如输入886就表示结束)
作业
1. 客户端从键盘每录入一条信息,就发送给服务端,服务端就打印内容,并把内容转成大写,再写回给客户端,客户端打印
客户端
public class ClientExer2 { public static void main(String[] args) { try { Socket s = new Socket("127.0.0.1",7777); OutputStream out = s.getOutputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out)); BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); Scanner sc = new Scanner(System.in); String line; while((line = sc.nextLine()) != null) { bw.write(line); bw.newLine(); bw.flush(); if("886".equals(line)) { break; } System.out.println(br.readLine()); } sc.close(); bw.close(); out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
服务端
public class ServerExer1 { public static void main(String[] args) { ServerSocket ss; try { ss = new ServerSocket(7777); Socket s = ss.accept(); BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream())); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); while(true) { String line = br.readLine(); System.out.println(line); bw.write(line.toUpperCase()); bw.newLine(); bw.flush(); if("886".equals(br.readLine())) { break; } } bw.close(); br.close(); s.close(); ss.close(); } catch (IOException e) { e.printStackTrace(); } } }
2.
(1)客户端发送一个图片给服务端,服务端保存该图片(图片上传)
客户端
public class ClientExer2 { public static void main(String[] args) { try { //1. 创建一个socket对象,用于和服务端连接 Socket s = new Socket("127.0.0.1",8989); //2. 从socket中获取一个输出流,用于向服务端写数据 BufferedOutputStream bo = new BufferedOutputStream(s.getOutputStream()); //3 创建一个输入流,用来读取图片 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("e:/a.jpg")); byte[] bs = new byte[1024]; int len; while((len = bis.read(bs)) != -1) { bo.write(bs,0,len); } bo.flush(); bo.close(); bis.close(); s.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
服务端
public class ServerExer2 { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(8989); Socket s = ss.accept(); InputStream in = s.getInputStream(); BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("e:/b.jpg")); byte[] bs = new byte[1024]; int len; while((len = in.read(bs)) != -1) { bo.write(bs,0,len); bo.flush(); } bo.close(); in.close(); s.close(); ss.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
(2)若要改成可以多人上传图片(不同时),该怎么实现这个需求呢?=====>只需要将服务端加个while(true),使服务端可以连接多个客户端请求,大妈如下:
public class ServerExer2 { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(8989); while(true) { Socket s = ss.accept(); InputStream in = s.getInputStream(); BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("e:/b.jpg")); byte[] bs = new byte[1024]; int len; while((len = in.read(bs)) != -1) { bo.write(bs,0,len); bo.flush(); } bo.close(); in.close(); s.close(); ss.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
(3)若要实现多个人同时上传图片,该怎么实现这个需求呢?=====>使用多线程
客户端
public class ClientExer2 { public static void main(String[] args) { try { for(int i=0;i<10;i++) { //1. 创建一个socket对象,用于和服务端连接 Socket s = new Socket("127.0.0.1",8989); //2. 从socket中获取一个输出流,用于向服务端写数据 BufferedOutputStream bo = new BufferedOutputStream(s.getOutputStream()); //3 创建一个输入流,用来读取图片 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("e:/a.jpg")); byte[] bs = new byte[1024]; int len; while((len = bis.read(bs)) != -1) { bo.write(bs,0,len); } bo.flush(); bo.close(); bis.close(); s.close(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
服务端
public class ServerExer2 { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(8989); while(true) { Socket s = ss.accept();//建立连接 ExecutorService pool = Executors.newFixedThreadPool(10); pool.execute(new UpLoad(s)); } } catch (IOException e) { e.printStackTrace(); } } } class UpLoad implements Runnable{ Socket s; public UpLoad(Socket s) { this.s = s; } @Override public void run() { try { InputStream in = s.getInputStream(); BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("e:/a/"+System.nanoTime()+".jpg")); byte[] bs = new byte[1024]; int len; while((len = in.read(bs)) != -1) { bo.write(bs,0,len); bo.flush(); } bo.close(); in.close(); s.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }