1. 实现多线程

1.1 进程

进程:是正在运行的程序

  • 是系统进行资源分配郑调用的独立单位
  • 每一个进程都有它自己的内存空间和系统资源

1.2 线程

线程:是进程中的单个顺序控制流,是一条执行路径

  • 单线程:一个进程如果只有一条执行路径,则称为单线程程序
  • 多线程:一个进程如果有多条执行路径,则称为多线程程序

举例:

  • 记事本程序
  • 扫雷程序

1.3 多线程的实现方式(方式1)

方式1:继承Thread类

  • 定义一个类MyThread继承Thread类
  • 在MyThread类中重写run()方法
  • 创建MyThread类的对象
  • 启动线程

两个小问题:

  • 为什么要重写run() 方法?

因为run() 是用来封装被线程执行的代码

  • run() 方法和start() 方法的区别?

run() :封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//1.创建线程类继承Thread类
package demo_01;

public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}

//2.
package demo_01;

public class MyThreadDemo {
public static void main(String[] args) {
//创建线程对象
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();

//run() :封装线程执行的代码,直接调用,相当于普通方法的调用
/* mt1.run();
mt2.run();*/

//void start()导致此线程开始执行; Java虚拟机调用此线程的run方法。
mt1.start();
mt2.start();
}
}

1.4 设置和获取线程名称

Thread类中设置和获取线程名称的方法

  • void setName(String name):将此线程的名称更改为等于参数name
  • String getName() :返回此线程的名称
  • 通过构造方法也可以设置线程名称

如何获取main() 方法所在的线程名称?

  • public static Thread currentThread() 返回对当前正在执行的线程对象的引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//1.
package demo_02;

public class MyThread extends Thread{
public MyThread() {
}

public MyThread(String name) {
super(name);
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}

//2.
package demo_02;
/*
Thread类中设置和获取线程名称的方法
void setName(String name):将此线程的名称更改为等于参数name
String getName() :返回此线程的名称
*/

public class MyThreadDemo {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();

//void setName(String name) 将此线程的名称更改为等于参数 name
mt1.setName("高铁");
mt2.setName("飞机");

//Thread(String name)分配一个新的Thread对象
// MyThread mt1 = new MyThread("高铁");
// MyThread mt2 = new MyThread("飞机");

mt1.start();
mt2.start();

//static Thread currentThread()返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName()); //main
}
}

1.5 线程调度

线程有两种调度模型

  • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
  • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
  • Java使用的是抢占式调度模型

假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,
才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

Thread类中设置和获取线程优先级的方法

  • public final int getPriority() :返回此线程的优先级
  • public final void setPriority(int newPriority):更改此线程的优先级
  1. 线程默认优先级是5;线程优先级的范围是:1-10
  2. 线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//1.
package demo_03;

public class ThreadPriority extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}

//2.
package demo_03;

public class ThreadPriorityDemo {
public static void main(String[] args) {
//创建线程对象
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();

tp1.setName("高铁");
tp2.setName("飞机");
tp3.setName("汽车");

//public final int getPriority()返回此线程的优先级
/*
System.out.println(tp1.getPriority()); //5
System.out.println(tp2.getPriority()); //5
System.out.println(tp3.getPriority()); //5
*/

//void setPriority(int newPriority)更改此线程的优先级
/*
System.out.println(Thread.MAX_PRIORITY); //10
System.out.println(Thread.MIN_PRIORITY); //1
System.out.println(Thread.NORM_PRIORITY); //5
*/

//设置正确的优先级
tp1.setPriority(5);
tp2.setPriority(10);
tp3.setPriority(1);

//启动线程
tp1.start();
tp2.start();
tp3.start();
}
}

1.6 线程控制

方法名 说明
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
void join() 等待这个线程死亡
void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//1.
package demo_04;

public class ThreadSleep extends Thread {
public ThreadSleep() {
}

public ThreadSleep(String name) {
super(name);
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
try {
//static void sleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

//2.
package demo_04;

//static void sleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性

public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep("曹操");
ThreadSleep ts2 = new ThreadSleep("刘备");
ThreadSleep ts3 = new ThreadSleep("孙权");

ts1.start();
ts2.start();
ts3.start();
}
}

void join() 等待这个线程死亡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//1.
package demo_04;

public class ThreadJoin extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}

//2.
package demo_04;
//void join() 等待这个线程死亡
public class ThreadJoinDemo {
public static void main(String[] args) {
//创建线程类对象
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();

tj1.setName("张三");
tj2.setName("李四");
tj3.setName("王五");

tj1.start();
try {
//void join() 等待这个线程死亡
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
tj2.start();
tj3.start();
}
}

void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//1.
package demo_04;

public class ThreadDaemon extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println();
}
}
}

//2.
package demo_04;
//void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();

td1.setName("关羽");
td1.setName("张飞");

//设置主线程为刘备
Thread.currentThread().setName("刘备");

//设置守护线程
td1.setDaemon(true);
td2.setDaemon(true);

for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}

1.7 线程生命周期

1.8 多线程的实现方式(方式2)

方式2:实现Runnable接口

  • 定义一个类MyRunnable:实现Runnablef接口
  • 在MyRunnable类中重写run()方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,把MyRunnable对像作为构造方法的参数
  • 启动线程

多线程的实现方案有两种

  • 继承Thread类
  • 实现Runnable接口

相比继承Thread类,实现Runnable接口的好处

  • 避免了Java单继承的局限性
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//1.
package demo_05;

public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}

//2.
package demo_05;
/*
方式2:实现Runnable接口
1.定义一个类MyRunnable:实现Runnablef接口
2.在MyRunnable类中重写run()方法
3.创建MyRunnable类的对象
4.创建Thread类的对象,把MyRunnable对像作为构造方法的参数
5.启动线程
*/
public class MyRunnableDemo {
public static void main(String[] args) {
//创建MyRunnable类的对象
MyRunnable mr = new MyRunnable();

//创建Thread类的对象,把MyRunnable对像作为构造方法的参数
//public Thread(Runnable target)
// Thread t1 = new Thread(mr);
// Thread t2 = new Thread(mr);

//public Thread(Runnable target, String name)
Thread t1 = new Thread(mr,"高铁");
Thread t2 = new Thread(mr,"飞机");

//启动线程
t1.start();
t2.start();
}
}

2. 线程同步

案例:卖票

需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

思路:
①定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets=100;
②在SellTicket类中重写run() 方法实现卖票,代码步骤如下:
A:判断票数大于0,就卖票,并告知是哪个窗口卖的
B:卖了票之后,总票数要减1
C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
③定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
A:创建SellTicket类的对象
B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
C:启动线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//1.定义一个类SellTicket实现Runnable接口
package demo_06;

public class SellTicket implements Runnable {
private int tickets = 100;

@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}

//2.定义一个测试类SellTicketDemo,里面有main方法
package demo_06;
/*
定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
A:创建SellTicket类的对象
B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
C:启动线程
*/

public class SellTicketDemo {
public static void main(String[] args) {
//创建SellTicket类的对象
SellTicket st = new SellTicket();

//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
Thread th1 = new Thread(st,"窗口1");
Thread th2 = new Thread(st,"窗口2");
Thread th3 = new Thread(st,"窗口3");

//启动线程
th1.start();
th2.start();
th3.start();
}
}

2.1 买票案例的思考

刚才讲解了电影院卖票程序,好像没有什么问题。但是在实际生活中,售票时出票也是需要时间的所以,在出售一张票的时候,需要一点时间的延迟,接下来我们去修改卖票程序中卖票的动作:每次出票时间100毫秒,用sleep() 方法实现

卖票出现了问题:

  1. 相同的票出现了多次
  2. 出现了负数的票

问题原因:

  • 线程执行的随机性导致的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//1.
package demo_06;

public class SellTicket implements Runnable {
private int tickets = 100;

@Override
public void run() {
//相同的票出现了多次
while (true) {
/* if (tickets > 0) {
//通过sleep() 方法来模拟出票时间
try {
Thread.sleep(1000);
//t1线程休息1000毫秒
//t2线程抢到cpu执行权,t2线程开始执行,执行到这里,t2休息1000毫秒
//t3线程抢到cpu执行权,t3线程开始执行,执行到这里,t3休息1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//按照线程顺序醒来
//t1抢到线程执执行权,在控制台输出:窗口1正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
//t2抢到线程执执行权,在控制台输出:窗口2正在出售第100张票
//t3抢到线程执执行权,在控制台输出:窗口3正在出售第100张票
tickets--;
//如果这三个线程按照顺序,执行到这里执行了3次--操作,最终票变成了97
}*/

//出现负数票的情况
if (tickets > 0) {
//通过sleep() 方法来模拟出票时间
try {
Thread.sleep(10);
//t1线程休息1000毫秒
//t2线程抢到cpu执行权,t2线程开始执行,执行到这里,t2休息1000毫秒
//t3线程抢到cpu执行权,t3线程开始执行,执行到这里,t3休息1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//假设线程按照顺序醒过来
//t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票
//假设t1继续拥有cPU的执行权,就会执行tickets--;操作,tickets=0;
//t2抢到了CPU的执行权,在控制台输出:窗口1正在出售第0张票
//假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets=-1;
//t3抢到了CPU的执行权,在控制台输出:窗口3正在出售第-1张票
//假设t2继续拥有CPU的执行权,就会执行tickets-;操作,tickets=-2;
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}

//2.
package demo_06;
/*
定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
A:创建SellTicket类的对象
B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
C:启动线程
*/

public class SellTicketDemo {
public static void main(String[] args) {
//创建SellTicket类的对象
SellTicket st = new SellTicket();

//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
Thread th1 = new Thread(st,"窗口1");
Thread th2 = new Thread(st,"窗口2");
Thread th3 = new Thread(st,"窗口3");

//启动线程
th1.start();
th2.start();
th3.start();
}
}

2.2 卖票案例数据安全问题解决

为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)

  • 是否是多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据

如何解决多线程安全问题呢?

  • 基本思想:让程序没有安全问题的环境

怎么实现呢?

  • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
  • Java提供了同步代码块的方式来解决(如下2.3)

2.3 同步代码块

锁多条语句操作共享数据,可以使用同步代码块实现

格式:

1
2
3
synchronized(任意对象){
多条语句操作共享数据的代码
}
  • synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端:

  • 好处:解决了多线程的数据安全问题
  • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//1.
package demo_06;

public class SellTicket implements Runnable {
private int tickets = 100;
private Object obj = new Object();

@Override
public void run() {

while (true) {
synchronized (obj){
//t1进来后就会把这段代码锁起来
if (tickets > 0) {
try {
Thread.sleep(100);
//t1线程休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
//t1出来了,这段代码就被释放了
}
}
}

//2.
package demo_06;

public class SellTicketDemo {
public static void main(String[] args) {
//创建SellTicket类的对象
SellTicket st = new SellTicket();

//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
Thread th1 = new Thread(st,"窗口1");
Thread th2 = new Thread(st,"窗口2");
Thread th3 = new Thread(st,"窗口3");

//启动线程
th1.start();
th2.start();
th3.start();
}
}

2.4 同步方法

同步方法:就是把synchronized关键字加到方法上

格式:

1
修饰符  synchronized  返回值类型  方法名(方法参数){}

同步方法的锁对象是什么呢?

  • this

同步静态方法:就是把synchronized关键字加到静态方法上

格式:

1
修饰符 static synchronized  返回值类型  方法名(方法参数){}

同步静态方法的锁对象是什么呢?

  • 类名.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
//1.
package demo_06;

public class SellTicket implements Runnable {
private static int tickets = 100;
private Object obj = new Object();
private int x = 0;

@Override
public void run() {
while (true) {
if (x % 2 == 0) {
// synchronized (obj) {
// synchronized (this) { //同步方法锁
synchronized (SellTicket.class) { //静态同步方法锁
//t1进来后就会把这段代码锁起来
if (tickets > 0) {
try {
Thread.sleep(10);
//t1线程休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
} else {
sellticks();
}
x++;
}

}

/*private void sellticks() {
synchronized (obj) {
//t1进来后就会把这段代码锁起来
if (tickets > 0) {
try {
Thread.sleep(100);
//t1线程休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}*/

//同步方法
/*private synchronized void sellticks() {
if (tickets > 0) {
try {
Thread.sleep(100);
//t1线程休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}*/

//静态同步方法
private static synchronized void sellticks() {
if (tickets > 0) {
try {
Thread.sleep(100);
//t1线程休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}

//2.
package demo_06;

public class SellTicketDemo {
public static void main(String[] args) {
//创建SellTicket类的对象
SellTicket st = new SellTicket();

//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
Thread th1 = new Thread(st,"窗口1");
Thread th2 = new Thread(st,"窗口2");
Thread th3 = new Thread(st,"窗口3");

//启动线程
th1.start();
th2.start();
th3.start();
}
}

2.5 线程安全的类

StringBuffer

  • 线程安全,可变的字符序列
  • 从版本JDK5开始,被StringBuilder替代。通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步

Vector

  • 从Java2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用ArrayList代替Vector

Hashtable

  • 该类实现了一个哈希表,它将键映射到值。任何非null对像都可以用作键或者值
  • 从Java2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。
    与新的集合实现不同,Hashtable被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package demo_07;

import java.util.*;

/*
线程的安全类:
StringBuffer
Vertor
Hashtable
*/
public class ThreadDemo {
public static void main(String[] args) {
StringBuffer sb2 = new StringBuffer();
StringBuilder sb = new StringBuilder();

Vector<String> v = new Vector<String>();
ArrayList<String> array = new ArrayList<String>();

Hashtable<String, String> ht = new Hashtable<String, String>();
HashMap<String, String> hm = new HashMap<String, String>();

//public static <T> List<T> synchronizedList(List<T> list)返回由指定列表支持的同步(线程安全)列表
List<String> lise = Collections.synchronizedList(new ArrayList<String>());
}
}

2.6 Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized() 方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法:

  • void lock() :获得锁
  • void unlock() :释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法:

  • ReentrantLock() :创建一个ReentrantLock的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//1.
package demo_08;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
} finally {
lock.unlock();
}
}
}
}

//2.
package demo_08;

public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();

Thread th1 = new Thread(st,"窗口1");
Thread th2 = new Thread(st,"窗口2");
Thread th3 = new Thread(st,"窗口3");

th1.start();
th2.start();
th3.start();
}
}

4. 网络编程入门

4.1 网络编程概述

计算机网络

  • 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统

网络编程

  • 在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据交换

4.2 网络编程三要素

IP地址

  • 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而P地址就是这个标识号。也就是设备的标识

端口

  • 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说P地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识

协议

  • 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规呗则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议

4.3 IP地址

IP地址:是网络中设备的唯一标识

P地址分为两大类

  • IPv4:是给每个连接在网络上的庄机分配一个32bi地址。按照TCP八P规定,P地址用二进制来表示,每个P地址长32bit,也就是4个字节。例破如一个采用二进制形式的P地址是“11000000101010000000000101000010°”,这么长的地址,处理起来也太费劲了。为了方便使用,P地址经常被写成十进制的形式,中间使用符号“”分隔不同的字节。于是,上面的1P地址可以表示为”192.168.1.66”。P地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
  • IPV6:由于互联网的蓬勃发展,P地址的需求量愈来愈大,但是网络地址资源有限,使得P的分配越发紧张。为了扩大地址空间,通过Pv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题

常用命令:

  • ipconfig:查看本机IP地址
  • ping IP地址:检查网络是否连通

特殊IP地址:

  • 127.0.0.1:是回送地址,可以代表本机地址,般用来测试使用

4.4 InetAddress的使用

为了方便我们对lP地址的获取和操作,Java提供了一个类InetAddress供我们使用

InetAddress:此类表示Internet协议 (IP) 地址

方法明 说明
static InetAddress getByName(String host) 确定主机名称的P地址。主机名称可以是机器名称,也可以是P地址
String getHostName() 获取此P地址的主机名
String getHostAddress() 返回文本显示中的IP地址字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package demo_01;
/*
InetAddress:此类表示Internet协议 (IP) 地址
static InetAddress getByName(String host)确定主机名称的P地址。主机名称可以是机器名称,也可以是P地址
String getHostName()获取此P地址的主机名
String getHostAddress()返回文本显示中的IP地址字符串
*/
import java.io.IOException;
import java.net.InetAddress;

public class InetAddressDemo {
public static void main(String[] args) throws IOException {

//static InetAddress getByName(String host)确定主机名称的P地址。主机名称可以是机器名称,也可以是P地址
InetAddress address = InetAddress.getByName("192.168.31.81");

//String getHostName()获取此P地址的主机名
String name = address.getHostName();
//String getHostAddress()返回文本显示中的IP地址字符串
String ip = address.getHostAddress();

System.out.println("主机名:"+name);
System.out.println("IP地址:"+ip);
}
}

4.5 端口

端口:设备上应用程序的唯一标识

端口号:用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败

4.6 协议

协议:计算机网络中,连接和通信的规则被称为网络通信协议

UDP协议:

  • 用户数据报协议(User Datagram Protocol)
  • UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
  • 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议

TCP协议:

  • 传输控制协议(Transmission Control Protocol)
  • TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收总建立逻辑连接,然后再传输数据,
    它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端
    向服务端发出连接请求,每次连接的创建都需要经过“三次握手”
  • 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
    第一次握手,客户端向服务器端发出连接请求,等待服务器确认
    第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
    第三次握手,客户端再次向服务器端发送确认信息,确认连接
  • 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等

5. UDP通信原理

5.1 UDP通信原理

UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念Java提供了DatagramSocket类作为基于UDP协议的Socket

5.2 UDP发送数据

发送数据的步骤

①创建发送端的Socket对象(DatagramSocket)

1
DatagramScoket()

②创建数据,并把数据打包

1
DatagramPacket(byte[]buf,int length,InetAddress address,int port)

③调用DatagramSocket对象的方法发送数据

1
void send(DatagramPacket p)

④关闭发送端

1
void close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package demo_02;

/*
发送数据的步骤:
创建发送端的Socket对象(DatagramSocket)
创建数据,并把数据打包
调用DatagramSocket对象的方法发送数据
关闭发送端
*/

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端的Socket对象(DatagramSocket)
//DatagramSocket()
//构造数据报套接字并将其绑定到本地主机上的任何可用端口
DatagramSocket ds = new DatagramSocket();

//创建数据,并把数据打包
//DatagramPacket(byte[] buf, int length, InetAddress address, int port)
//构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号
byte[] bys = "hello,java".getBytes();
DatagramPacket dp = new DatagramPacket(bys,bys.length, InetAddress.getByName("192.168.31.81"),10010);

//调用DatagramSocket对象的方法发送数据
//void send(DatagramPacket p)从此套接字发送数据报包
ds.send(dp);

//关闭发送端
ds.close();
}
}

5.3 UDP接收数据

接收数据的步骤

①创建接收端的Socket对象(DatagramSocket)

1
DatagramSocket(int port)

②创建一个数据包,用于接收数据

1
DatagramPacket(byte[]buf,int length)

③调用DatagramSocket对象的方法接收数据

1
void receive(DatagramPacket p)

④解析数据包,并把数据在控制台显示

1
2
byte[] getData()
int getLength()

⑤关闭接收端

1
void close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package demo_02;
/*
接收数据的步骤:
创建接收端的Socket对象(DatagramSocket)
创建一个数据包,用于接收数据
调用DatagramSocket对象的方法接收数据
解析数据包,并把数据在控制台显示
关闭接收端
*/
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端的Socket对象(DatagramSocket)
DatagramSocket ds = new DatagramSocket(10010);

//创建一个数据包,用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys,bys.length);

//调用DatagramSocket对象的方法接收数据
ds.receive(dp);

//解析数据包,并把数据在控制台显示
byte[] datas = dp.getData();
int len = dp.getLength();
System.out.println("数据是:"+new String(datas,0,len));

//关闭接收端
ds.close();
}
}

5.4 UDP通信程序练习

按照下面的要求实现程序:

  1. UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
  2. UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//1.发送端
package demo_03;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端的Scoket对象(DatagramScoket());
DatagramSocket ds = new DatagramSocket();

//自己封装键盘录入数据
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line=br.readLine())!=null){
//输入的数据为886,发送数据结束
if ("886".equals(line)){
break;
}
//创建数据并把数据打包
byte[] bys = line.getBytes();
DatagramPacket dp = new DatagramPacket(bys,bys.length, InetAddress.getByName("192.168.31.81"),10010);

//调用DagramScoket对象的方法发送数据
ds.send(dp);

//关闭发送端
// ds.close();
}
}
}

//2.服务器端
package demo_03;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建结束端的Scoket对象(DatagramScoket)
DatagramSocket ds = new DatagramSocket(10010);

while (true){
//创建数据包用于接收数据
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys,bys.length);

//调用DatagramScoket对象的方法接收数据
ds.receive(dp);

//解析数据包,并把数据输出在控制台
System.out.println("数据是:"+new String(dp.getData(),0,dp.getLength()));
}

//关闭接收端
// ds.close();
}
}

6. TCP通信原理

6.1 TCP通信原理

  1. TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端新形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就河以通过虚拟链路进行通信
  2. Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信
  3. Java为客户端提供了Socket类,为服务器端提供了ServerSocket类

6.2 TCP发送数据

发送数据的步骤:

①创建客户端的Socket对象(Socket)

1
Socket(String host,int port)

②获取输出流,写数据

1
OutputStream getOutputStream()

③释放资源

1
void close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package TCPDemo_04;
/*
发送数据的步骤
创建客户端的Socket对象(Socket)
获取输出流,写数据
科放资源
*/
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端的Socket对象(Socket)
//Socket(InetAddress address, int port)创建流套接字并将其连接到指定IP地址的指定端口号
// Socket s = new Socket(InetAddress.getByName("192.168.31.81"),10010);
//Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号
Socket s= new Socket("192.168.31.81",10010);

//获取输出流,写数据
//OutputStream getOutputStream()返回此套接字的输出流
OutputStream os = s.getOutputStream();
os.write("hello,java".getBytes());

//释放资源
s.close();
}
}

6.3 TCP接受数据

接收数据的步骤:

①创建服务器端的Socket对象(ServerSocket)

1
ServerSocket(int port)

②监听客户端连接,返回一个Socket对象

1
Socket accept()

③获取输入流,读数据,并把数据显示在控制台

1
InputStream getlnputStream()

④释放资源

1
void close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package TCPDemo_04;
/*
接收数据的步骤:
创建服务器端的Socket对象(ServerSocket)
监听客户端连接,返回一个Sockety对象
获取输入流,读数据,并把数据显示在控制台
释放资源
*/
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Serverdemo {
public static void main(String[] args) throws IOException {
//创建服务器端的Socket对象(ServerScoket)
//ServerSocket(int port)创建绑定到指定端口的服务器套接字
ServerSocket ss = new ServerSocket(10010);

//监听客户端连接,返回一个Sockety对象
Socket s = ss.accept();

//获取输入流,读数据,并把数据显示在控制台
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String data = new String(bys,0,len);
System.out.println("数据是:"+data);

//释放资源
ss.close();
s.close();
}
}

6.4 TCP通信程序练习

练习1

  • 客户端:发送数据,接收服务器反馈
  • 服务端:接收数据,给出反馈
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
//服务器端
package TcpPracticeDemo_01;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
//服务端:接收数据,给出反馈
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器Socket对象(ServerSocket)
ServerSocket ss = new ServerSocket(10010);

//监听客户端连接,返回Socket对象
Socket s = ss.accept();

//获取输入流,读数据,并把数据显示在控制台
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);
String data = new String(bys,0,len);
System.out.println("服务器"+data);

/*int len;
while ((len = is.read(bys)) != -1) {
String data = new String(bys,0,len);
System.out.println("服务器"+data);
}*/

//给出反馈
OutputStream os = s.getOutputStream();
os.write("over".getBytes());

//释放资源
ss.close();
}
}

//客户端
package TcpPracticeDemo_01;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
//客户端:发送数据,接收服务器反馈
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket ss = new Socket("192.168.31.81",10010);

//获取输出流,写数据
OutputStream os = ss.getOutputStream();
os.write("hello,java".getBytes());

//接受服务器反馈
InputStream is = ss.getInputStream();
byte[] bys = new byte[1024];
int len;
while ((len = is.read(bys)) != -1) {
System.out.println("客户端:" + new String(bys, 0, len));
}

//释放资源
ss.close();
}
}

练习2

  • 客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束
  • 服务器:接收到的数据在控制台输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//服务器端
package TPracticeDemo_02;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

//服务器:接收到的数据在控制台输出
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(10010);

//监听客户端连接,返回对应的Socket对象
Socket s = ss.accept();

//获取输入流
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
String data = new String(line);
System.out.println("服务器:"+data);
}
//释放资源
ss.close();
}
}

//客户端
package TPracticeDemo_02;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

//客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s= new Socket(InetAddress.getByName("192.168.31.81"),10010);

//数据来自键盘录入,输入886,发送结束
BufferedReader be = new BufferedReader(new InputStreamReader(System.in));
//封装输出数据流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line;
while ((line=be.readLine())!=null){
if ("886".equals(line)){
break;
}
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
s.close();
}
}

练习3

  • 客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束
  • 服务器:接收到的数据写入文本文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//服务器端
package TPracticeDemo_03;

import com.sun.source.tree.WhileLoopTree;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

//- 服务器:接收到的数据写入文本文件
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务器Socket对象(ServerSocket)
ServerSocket ss = new ServerSocket(10010);

//监听客户端连接,返回Socket对象
Socket s = ss.accept();

//创建输入流,接收数据
BufferedReader data = new BufferedReader(new InputStreamReader(s.getInputStream()));
//创建输出流对象
BufferedWriter bw = new BufferedWriter(new FileWriter("myInet\\java.txt"));
String line;
while ((line = data.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
ss.close();
bw.close();
}
}

//客户端
package TPracticeDemo_03;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

//- 客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束

public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket(InetAddress.getByName("192.168.31.81"),10010);

//创建输入流,键盘录入数据,直到886结束
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//创建输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line;
while ((line = br.readLine()) != null) {
if ("886".equals(line)) {
break;
}
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
s.close();
}
}

练习4

  • 客户端:数据来自于文本文件
  • 服务器:接收到的数据写入文本文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//服务器端
package TPracticeDemo_04;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

//- 服务器:接收到的数据写入文本文件
public class ServerDemo {
public static void main(String[] args) throws Exception{
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(10010);

//监听客户端连接,返回Socket对象
Socket s = ss.accept();

//创建输入流接收数据
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//创建输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("myInet\\java.txt"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
ss.close();
bw.close();
}
}

//客户端
package TPracticeDemo_04;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

//- 客户端:数据来自于文本文件

public class ClientDemo {
public static void main(String[] args) throws Exception{
//创建客户端Socket对象
Socket s = new Socket("192.168.31.81",10010);

//创建输入流
BufferedReader br = new BufferedReader(new FileReader("myInet\\copy.txt"));
//创建输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
s.close();
br.close();
}
}

练习5

  • 客户端:数据来自于文本文件,接收服务器反馈
  • 服务器:接收到的数据写入文本文件,给出反馈

出现问题:程序一直等待

原因:读数据的方法是阻塞式的
解决办法:自定义结束标记使用shutdownOutput()方法(推荐)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
//服务器端
package TPracticeDemo_05;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

//- 服务器:接收到的数据写入文本文件,给出反馈
public class ServerDemo {
public static void main(String[] args) throws Exception{
//创建服务端Socket对象
ServerSocket ss = new ServerSocket(10010);

//监听客户端,返回Socket对象
Socket s = ss.accept();

//创建输入流
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//创建输出流
BufferedWriter bw= new BufferedWriter(new FileWriter("myInet\\copy.txt"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}

System.out.println(11111);

//创建输出流输出反馈数据
OutputStream outputStream = s.getOutputStream();
outputStream.write("数据已接收".getBytes());

//释放资源
ss.close();
bw.close();
}
}

//客户端
package TPracticeDemo_05;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

//- 客户端:数据来自于文本文件,接收服务器反馈

public class ClientDemo {
public static void main(String[] args) throws Exception{
//创建客户端Socket对象
Socket s = new Socket("192.168.31.81", 10010);

//创建输入流
BufferedReader br = new BufferedReader(new FileReader("myInet\\java.txt"));
//创建输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
//void shutdownOutput()禁用此套接字的输出流
s.shutdownOutput();

//创建输入流接收服务器反馈
InputStream inputStream = s.getInputStream();
byte[] bys = new byte[1024];
int len;
while ((len=inputStream.read(bys))!=-1){
System.out.println(new String(bys,0,len));
}
//释放资源
s.close();
br.close();
}
}

练习6

  • 客户端:数据来自于文本文件,接收服务器反馈
  • 服务器:接收到的数据写入文本文件,给出反馈,代码用线程进行封装,为每一个客户端开启一个线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//1.客户端
package TPracticeDemo_06;
//- 客户端:数据来自于文本文件,接收服务器反馈

import java.io.*;
import java.net.Socket;

public class ClientDemo {
public static void main(String[] args) throws Exception{
//创建客户端Socket对象
Socket s = new Socket("192.168.31.81",10010);

//创建输入流
BufferedReader br = new BufferedReader(new FileReader("myInet\\java.txt"));
//创建输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}

s.shutdownOutput();

//创建输入流接受服务器反馈
InputStream inputStream = s.getInputStream();
byte[] bys = new byte[1024];
int len;
while ((len = inputStream.read(bys)) != -1) {
System.out.println(new String(bys,0,len));
}
//释放资源
s.close();
br.close();
}
}

//2.服务器端
package TPracticeDemo_06;

import java.net.ServerSocket;
import java.net.Socket;

//- 服务器:接收到的数据写入文本文件,给出反馈,代码用线程进行封装,为每一个客户端开启一个线程
public class ServerDemo {
public static void main(String[] args) throws Exception {
//创建服务器Socket对象
ServerSocket ss = new ServerSocket(10010);

while (true) {
//监听客户端连接,返回socket对象
Socket s = ss.accept();
//为每一个客户端开启一个线程
new Thread(new ServerThread(s)).start();
}
}
}

//3.多线程
package TPracticeDemo_06;

import java.io.*;
import java.net.Socket;

public class ServerThread implements Runnable {
private Socket s;

public ServerThread(Socket s) {
this.s = s;
}

@Override
public void run() {
//接收数据写到文本文件
try {
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//解决命名冲突问题
int count = 0;
File file = new File("myInet\\copy"+count+".txt");
while (file.exists()) {
count++;
file = new File("myInet\\copy"+count+".txt");
}
BufferedWriter bw= new BufferedWriter(new FileWriter(file));
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}

//给出反馈
OutputStream outputStream = s.getOutputStream();
outputStream.write("服务器已成功接收数据".getBytes());

} catch (Exception e){
e.printStackTrace();
}
}
}