多线程
正常来说代码执行顺序从上到下
1,通过断点调试运行
右键添加Toggle Breakpoint,需要再Debug模式运行(点击小虫子或者右键Debug As)
Resume(F8)继续运行直到遇到其他断点
Step Into(F5)会进入方法内部
Step over(F5)
2,进程和线程
一个程序就是一个进程
3,我们的计算机是支持多进程的
4,同理多线程有什么作用
以游戏为例子(需要一个线程来控制主角移动,一个线程控制敌人的AI攻击行为)
当我们需要多个任务同时运行的时候,就可以使用多线程
5,主线程
我们以前平时写的java程序都是单线程的,都是从main方法执行的,main方法处于一个
默认的线程中,这个线程称为主线程。主线程是系统默认系统创建出来的。(JVM创建启动)
6,创建线程(方法一)不常用
a)创建MyThread类,继承Thread
b)重写run方法
public class MyThread extends Thread {
public MyThread() {}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<100;i++) {
if(interrupted()) {//当前线程被设置为中断状态
//做一些事情
System.out.println("释放资源");
break;
}
System.out.println(getName()+":" +i);
}
}
}
c)创建对象,并调用start方法运行<br /> (如果直接调用run方法,相当于调用普通方法,并不会启动线程去调用run)<br /> <br />其他<br /> a)获取线程名字 thread.getName(),设置名字 thread.setName("线程1")或者构造Thread对象时直接设置<br /> b)如何获取主线程(main所在线程) Thread mainThread = Thread.currentThread()<br /> <br />**线程调度规则**<br />(线程调度会整体上是遵守下面的规则,但是从单个上来看是随机的)
- 分时调度(平均分配)
抢占式调度(按照优先级)(Java使用的调度规则)
优先级高的,有更高的几率被CPU所执行
c)设置优先级 setPriority 得到优先级 getPriority
d)线程休眠,让当前线程休眠 Thread.sleep();
e)join()线程加入 把某个线程加入(合并)到当前线程中,
f)设置守护线程 setDaemon(类似C#的前台线程后台线程)如果程序中只剩下守护线程在运行,那么程序会停止
g)线程中断 强制性杀死
stop()被弃用 直接强制杀死 被动
推荐 interrupt()
thread.interrupt()设置线程中断状态,并不是真正中断了。需要主动去判断状态interrupted(),在线程类中根据状态做一些事情
让线程自己抛出异常,让线程自己可以处理被终止的时候做一些事情
```java package com.sikiedu.chapter1;
public class Demo01_CreateThread { public static void main(String[] args) { System.out.println(Thread.NORM_PRIORITY);//默认优先级5 System.out.println(Thread.MIN_PRIORITY);//最小优先级1 System.out.println(Thread.MAX_PRIORITY);//最大优先级10
Thread mainThread = Thread.currentThread();
mainThread.setPriority(10);
MyThread thread = new MyThread();
System.out.println(mainThread.getPriority());
System.out.println(thread.getPriority());
// thread.run();//error 相当于普通方法调用 thread.start();//启动线程
int j = 0;
for(int i=0;i<10000000;i++) {
j+=i;
}
System.out.println("MainThread"+System.currentTimeMillis());
}
}
```java
package com.sikiedu.chapter1;
public class Demo02_Thread {
public static void main(String[] args) throws Exception {
// try {
// Thread.sleep(4000);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
MyThread t1 = new MyThread("线程1");
// System.out.println("ThreadT1Name:"+t1.getName());
t1.start();
Thread.sleep(1);
// t1.stop();
t1.interrupt();//
// t1.join();//当t1加入到当前线程中,就是加入到主线程中,就是放到主线程中去继续执行
//t1.start();//不可以重复调用start
MyThread t2 = new MyThread("线程2");
// System.out.println("ThreadT2Name:"+t2.getName());
// t2.setDaemon(true);//需要再启动线程前设置守护线程
t2.start();
System.out.println("Main end");
}
}
7,线程生命周期:https://blog.csdn.net/lonelyroamer/article/details/7949969
8,创建线程(方法二)常用
a)实现Runnable接口
b)实现run方法
c)创建当前MyRunnable类的对象和Thread的对象,并使用thread对象启动线程
package com.sikiedu.chapter1;
public class MyRunnable implements Runnable {
private String data = "";
@Override
public void run() {
for(int i=0;i<100;i++) {
Thread t = Thread.currentThread();
System.out.println(t.getName()+":"+i);
}
}
}
package com.sikiedu.chapter1;
// extends Thread
// implements Runnable
public class Demo03_CreateThreadMethod2 {
public static void main(String[] args) {
MyRunnable t = new MyRunnable();
Thread t1 = new Thread(t,"线程1");
t1.start();
Thread t2 = new Thread(t,"线程2");
t2.start();
}
}
其他<br /> a)获取当前线程的名字<br /> Thread.currentThread().getName()<br /> 设置名字通过Thread对象<br /> b)构造方法<br /> Thread t = new Thread(Runnable target);<br /> Thread t = new Thread(Runnable target,String name);
9,方式二的好处
可以避免单继承带来的局限性(实现了接口后,可以继承一个别的类)
可以很好的处理两个线程共享一个资源的情况(数据共享)。
10,创建线程(方法三)
依据方法一和二,使用匿名内部类
a)new Runnable(){}
b)new Thread(){}
package com.sikiedu.chapter1;
public class Demo04_CreateThread {
public static void main(String[] args) {
// // 匿名内部类
// Runnable r = new Runnable() {
//
// @Override
// public void run() {
// for(int i = 0;i<100;i++) {
// System.out.println(Thread.currentThread().getName()+":"+i);
// }
// }
//
// };
//
//// Thread t = new Thread(r,"匿名内部类线程");
//// t.start();
// new Thread(r,"匿名内部类线程").start();
new Thread() {
@Override
public void run() {
for(int i = 0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}.start();
}
}
11,我们用代码来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程表示。
方法一:
package com.sikiedu.chapter1;
public class TicketThread extends Thread{
private static int count = 100;//所有线程应该一共有100张票,而不是每个售票点对象都有100张票,所以票数应设置成static
public TicketThread(String name) {
super(name);
}
@Override
public void run() {
while(true) {
if(count>0) {
System.out.println(getName()+ "卖出第"+count+"张票");
count--;
}else {
break;
}
}
}
}
TicketThread t1 = new TicketThread("售票点1");
TicketThread t2 = new TicketThread("售票点2");
TicketThread t3 = new TicketThread("售票点3");
TicketThread t4 = new TicketThread("售票点4");
t1.start();
t2.start();
t3.start();
t4.start();
问题:count即使设置成static后依然会出现重复卖同一张票的情况。
方法二:
package com.sikiedu.chapter1;
public class TicketRunnable implements Runnable {
private int count= 100;
@Override
public void run() {
while(count>0) {
if(count>0) {//线程1 线程2
System.out.println(Thread.currentThread().getName()+ "卖出第"+count+"张票");//线程1 100
count--;
}else {
break;
}
}
}
}
TicketRunnable t = new TicketRunnable();
Thread t1 = new Thread(t,"售票点1");
Thread t2 = new Thread(t,"售票点2");
Thread t3 = new Thread(t,"售票点3");
Thread t4 = new Thread(t,"售票点4");
t1.start();
t2.start();
t3.start();
t4.start();
问题:count即使设置成static后依然会出现重复卖同一张票的情况。
12,线程安全问题
售票问题原因:多个线程同时要修改一个变量的时候,引起冲突
线程安全问题解决:
synchronized(对象){//锁住某个对象,如果这个对象已经被锁定,那么等待。
}
package com.sikiedu.chapter1;
public class TicketThread extends Thread{
private static int count = 100;//所有线程应该一共有100张票,而不是每个售票点对象都有100张票,所以票数应设置成static
private static Object lock = new Object();
public TicketThread(String name) {
super(name);
}
@Override
public void run() {
while(true) {
synchronized (lock) {//这里lock不可以换成this,因为每个this各代表一个对象
if(count>0) {
System.out.println(getName()+ "卖出第"+count+"张票");
count--;
}else {
break;
}
}
}
}
}
package com.sikiedu.chapter1;
public class TicketRunnable implements Runnable {
private int count= 100;
private Object lock = new Object();
@Override
public void run() {
while(true) {
//这里lock可以换成this
synchronized (lock) {//第一个线程来的 时候会锁上,并拿走钥匙,第二个线程来的 时候,发现被锁上了,等待
if(count>0) {//线程1 线程2
System.out.println(Thread.currentThread().getName()+ "卖出第"+count+"张票");//线程1 100
count--;
}else {
break;
}
}//执行完代码,归还钥匙
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
a)出现线程安全问题的地方,要锁同一个对象(可以是当前对象this,也可以单独创建一个Object对象lock,只限方法二)
b)锁住某个对象,如果这个对象已经被锁定,那么停止当前线程的执行,一直等待,一直等到对象被解锁。
(保证同一个时间,只有一个线程在使用这个对象,)
c)创建同步方法
去掉synchronized (this),把方法声明为synchronized
同步方法锁的是哪个对象呢?锁定的是当前对象this
package com.sikiedu.chapter1;
public class TicketRunnable implements Runnable {
private int count= 100;
private Object lock = new Object();
@Override
public void run() {
while(count>0) {
sellTicket();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public synchronized void sellTicket() {
// synchronized (this) {//第一个线程来的 时候会锁上,并拿走钥匙,第二个线程来的 时候,发现被锁上了,等待
if(count>0) {//线程1 线程2
System.out.println(Thread.currentThread().getName()+ "卖出第"+count+"张票");//线程1 100
count--;
}
// }//执行完代码,归还钥匙
}
}
13,线程安全的类
安全: StringBuffer(查看源码会发现方法声明为synchronized。所以线程安全,不过效率略低。其他类同理) Vector
不安全:StringBuilder ArrayList
14,同步锁的第二种使用方式
a)创建锁对象 ReentrantLock lock = new ReentrantLock();
b)加锁和解锁
使用try finally
lock.lock();加锁
lock.unlock();解锁
package com.sikiedu.chapter1;
import java.util.concurrent.locks.ReentrantLock;
public class TicketRunnable implements Runnable {
private int count= 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(count>0) {
lock.lock();//加锁
try {
if(count>0) {//线程1 线程2
System.out.println(Thread.currentThread().getName()+ "卖出第"+count+"张票");//线程1 100
count--;
}
} finally {
//把unlock()解锁放在finally里,保证即使需要加锁的代码中出现错误也能执行到unlock()
lock.unlock();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
15,线程安全问题的第二个例子
买票问题升级(两种卖票方式,一种通过电影院窗口,一种通过手机App)
问:之前是在一个Runnable对象中进行线程安全的处理。现在是两个Runnable对象,那该怎么保证是同一个锁呢?
答:定义一个成员变量,通过构造方法把锁对象传入即可
package com.sikiedu.chapter1;
public class TicketMng {
public static int count = 100;
}
package com.sikiedu.chapter1;
public class WindowThread implements Runnable {
public WindowThread(Object lock) {
this.lock=lock;
}
private Object lock;
@Override
public void run() {
while( TicketMng.count>0 ) {
synchronized (lock) {
if(TicketMng.count>0) {
System.out.println(Thread.currentThread().getName()+"售出第"+TicketMng.count+"张票");
TicketMng.count--;
}
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package com.sikiedu.chapter1;
public class AppThread implements Runnable {
public AppThread(Object lock) {
this.lock=lock;
}
private Object lock;
@Override
public void run() {
while( TicketMng.count>0 ) {
synchronized (lock) {
if(TicketMng.count>0) {
System.out.println(Thread.currentThread().getName()+"售出第"+TicketMng.count+"张票");
TicketMng.count--;
}
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package com.sikiedu.chapter1;
public class Demo06_Practice {
public static void main(String[] args) {
Object lock = new Object();
WindowThread windowThread = new WindowThread(lock);
AppThread appThread = new AppThread(lock);
new Thread(windowThread,"窗口").start();
new Thread(appThread,"手机App").start();
}
}
16,死锁问题
死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
package com.sikiedu.chapter1;
public class Demo07_DeadLock {
public static Object lock1 = new Object();
public static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(new Thread1()).start();
new Thread(new Thread2()).start();
}
}
class Thread1 implements Runnable{
@Override
public void run() {
synchronized (Demo07_DeadLock.lock1) {
System.out.println("取得第一把锁之后要做的事情");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (Demo07_DeadLock.lock2) {
System.out.println("Thread1同时取得两把锁之后要做的事情");
}
}
}
}
class Thread2 implements Runnable{
@Override
public void run() {
synchronized (Demo07_DeadLock.lock2) {
System.out.println("取得第二把锁之后要做的事情");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (Demo07_DeadLock.lock1) {
System.out.println("Thread2同时取得两把锁之后要做的事情");
}
}
}
}
解决方法:锁的顺序设置相同
package com.sikiedu.chapter1;
public class Demo07_DeadLock {
public static Object lock1 = new Object();
public static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(new Thread1()).start();
new Thread(new Thread2()).start();
}
}
class Thread1 implements Runnable{
@Override
public void run() {
synchronized (Demo07_DeadLock.lock1) {
System.out.println("取得第一把锁之后要做的事情");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (Demo07_DeadLock.lock2) {
System.out.println("Thread1同时取得两把锁之后要做的事情");
}
}
}
}
class Thread2 implements Runnable{
@Override
public void run() {
synchronized (Demo07_DeadLock.lock1) {
synchronized (Demo07_DeadLock.lock2) {
System.out.println("取得第二把锁之后要做的事情");
System.out.println("Thread2同时取得两把锁之后要做的事情");
}
}
}
}
17,线程组ThreadGroup 默认处于同一个组里面
使用线程组可以统一设置这个组内线程的一些东西。比如设置优先级,设置是否是守护线程。
package com.sikiedu.chapter1;
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0;i<100;i++) {
Thread t = Thread.currentThread();
System.out.println(t.getName()+":"+i);
}
}
}
package com.sikiedu.chapter1;
public class Demo08_ThreadGroup {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
ThreadGroup tg = new ThreadGroup("我们的线程组");
Thread t1 = new Thread(tg,r);
Thread t2 = new Thread(tg,r);
System.out.println(tg.getName());//我们的线程组
//批量设置线程属性
t1.setDaemon(true);
t1.setPriority(9);
t1.interrupt();
t1.start();
t2.start();
}
static void DefalutThreadGroup()
{
// 不设置线程组的话默认处于同一个组里面
MyRunnable r = new MyRunnable();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
ThreadGroup tg = t1.getThreadGroup();
System.out.println(tg.getName());//main
System.out.println(t2.getThreadGroup().getName());//main
t1.start();
t2.start();
}
}
18,定时器(本质也是线程)
作用:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。
使用类:Timer和TimerTask
方法
timer.schedule(TimerTask task, long delay)
timer.schedule(TimerTask task, long delay, long period)
timer.schedule(TimerTask task, Date time)
timer.cancel();
package com.sikiedu.chapter1;
import java.util.Timer;
import java.util.TimerTask;
public class Demo09_Timer {
public static void main(String[] args) {
Timer t = new Timer();
// t.schedule(new MyTimerTask(), 2000);//等待2s,执行任务
// t.schedule(new MyTimerTask(), 2000, 3000);//等待2s,然后每隔3s执行一次
// t.cancel();//终止任务
}
}
class MyTimerTask extends TimerTask{
@Override
public void run() {
System.out.println("定时器任务");
}
}
网络编程(两个计算机通信)
简述:跟网络相关的编程<br /> 例子: 下载资源<br /> 资源上传<br /> 从服务器(某个网络终端)取得数据<br /> 向其他计算机发送消息<br /> 接收其他计算机发送的消息<br /> 计算机之间的交流,计算机之间数据的传递
1,什么是ip地址
简述:ip地址是网络中计算机的唯一标识。
举例:xx.xx.xx.xx xx是0-255之间的一个数字
问题:
什么是局域网ip(192.168.xx.xx),什么是外网ip?
如何查看本机ip?(ipconfig)
ip是不能重复的
局域网ip在局域网内,不可以重复。外网ip在外网环境下不可以重复
每个域名,访问的其实都是某个服务器,服务器其实是一台计算机,是计算机就有一个唯一的ip地址。
访问域名的时候,其实就是通过服务器的ip地址,找到服务器并获取数据。
域名跟ip地址是一一对应的。
当前计算机与ping的地址之间的通信是否通畅。
ping 域名
ping ip地址
特殊的ip地址
本机 127.0.0.1 localhost
什么是ipv4和ipv6
由于全球计算机数量越来越多,ipv6的出现是为了解决ipv4可能不够用的问题,有6位,每一部分由16进制组成。了解就行。
2,既然要在Java里面做网络编程,那在Java里面肯定要处理IP地址。
在Java程序里面如何表示一个IP地址呢? 通过字符串?还是某个系统的类?
a)InetAddress表示一个IP地址对象
//可以根据主机名(计算机名)构建InetAddress对象
//也可以根据ip地址(字符串)构建InetAddress对象
InetAddress.getByName(String name);
package com.sikiedu.chapter2;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Demo01_InetAddress {
public static void main(String[] args) throws Exception {
InetAddress ip1 = InetAddress.getByName("DESKTOP-J8UV13P");//此电脑->右键->设备名称
InetAddress ip2 = InetAddress.getByName("192.168.31.225");
System.out.println(ip1);// DESKTOP-J8UV13P/192.168.31.225
System.out.println(ip2);// /192.168.31.225
}
}
3,端口号
有了地址可以找到住的地方,这个地方可能有很多人同时居住,我们还需要一个收件人。端口就相当于收件人。
我们的电脑上有很多运行的程序,有的程序不需要跟外界交互(单机软件)
有的程序需要跟外界交互,这个时候我们需要通过端口号区分我们要跟那个软件交互。
总结:通过ip定位计算机,通过port定位哪个软件
注意:端口号是不能重复的。端口号是一个数字。
百度百科:https://baike.baidu.com/item/%E7%AB%AF%E5%8F%A3%E5%8F%B7/10883658
4,通信协议(两个计算机通信,也就是两个计算机交流,也就是两个计算机交流数据)
通信规则
UDP
速度快
不需要建立连接
不可靠
TCP
速度慢
需要通过三次握手建立连接
可靠
举例:
UDP:发短信
TCP:打电话
网络编程的三要素总结:
IP地址,端口号,通信协议。
5,Socket套接字
在程序中我们通过Socket进行通信,在使用Socket通信的时候
需要指定上面所说的几个条件(ip,port,协议)
数据发送分成两步
第一步是监听(等待数据发送过来),用来接收数据。需要指定监听的端口号
第二步是发送,需要指定发送到哪个计算机(IP地址),需要指定发送到这个计算机的哪个端口。
Socket中分为发送端和接收端
发送端一般为客户端,接收端为服务器端。
一般情况下我们会有多个客户端,一个服务器端。
6,使用UDP发送和接收数据,并实现数据的循环接收和循环发送
package com.sikiedu.chapter2;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class Demo02_UDP_Send {
public static void main(String[] args) throws Exception {
DatagramSocket ds = new DatagramSocket();
Scanner s = new Scanner(System.in);
while(true) {
String str = s.nextLine();
if(str.equals("end"))break;
byte[] buf = str.getBytes();
int length = buf.length;
InetAddress address= InetAddress.getByName("192.168.31.225");//127.0.0.1 localhost
int port = 7879;
DatagramPacket dp = new DatagramPacket(buf, length, address, port);
ds.send(dp);
}
ds.close();//释放资源
}
}
package com.sikiedu.chapter2;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Demo03_UDP_Receive {
public static void main(String[] args) throws Exception {
DatagramSocket ds = new DatagramSocket(7879);//要指定 监听哪个端口号来接收数据
while(true) {
byte[] buf = new byte[1024];
int length = buf.length;
DatagramPacket dp = new DatagramPacket(buf, length);
ds.receive(dp);//程序会在这里等待,等待数据的到来
String str = new String( dp.getData(),0,dp.getLength() );
InetAddress address = dp.getAddress();//发送端的地址
// System.out.println(dp.getPort());//发送端的端口号,并不是接收端的7879端口,由系统自动分配,每次都不同,一般没啥作用
System.out.println(address+"----"+str);
}
// ds.close();接收数据端需要不断监听,所以不需要关闭
}
}
7,实现双向聊天功能
package com.sikiedu.chapter2;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class ReceiveThread extends Thread {
private int port;
public ReceiveThread(int port) {
this.port=port;
}
@Override
public void run() {
DatagramSocket ds=null;
try {
ds = new DatagramSocket(port);
while(true) {
byte[] buf = new byte[1024];
int length = buf.length;
DatagramPacket dp = new DatagramPacket(buf, length);
ds.receive(dp);//程序会在这里等待,等待数据的到来
String str = new String( dp.getData(),0,dp.getLength() );
InetAddress address = dp.getAddress();
// System.out.println(dp.getPort());
System.out.println(address+":"+str);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(ds!=null)
ds.close();
}
}
}
package com.sikiedu.chapter2;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class SendThread extends Thread {
private int port;
public SendThread(int port) {
this.port = port;
}
@Override
public void run() {
DatagramSocket ds=null;
try {
ds = new DatagramSocket();
Scanner s = new Scanner(System.in);
while(true) {
String str = s.nextLine();
if(str.equals("end"))break;
byte[] buf = str.getBytes();
int length = buf.length;
InetAddress address= InetAddress.getByName("192.168.31.225");//127.0.0.1 localhost
DatagramPacket dp = new DatagramPacket(buf, length, address, port);
ds.send(dp);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(ds!=null)
ds.close();
}
}
}
package com.sikiedu.chapter2;
public class Demo04_Chat_User01 {
public static void main(String[] args) {
new SendThread(7878).start();
new ReceiveThread(7879).start();
}
}
package com.sikiedu.chapter2;
public class Demo05_Chat_User02 {
public static void main(String[] args) {
new ReceiveThread(7878).start();
new SendThread(7879).start();
}
}
8,TCP发送端(客户端)
客户端和服务器端双方发送数据正常。
但是会出现问题:
1、在客户端输入end关闭时客户端会抛出异常Socket closed。明明在接收时通过if(s.isClosed())break;判断了,为什么还会出现这种情况呢?
答:因为线程是异步的,客户端第一次运行接收线程时isClosed()为false,然后执行到input.read(buf);一直等待服务器发送数据,收到数据后再次进行等待,会一直停留在input.read(buf);这行语句。而在这时我们客户端Socket关闭连接了,而input是依赖Socket的,输入流也会关闭。
解决方案:抛出Exception异常,不影响执行。
2、bug:服务器端输入end无效。
TODO
package com.sikiedu.chapter2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
//使用tcp 发送端:client客户端 接收端server服务器端
public class Demo06_TCP_Send {
public static void main(String[] args) throws Exception {
//Socket
//TCP需要建立连接
Socket s = new Socket("192.168.31.225", 7878);//建立跟指定ip port建立连接
InputStream input = s.getInputStream();
OutputStream output = s.getOutputStream();//得到输出流。使用流适合发送大容量的数据,数据大小不受限制,可以持续写数据,并保证稳定性
//客户端接收数据。
//如果不使用多线程的话。执行到while(true)时就永远不会执行下面的发送数据代码了,所以需要使用多线程并行
new Thread() {
public void run() {
int length = -1;
byte[] buf = new byte[1024];
try {
while(true) {
if(s.isClosed())break;
length = input.read(buf);
if(length==-1)break;
System.out.println(new String(buf,0,length));
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
//客户端发送数据
Scanner scanner = new Scanner(System.in);
while(true) {
String str = scanner.nextLine();
if(str.equals("end"))break;
//输出流写入数据
output.write(str.getBytes());
}
s.close();
}
}
package com.sikiedu.chapter2;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Demo07_TCP_Receive {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(7878);
Socket client = ss.accept();//接收,建立跟发送端的连接。会等待客户端的连接
System.out.println("接收到一个客户端的链接");
//new Socket("192.168.0.107", 7878);发送端执行这行的时候,会发送跟接收端建立连接的请求
InputStream input = client.getInputStream();//接收使用输入流
OutputStream output = client.getOutputStream();//发送使用输出流
//服务器端发送数据。
//如果不使用多线程的话。执行到while(true)时就永远不会执行下面的发送数据代码了,所以需要使用多线程并行
Thread t = new Thread() {
public void run() {
Scanner scanner = new Scanner(System.in);
while(true) {
if(client.isClosed())break;
String str = scanner.nextLine();
if(str.equals("end"))break;
try {
output.write(str.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
t.setDaemon(true);//设置成守护线程。防止客户端关闭连接时,服务器端卡在scanner.nextLine();//然而并无卵用,TODO
t.start();
//服务器端接收数据
byte[] buf = new byte[1024];
// while(true) {//接收数据不能使用死循环,因为客户端关闭连接时接收到的数据length是-1,需用下面这种方法
// int length = input.read(buf);
// System.out.println(length);
// System.out.println(new String(buf,0,length));
// }
int length = -1;//-1代表流结束,客户端关闭连接close()时会返回-1
while( (length = input.read(buf))!=-1 ) {//read()会等待客户端发送数据
System.out.println(new String(buf,0,length));
}
client.close();
ss.close();
}
}
10,聊天室功能
一个服务器端接收数据并输出,多个客户端可以持续的发送数据
一个服务器端接收数据并转发到所有客户端。
package com.sikiedu.chapter2;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Demo08_TCP_Server {
public static void main(String[] args) {
new ConnectionThread().start();
}
}
class ConnectionThread extends Thread {
@Override
public void run() {
ServerSocket ss = null;
try {
ss = new ServerSocket(7878);
while(true) {
Socket s = ss.accept();//监听客户端的连接请求
System.out.println("接收到一个客户端连接:"+s.getInetAddress());
new ClientThread(s).start();//必须开启线程,不然会阻塞下一个客户端请求
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if(ss!=null)
ss.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class ClientThread extends Thread{
private Socket s;
public ClientThread(Socket s) {
this.s= s;
}
@Override
public void run() {
try {
InputStream input= s.getInputStream();
byte[] buf = new byte[1024];
int length = -1;
while( (length = input.read(buf))!=-1 ) {
System.out.println(new String(buf,0,length));
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(s!=null)
try {
s.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package com.sikiedu.chapter2;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class Demo09_TCP_Client {
public static void main(String[] args) throws Exception {
Socket s = new Socket("192.168.31.225", 7878);
Scanner scanner = new Scanner(System.in);
s.getOutputStream().write(scanner.nextLine().getBytes());
s.close();
}
}
枚举类型
作用:见名知意,增加代码可读性。<br /> <br /> <br /> 注意枚举类型的作用是见名知意,它底层还是使用int类型来存储和判断的。<br /> 枚举类型里面的每个值都有一个对应的int值。<br />
package com.sikiedu.chapter3;
public class Demo01_CustomEnum {
public static void main(String[] args) {
RoleTypeEnum rt1 = RoleTypeEnum.TEACHER;
RoleTypeEnum rt2 = RoleTypeEnum.STUDENT;
if(rt1==RoleTypeEnum.PRINCIPAL) {//因为本质是int,所以可以直接==比较
}
SeasonEnum s1 = SeasonEnum.FALL;
SeasonEnum s2= SeasonEnum.WINTER;
System.out.println(s1);//FALL。直接输出SeasonEnum对象输出的是枚举所对应的字符串
System.out.println(s1.ordinal());//2。获取枚举值对应的int
for(SeasonEnum se : SeasonEnum.values()) {//遍历枚举类型的所有值
System.out.println(se);//SPRINT SUMMER FALL WINTER
}
}
}
public enum RoleTypeEnum {
TEACHER,STUDENT,PRINCIPAL,HEADTEACHER,LOGISTICS
}
public enum SeasonEnum {
SPRINT,SUMMER,FALL,WINTER
}
类的加载机制和反射
类的使用分为三个步骤:
类的加载->类的连接->类的初始化
1,类的加载
当程序运行的时候,系统(JVM)会首先把我们要使用的Java类加载到内存中。这里加载的是编译后的.class文件。
每个类加载到内存中,会创建一个对应的Class对象。这个Class对象保存了这个类有哪些成员(数据成员,方法成员)。
注意:这里只有在某个Java类被使用的时候,才会被加载。
加载时机:任何用到这个类的时候。(实例化,使用里面的静态静态成员….)
2,类加载器(JVM里面的一个东东)
作用:将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并生成与之对应的java.lang.Class对象。
分类:
Bootstrap ClassLoader 根类加载器
加载JRE中的核心类,比如java.lang.String 。。。
Extension ClassLoader 扩展类加载器
加载JRE中的扩展类
System ClassLoader 系统类加载器
一般用来加载我们自己写的类
3,反射
解释:在程序运行的时候,查看一个类有哪些信息(包含的数据成员和方法成员)。这个过程称之为反射。
程序中用到了java.lang.Class对象,那就是在用反射。
如果我们知道我们要使用哪个类,那么我们只需要只用这个对应的类创建对象,然后就可以调用获取这个对象里面的数据和调用里面的方法。
(知道类,知道这个类里面有哪些属性和方法——>使用这个对象里面对应的属性和方法)
但是我们不知道我们要使用的是哪个类,这个时候我们需要使用反射获取到类的信息,里面有哪些成员,再使用。
(不知道——>获取类信息—->使用(实例化对象,调用属性和方法))反射
4,获取Class对象(反射的第一步)
方式一:通过对象获得 对象.getClass()
方式二(很少用):通过类获得 类.class
方式三:Class.forName(“类路径”)//必须传递完整路径(加上包名)
package com.sikiedu.chapter4;
public class Demo01_GetClass {
public static void main(String[] args) throws Exception {
//获取Class对象
//1,通过对象
User user1 = new User(100,"siki","123");
User user2 = new User(200,"edu","456");
Class c1 = user1.getClass();
System.out.println(c1);//class com.sikiedu.chapter4.User
Class c2 = user2.getClass();
System.out.println(c2);//class com.sikiedu.chapter4.User
System.out.println(c1==c2);//true 每个类只有对应的一个Class对象
Demo01_GetClass o = new Demo01_GetClass();
Class c3 = o.getClass();
System.out.println(c3==c2);//false c2是User的Class对象,c3是Demo01_GetClass的Class对象
//2,通过类(很少用)
Class c4 = User.class;
System.out.println(c4==c1);//true
//3,Class.forName
Class c5 = Class.forName("java.lang.Math");
System.out.println(c5==c1);//false
Class c6 = Class.forName("com.sikiedu.chapter4.User");
System.out.println(c6==c1);//true
}
}
5,从Class对象中获取信息
测试类User:
package com.sikiedu.chapter4;
public class User {
private int id;
private String username;
private String password;
public int age;
private User(int id) {
this.id = id;
}
public User() {}
public User(int id,String username,String password) {
this.id = id;
this.username = username;
this.password = password;
}
public User(int id ,String username,String password,int age) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
}
public void show() {
System.out.println(id+":"+username+":"+password);
}
public void study() {
System.out.println("学习");
}
public void study(String courseName) {
System.out.println(username+"正在学习"+courseName);
}
private void shower(String name) {
System.out.println(username+"正在使用"+name+"洗澡");
}
}
5.1,得到构造方法:
getConstructors();//获取所有public构造方法信息
getConstructor(Class<?> … parameterTypes );//根据参数类型,获取指定参数类型的public构造方法
getDeclaredConstructor(Class<?> … parameterTypes );//忽略访问权限,获取构造方法
getDeclaredConstructors();//忽略访问权限,获取构造方法
package com.sikiedu.chapter4;
import java.lang.reflect.Constructor;
public class Demo02_GetConstructorByClass {
public static Class c;
public static void main(String[] args) throws Exception {
c = Class.forName("com.sikiedu.chapter4.User");
// getConstructors();
// getConstructor();
// getConstructorByPara();
// getAllConstructors();
// getSpecifiedConstructorByPara();
}
// 得到所有的public构造方法
static void getConstructors() {
Constructor[] cs = c.getConstructors();//
for (Constructor con : cs) {
System.out.println(con);
}
/*
* 输出: public com.sikiedu.chapter4.User() public
* com.sikiedu.chapter4.User(int,java.lang.String,java.lang.String) public
* com.sikiedu.chapter4.User(int,java.lang.String,java.lang.String,int)
*/
}
// 得到指定的public构造方法,并调用构造方法
// 1,得到默认的构造方法并调用
static void getConstructor() throws Exception {
Constructor con = c.getConstructor();
System.out.println(con);// public com.sikiedu.chapter4.User()
Object o1 = con.newInstance();
User user = (User) o1;
user.show();// 0:null:null
}
// 2,得到带参数的构造方法并调用
static void getConstructorByPara() throws Exception {
Constructor con = c.getConstructor(int.class, String.class, String.class);
System.out.println(con);// public com.sikiedu.chapter4.User(int,java.lang.String,java.lang.String)
Object o1 = con.newInstance(100, "siki", "123");
User user = (User) o1;
user.show();// 100:siki:123
}
// 忽略访问权限,得到所有的构造函数(public + private)
static void getAllConstructors() {
Constructor[] cs = c.getDeclaredConstructors();
for (Constructor con : cs) {
System.out.println(con);
}
/*
* 输出 public
* com.sikiedu.chapter4.User(int,java.lang.String,java.lang.String,int) public
* com.sikiedu.chapter4.User(int,java.lang.String,java.lang.String) public
* com.sikiedu.chapter4.User() private com.sikiedu.chapter4.User(int)
*/
}
// 忽略访问权限,得到带参数的构造方法并调用(public + private)
static void getSpecifiedConstructorByPara() throws Exception {
Constructor con = c.getDeclaredConstructor(int.class);
System.out.println(con);// private com.sikiedu.chapter4.User(int)
con.setAccessible(true);// 这里必须加这句才能调用private的构造方法
Object o1 = con.newInstance(100);
User user = (User) o1;
user.show(); // 100:null:null
}
}
5.2,得到成员变量
getFields
getDeclaredFields
getField
getDeclaredField
package com.sikiedu.chapter4;
import java.lang.reflect.Field;
//构造方法 成员变量 成员方法
public class Demo03_GetFieldByClass {
public static void main(String[] args) throws Exception {
User u = new User(100,"siki","123",40);
Class c = Class.forName("com.sikiedu.chapter4.User");
// //得到所有的public字段(成员变量)
// Field[] fields = c.getFields();
// for( Field f : fields ) {
// System.out.println(f);//public int com.sikiedu.chapter4.User.age
// }
// //得到指定的public字段(成员变量)
// Field ageField = c.getField("age");
// System.out.println(ageField);//public int com.sikiedu.chapter4.User.age
// int age = ageField.getInt(u);
// System.out.println(age);//40 通过反射的方式访问成员变量 正常情况:u.age 反射情况:age.u
// //忽略访问权限,得到所有的字段(public + private)
// Field[] fields = c.getDeclaredFields();
// for( Field f : fields ) {
// System.out.println(f);
// }
// /*
// * private int com.sikiedu.chapter4.User.id
// private java.lang.String com.sikiedu.chapter4.User.username
// private java.lang.String com.sikiedu.chapter4.User.password
// public int com.sikiedu.chapter4.User.age
//
// * */
// //忽略访问权限,得到制定的字段(public + private)
// Field usernameField = c.getDeclaredField("username");
// usernameField.setAccessible(true);
// System.out.println(usernameField.get(u));//siki
}
}
5.3,得到成员方法
getMethod
getMethods
getDeclaredMethod
getDeclaredMethods
package com.sikiedu.chapter4;
import java.lang.reflect.Method;
public class Demo04_GetMethodByClass {
public static void main(String[] args) throws Exception {
User u = new User(100,"siki","123",40);
Class c = Class.forName("com.sikiedu.chapter4.User");
// //得到所有public方法,包括父类的
// Method[] methods = c.getMethods();
// for(Method m :methods) {
// System.out.println(m);
// }
// /*
// * public void com.sikiedu.chapter4.User.show()
// public void com.sikiedu.chapter4.User.study(java.lang.String)
// public void com.sikiedu.chapter4.User.study()
// public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
// public final void java.lang.Object.wait() throws java.lang.InterruptedException
// public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
// public boolean java.lang.Object.equals(java.lang.Object)
// public java.lang.String java.lang.Object.toString()
// public native int java.lang.Object.hashCode()
// public final native java.lang.Class java.lang.Object.getClass()
// public final native void java.lang.Object.notify()
// public final native void java.lang.Object.notifyAll()
// * */
//
// //得到指定的public方法
// //u.show() show.u
// Method m= c.getMethod("show");
// Object o =m.invoke(u);//100:siki:123
// System.out.println(o);//null
// Method studyMethod = c.getMethod("study", String.class);
// studyMethod.invoke(u, "生物");//siki正在学习生物
// //忽略访问权限,得到所有的自身(不包括父类)方法(public + private)
// Method[] methods = c.getDeclaredMethods();//只得到自身的所有方法(包括private),不包括父类里面的方法
// for(Method m :methods) {
// System.out.println(m);
// }
// /*
// * private void com.sikiedu.chapter4.User.shower(java.lang.String)
// public void com.sikiedu.chapter4.User.study()
// public void com.sikiedu.chapter4.User.study(java.lang.String)
// public void com.sikiedu.chapter4.User.show()
// * */
// //忽略访问权限,得到指定的自身(不包括父类)方法(public + private)
// Method showerM = c.getDeclaredMethod("shower", String.class);
// showerM.setAccessible(true);
// showerM.invoke(u, "海飞丝");//siki正在使用海飞丝洗澡
}
}
Constructor成员:
newInstance
setAccessible