1.线程
线程是程序的执行路径,一个进程包含多个线程
多线程并发可以提高程序运行效率,同时完成多个工作。
实例:
服务器处理多个客户端请求,迅雷多线程下载,多人视频,一个cpu处理多个事务。
并行:甲乙任务同时进行(需要多核CPU)
并发:甲乙快速交替运行。
java命令启动JVM,启动程序,意味着启动进程,进程启动一个主线程,主线程调用某个类的main方法。
至少启动main和垃圾回收机制,所以JVM是多线程的。
2.多线程实现的方法共有三种,最常用的两种
第三种在15线程池中介绍
多线程实现的第一个方法:
①继承Thread类
②重写run方法
③创建Thread的子类对象
④开启start方法(不开启start方法,只是伪多线程)
案例代码:
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main");
}
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("MyThread");
}
}
先开启的是MyThread的线程,为什么先输出main?
线程的启动需要时间
多线程实现的第二个方法:
①实现Runnable接口
②重写run方法(只有这一个)
③创建Runnnable子类对象
④创建Thread的子类对象,将Runnable子类对象当做参数传入Thread的构造函数中
⑤开启start方法
案例代码:
public class Demo2_Thread {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread thread = new Thread(mr);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main");
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("MyRunnable");
}
}
}
两种方法的区别
查看源码的区别:
继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用
是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法
使用中:
继承Thread
* 好处是:可以直接使用Thread类中的方法,代码简单
* 弊端是:如果已经有了父类,就不能用这种方法
实现Runnable接口
* 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
* 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂
匿名内部类实现线程的两种方式:
public static void main(String[] args) {
new Thread() {
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("Thread");
}
};
}.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("Runnable");
}
}
}).start();
}
依旧是直接匿名类,或者是匿名内部类当做参数
3.获取和设置线程的名字
public static void main(String[] args) {
new Thread("构造方法直接命名") {
public void run() {
System.out.println(this.getName() + ">>>第一种命名");
};
}.start();
new Thread() {
public void run() {
this.setName("setName为线程命名");
System.out.println(this.getName() + ">>>第二种命名");
};
}.start();
Thread t = new Thread() {
@Override
public void run() {
System.out.println(this.getName() + ">>>第二种的衍生命名");
}
};
t.setName("索引命名");
t.start();
}
获取当前线程的对象
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
System.out.println(this.getName() + "正在运行");
}
}.start();
new Thread(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "正在运行");
};
}).start();
;
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName() + "正在运行");
}
currentThread()可以查看当前线程,继承Thread可以直接调用getname方法查看,但是实现Runnable接口需要先调用静态方法转为Thread对象再调用对应方法。
4.线程控制方法介绍
①休眠线程
public static void main(String[] args) throws InterruptedException {
// demo1();
new Thread("线程1") {
@Override
public void run() {
for (int i = 1; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.getName() + "-" + i);
}
}
}.start();
new Thread("线程2") {
@Override
public void run() {
for (int i = 1; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(this.getName() + "-" + i);
}
}
}.start();
}
sleep方法是让线程休眠指定的时间
②守护进程:
public static void main(String[] args) {
Thread t1 = new Thread() {
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(getName() + " " + i);
}
};
};
Thread t2 = new Thread() {
public void run() {
for (int i = 0; i < 400; i++) {
System.out.println(getName() + " " + i);
}
};
};
t2.setDaemon(true);
t1.start();
t2.start();
}
当正在运行的线程都是守护线程时,Java 虚拟机退出。守护进程就像陪跑一样。
为什么奇遇劲城运行结束,守护进程还要运行一段时间?
因为守护进程被通知需要停止到实际停止也需要一段时间。
③加入线程:
public static void demo2() {
Thread t1 = new Thread("插队那个") {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
System.out.println(this.getName() + "..." + i);
}
}
};
Thread t2 = new Thread("普通线程") {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
if (i == 500) {
try {
System.out.println("插队的来了!!!!");
t1.join(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(this.getName() + "..." + i);
}
}
};
t1.start();
t2.start();
}
public static void demo1() {
Thread t1 = new Thread("插队那个") {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(this.getName() + "..." + i);
}
}
};
Thread t2 = new Thread("普通线程") {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i == 5) {
try {
System.out.println("插队的来了!!!!");
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(this.getName() + "..." + i);
}
}
};
t1.start();
t2.start();
}
join是允许插队
join()是允许插队多久
join结束以后优先权又一样了
④礼让线程:
yield是让出当前cpu执行权,没实际作用。
⑤设置线程优先级:
java.lang.Thread | ||
---|---|---|
public static final int | MAX_PRIORITY | 10 |
public static final int | MIN_PRIORITY | 1 |
public static final int | NORM_PRIORITY | 5 |
常量值如上所示:
如果设置的优先级不在1-10,会报出如下错误:
Exception in thread "main" java.lang.IllegalArgumentException
案例代码:
public static void main(String[] args) {
Thread t1 = new Thread("第一个线程") {
public void run() {
for (int i = 0; i < 4000; i++) {
System.out.println(getName() + " " + i);
}
};
};
Thread t2 = new Thread("第二个线程") {
public void run() {
for (int i = 0; i < 4000; i++) {
System.out.println(getName() + " " + i);
}
};
};
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();
}
优先级高的线程,cpu会资源倾向
5.Synchronized关键字
什么情况下需要同步
当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
Synchronized锁代码块:
案例代码:
public class Demo1_Synchronized {
public static void main(String[] args) {
final Printer p = new Printer();
new Thread() {
@Override
public void run() {
while (true) {
p.print1();
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
p.print2();
}
}
}.start();
}
}
class Printer {
Lock lock = new Lock();
public void print1() {
synchronized (lock) {
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.println();
}
}
public void print2() {
synchronized (lock) {
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.println();
}
}
}
class Lock {
}
锁对象可以是任意的
匿名内部类调用局部变量,局部变量必须用final修饰
在锁代码块中,锁的对象是任意的,只是为了证明,谁抢到了锁谁就拥有执行权。
Synchronized锁方法
class Printer2 {
Demo d = new Demo();
//非静态的同步方法的锁对象是神马?
//答:非静态的同步方法的锁对象是this
//静态的同步方法的锁对象是什么?
//是该类的字节码对象
public static synchronized void print1() { //同步方法只需要在方法上加synchronized关键字即可
System.out.print("黑");
System.out.print("马");
System.out.print("程");
System.out.print("序");
System.out.print("员");
System.out.print("\r\n");
}
public static void print2() {
//synchronized(new Demo()) { //锁对象不能用匿名对象,因为匿名对象不是同一个对象
synchronized(Printer2.class) {
System.out.print("传");
System.out.print("智");
System.out.print("播");
System.out.print("客");
System.out.print("\r\n");
}
}
}
非静态的同步方法的锁对象是this,静态同步方法的锁对象是字节码对象,即类名.class
参考博客:https://blog.csdn.net/sinat_32588261/article/details/72880159
6.火车站买票实例的实现及分析
public class Demo3_Ticket {
/**
* 需求:铁路售票,一共100张,通过四个窗口卖完.
*/
public static void main(String[] args) {
new Ticket().start();
new Ticket().start();
new Ticket().start();
new Ticket().start();
}
}
class Ticket extends Thread {
private static int ticket = 100;
//private static Object obj = new Object(); //如果用引用数据类型成员变量当作锁对象,必须是静态的
public void run() {
while(true) {
synchronized(Ticket.class) {
if(ticket = 0) {
break;
}
try {
Thread.sleep(10); //线程1睡,线程2睡,线程3睡,线程4睡
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "...这是第" + ticket-- + "号票");
}
}
}
}
共享票,所以票是static。
当ticket=1,每个线程都沉睡,一起ticket--,这个时候会错过ticket=0,不断执行,出现负数,但是判断条件改为<=0又会显示出这是第0张票的错误,所以,while循环里面的代码需要同步。
同步锁因为上面属于不同对象,所以只能使用静态同步锁,一种是字节码文件,另一种是自定义一个静态对象,确保四个线程对象共享一个锁。
抢票实现Runnable接口的实现代码:
public class Demo4_Ticket {
public static void main(String[] args) {
MyTicket mt = new MyTicket();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
class MyTicket implements Runnable {
private static int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (MyTicket.class) {
if (ticket <= 0) {
System.out.println("票已售完");
break;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticket-- + "张票");
}
}
}
}
7.死锁及线程安全常用类回顾
public class Demo5_DeadLock {
private static String s1 = "筷子左";
private static String s2 = "筷子右";
public static void main(String[] args) {
new Thread("一号客人") {
@Override
public void run() {
while (true) {
synchronized (s1) {
System.out.println(Thread.currentThread().getName() + "已经取得左筷子" + "现在等待右筷子");
synchronized (s2) {
System.out.println(Thread.currentThread().getName() + "左右筷子都有了,可以开始吃饭");
}
}
}
}
}.start();
new Thread("二号客人") {
@Override
public void run() {
while (true) {
synchronized (s2) {
System.out.println(Thread.currentThread().getName() + "已经取得右筷子" + "现在等待左筷子");
synchronized (s1) {
System.out.println(Thread.currentThread().getName() + "左右筷子都有了,可以开始吃饭");
}
}
}
}
}.start();
}
}
输出结果如下:
一号客人已经取得左筷子现在等待右筷子
一号客人左右筷子都有了,可以开始吃饭
一号客人已经取得左筷子现在等待右筷子
一号客人左右筷子都有了,可以开始吃饭
一号客人已经取得左筷子现在等待右筷子
二号客人已经取得右筷子现在等待左筷子
多线程的时候,如果两个同步代码块嵌套,会出现一个时间点,两个线程资源冲突出现死锁现象。
线程安全
看源码:Vector,StringBuffer,Hashtable,Collections.synchroinzed(xxx)
Vector是线程安全的,ArrayList是线程不安全的
StringBuffer是线程安全的,StringBuilder是线程不安全的
Hashtable是线程安全的,HashMap是线程不安全的
8.单例设计模式
三种:
/*
* 饿汉式
class Singleton {
//1,私有构造方法,其他类不能访问该构造方法了
private Singleton(){}
//2,创建本类对象
private static Singleton s = new Singleton();
//3,对外提供公共的访问方法
public static Singleton getInstance() { //获取实例
return s;
}
}*/
/*
* 懒汉式,单例的延迟加载模式
*/
/*class Singleton {
//1,私有构造方法,其他类不能访问该构造方法了
private Singleton(){}
//2,声明一个引用
private static Singleton s ;
//3,对外提供公共的访问方法
public static Singleton getInstance() { //获取实例
if(s == null) {
//线程1等待,线程2等待
s = new Singleton();
}
return s;
}
}*/
class Singleton {
//1,私有构造方法,其他类不能访问该构造方法了
private Singleton(){}
//2,声明一个引用
public static final Singleton s = new Singleton();
}
饿汉式和懒汉式的区别
1,饿汉式是空间换时间,懒汉式是时间换空间
2,在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象
懒汉只会先声明,饿汉直接创建。
实际中使用饿汉式多,时间比空间更重要。
9.Runtime类和Time类
饿汉式构造单例对象
Process | exec(String command) 在单独的进程中执行指定的字符串命令。 |
Timer类
计时器
void | schedule(TimerTask task, Date time) 安排在指定的时间执行指定的任务。 |
void | schedule(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定延迟执行。 |
void | schedule(TimerTask task, long delay) 安排在指定延迟后执行指定的任务。 |
void | schedule(TimerTask task, long delay, long period) 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。 |
void | scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 安排指定的任务在指定的时间开始进行重复的固定速率执行。 |
void | scheduleAtFixedRate(TimerTask task, long delay, long period) 安排指定的任务在指定的延迟后开始进行重复的固定速率执行。 |
周期性执行方法,设定时间,设定间隔
10.进程之间有序通讯
两个线程之间的通信
public class Demo1_Notify {
public static void main(String[] args) {
Printer p = new Printer();
new Thread("第一线程") {
public void run() {
while (true) {
try {
p.print1();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread("第二线程") {
public void run() {
while (true) {
try {
p.print2();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer {
private int flag = 1;
public void print1() throws InterruptedException {
synchronized (this) {
if (flag != 1) {
this.wait();
}
System.out.print("国");
System.out.print("庆");
System.out.print("快");
System.out.print("乐");
System.out.println();
flag = 2;
this.notify();
}
}
public void print2() throws InterruptedException {
synchronized (this) {
if (flag != 2) {
this.wait();
}
System.out.print("恭");
System.out.print("喜");
System.out.print("发");
System.out.print("财");
System.out.println();
flag = 1;
this.notify();
}
}
}
设定一个控制变量,根据控制变量的值判断调用哪个线程。
多个线程之间的通信:
notify()方法是随机唤醒一个线程
notifyAll()方法是唤醒所有线程
JDK5之前无法唤醒指定的一个线程
如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
案例代码:
public class Demo2_NotifyAll {
public static void main(String[] args) {
Printer2 p = new Printer2();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print1();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print2();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print3();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer2 {
private int flag = 1;
public void print1() throws InterruptedException {
synchronized (this) {
while (flag != 1) {
this.wait();
}
System.out.print("国");
System.out.print("庆");
System.out.print("快");
System.out.print("乐");
System.out.println();
flag = 2;
this.notifyAll();
}
}
public void print2() throws InterruptedException {
synchronized (this) {
while (flag != 2) {
this.wait();
}
System.out.print("恭");
System.out.print("喜");
System.out.print("发");
System.out.print("财");
System.out.println();
flag = 3;
this.notifyAll();
}
}
public void print3() throws InterruptedException {
synchronized (this) {
while (flag != 3) {
this.wait();
}
System.out.print("大");
System.out.print("吉");
System.out.print("大");
System.out.print("利");
System.out.println();
flag = 1;
this.notifyAll();
}
}
}
多个线程进行通信,假设有三个的话,一个结束要通知其余所有,所以用notifyAll唤醒所有线程。
对已经进入wait状态的线程,如果正好notify到,不会重复判断,而是直接醒来,继续下一步,所以要把if判断语句改为while判断语句,可以再唤醒的时候再次进行判断,看是否符合线程启动条件。
但是多次唤醒所有的线程,对资源浪费很大,在JDK1.5以后就有了其他的方法。
11.JDk1.5新特性 互斥锁ReenTrantLock
案例代码:
public class Demo3_ReentrantLock {
public static void main(String[] args) {
Printer3 p = new Printer3();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print1();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print2();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print3();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer3 {
private ReentrantLock r = new ReentrantLock();
private Condition c1 = r.newCondition();
private Condition c2 = r.newCondition();
private Condition c3 = r.newCondition();
private int flag = 1;
public void print1() throws InterruptedException {
r.lock();
while (flag != 1) {
c1.await();
}
System.out.print("国");
System.out.print("庆");
System.out.print("快");
System.out.print("乐");
System.out.println();
flag = 2;
c2.signal();
r.unlock();
}
public void print2() throws InterruptedException {
r.lock();
while (flag != 2) {
c2.await();
}
System.out.print("恭");
System.out.print("喜");
System.out.print("发");
System.out.print("财");
System.out.println();
flag = 3;
c3.signal();
r.unlock();
}
public void print3() throws InterruptedException {
r.lock();
while (flag != 3) {
c3.await();
}
System.out.print("大");
System.out.print("吉");
System.out.print("大");
System.out.print("利");
System.out.println();
flag = 1;
c1.signal();
r.unlock();
}
}
掌握的方法:
ReentrantLock
void | lock() 获取锁。 |
void | unlock() 试图释放此锁。 |
Condition | newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。 |
取代synchronized
Condition
void | await() 造成当前线程在接到信号或被中断之前一直处于等待状态。 |
void | signal() 唤醒一个等待线程。 |
用于取代wait和notify
12.线程的等待唤醒面试题
1.在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法
2.为什么wait方法和notify方法定义在Object这类中?
因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中
3,sleep方法和wait方法的区别?
①sleep方法必须传入参数,参数就是时间,时间到了自动醒来
wait方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
②sleep方法在同步函数或同步代码块中,不释放锁,带着锁一起休息
wait方法在同步函数或者同步代码块中,释放锁,是放开锁再休息
13.线程组ThreadGroup
案例代码:
public class Demo4_ThreadGroup {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
ThreadGroup tg = new ThreadGroup("新的线程组");
Thread t1 = new Thread(mr, "张三");
Thread t2 = new Thread(mr, "李四");
Thread t3 = new Thread(tg, mr, "王五");
String name1 = t1.getThreadGroup().getName();
String name2 = t2.getThreadGroup().getName();
String name3 = t3.getThreadGroup().getName();
System.out.println(name1);
System.out.println(name2);
System.out.println(name3);
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + " ." + i);
}
}
}
掌握的方法:
ThreadGroup的getname方法
Thread的两种参数有ThreadGroup的构造方法
14.线程的五种状态:
15.线程池ExecutorService
JDk5开始Java内置线程池
public class Demo5_Executors {
public static void main(String[] args) {
ExecutorService nftp = Executors.newFixedThreadPool(3);
nftp.submit(new MyRunnable());
nftp.submit(new MyRunnable());
nftp.submit(new MyRunnable());
nftp.shutdown();
}
}
创建指定大小的线程池,提交Runnable任务用于执行,shutdown是用于关闭线程池。
多线程的三种实现方式:
第一种类继承Thread
第二种实现Runnable接口
第三种ExecutorService,Future接口,子类实现Callable计算接口
案例代码:
public class Demo6_Callable {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService nftp = Executors.newFixedThreadPool(2);
Future<Integer> s1 = nftp.submit(new MyCallable(100));
Future<Integer> s2 = nftp.submit(new MyCallable(50));
System.out.println(s1.get());
System.out.println(s2.get());
nftp.shutdown();
}
}
class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= num; i++) {
sum += i;
}
return sum;
}
}
16.工厂模式
简单工厂模式
案例代码:
public static Animal creatAnimal(String name) {
if ("cat".equals(name)) {
return new Cat();
} else if ("dog".equals(name)) {
return new Dog();
} else {
return null;
}
}
* 动物抽象类:public abstract Animal { public abstract void eat(); }
* 具体狗类:public class Dog extends Animal {}
* 具体猫类:public class Cat extends Animal {}
按照匹配的名称进行创建,cat和dog分别是animal的子类。但是如果输入的内容不是直接存在的,就不会创建,所以他是静态的。
而且,新增种类就要修改代码,很麻烦。
工厂模式:
public interface Factory {
public Animal createAnimal();
}
public class Dogfactory implements Factory {
@Override
public Animal createAnimal() {
return new Dog();
}
}
public class Catfactory implements Factory {
@Override
public Animal createAnimal() {
return new Cat();
}
}
public static void main(String[] args) {
Dogfactory df = new Dogfactory();
Dog d = (Dog) df.createAnimal();
d.eat();
Catfactory cf = new Catfactory();
Cat c = (Cat) cf.createAnimal();
c.eat();
}
* 动物抽象类:public abstract Animal { public abstract void eat(); }
* 具体狗类:public class Dog extends Animal {}
* 具体猫类:public class Cat extends Animal {}
接口工厂,生产Animal抽象类,实例工厂重写方法,返回具体的对象,新增一个种类新添加一个具体的工厂类。
创建对象的时候先创建工厂,再创建对象。
17.GUI
图形用户接口:Frame类为主要
GUI(如何创建一个窗口并显示)
GUI(布局管理器)
* FlowLayout(流式布局管理器)
* 从左到右的顺序排列。
* Panel默认的布局管理器。
* BorderLayout(边界布局管理器)
* 东,南,西,北,中
* Frame默认的布局管理器。(麻将桌)
* GridLayout(网格布局管理器)
* 规则的矩阵
* CardLayout(卡片布局管理器)
* 选项卡(eclipse操作界面)
* GridBagLayout(网格包布局管理器)
* 非规则的矩阵(计算器的按钮)
GUI(窗体监听)
GUI聊天窗口案例代码:
public class Demo1_Frame {
public static void main(String[] args) {
Frame f = new Frame("我的第一个窗口");
// 设置窗体大小
f.setSize(400, 600);
// 设置窗体位置
f.setLocation(500, 50);
// 设置图标
f.setIconImage(Toolkit.getDefaultToolkit().createImage("qq.png"));
// 创建按钮并加入到窗体中
Button b1 = new Button("第一个按钮");
Button b2 = new Button("第二个按钮");
f.add(b1);
f.add(b2);
// 设置布局管理器
f.setLayout(new FlowLayout());
// 设置窗口监听
// f.addWindowListener(new
// MyWindowListener());需要重写的功能太多,放弃,使用类继承WindowAdapter来实现功能,因为只调用一个
// 功能,所以直接匿名内部类
f.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
// 鼠标监听
b1.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent e) {
System.exit(0);
}
});
b1.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
// System.out.println(e.getKeyCode());
// 设置鼠标事件为点击空格退出程序
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
System.exit(0);
}
}
});
// 动作监听,应用场景就是暂停和播放视频
b2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
// 设置窗口可见
f.setVisible(true);
}
}
事件: 用户的一个操作(点鼠标,按键盘)
事件源: 被操作的组件(按钮等)
监听器: 一个自定义类的对象, 实现了监听器接口, 包含事件处理方法,把监听器添加在事件源上, 当事件发生的时候虚拟机就会自动调用监听器中的事件处理方法
18.设计模式(适配器设计模式)
什么是适配器
在使用监听器的时候, 需要定义一个类事件监听器接口.
通常接口中有多个方法, 而程序中不一定所有的都用到, 但又必须重写, 这很繁琐.适配器简化了这些操作, 我们定义监听器时
只要继承适配器, 然后重写需要的方法即可.
适配器原理
适配器就是一个类, 实现了监听器接口, 所有抽象方法都重写了, 但是方法全是空的.
适配器类需要定义成抽象的,因为创建该类对象,调用空方法是没有意义的
目的就是为了简化程序员的操作, 定义监听器时继承适配器, 只重写需要的方法就可以了.
案例代码:
interface HeShang {
public void daZuo();
public void nianJing();
public void zhuangZhong();
public void xiWu();
}
// 因为不想让其他类创建本类对象,因为创建没有意义,发放都是空的,所以声明为抽象
abstract class Haohan implements HeShang {
@Override
public void daZuo() {
}
@Override
public void nianJing() {
}
@Override
public void zhuangZhong() {
}
@Override
public void xiWu() {
}
}
class LuZhiShen extends Haohan {
public void xiWu() {
System.out.println("倒拔垂杨柳");
System.out.println("拳打镇关西");
System.out.println("大闹野猪林");
System.out.println("......");
}
}
在本例中,适配器是HaoHan类,监听器是HeShang类
19.网络编程三要素
IP:每个设备在网络中的唯一标识
端口号:每个程序在设备上的唯一标识
常用端口介绍:mysql 3306 oracle 1521 web 80 tomcat 8080
协议:数据交换的规则
UDP:短信。面向无连接,速度快,不安全,不区分客户端和服务端
TCP:面向连接,速度略低,安全,分为客户端和服务端
三次握手:客户端请求,服务端响应,传输数据
Socket:网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
通信两端都有socket
数据在两个客户端之间通过io流进行传输
Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和port
20.UDP传输原理
UDP传输原理:
发送Send
* 创建DatagramSocket, 随机端口号
* 创建DatagramPacket, 指定数据, 长度, 地址, 端口
* 使用DatagramSocket发送DatagramPacket
* 关闭DatagramSocket
接收Receive
* 创建DatagramSocket, 指定端口号
* 创建DatagramPacket, 指定数组, 长度
* 使用DatagramSocket接收DatagramPacket
* 关闭DatagramSocket
* 从DatagramPacket中获取数据
接收方获取ip和端口号
* String ip = packet.getAddress().getHostAddress();
* int port = packet.getPort();
案例代码:
public class Demo3_MoreThread {
/**
* @param args
*/
public static void main(String[] args) {
new Receive().start();
new Send().start();
}
}
class Receive extends Thread {
public void run() {
try {
DatagramSocket socket = new DatagramSocket(6666); //创建Socket相当于创建码头
DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);//创建Packet相当于创建集装箱
while(true) {
socket.receive(packet); //接货,接收数据
byte[] arr = packet.getData(); //获取数据
int len = packet.getLength(); //获取有效的字节个数
String ip = packet.getAddress().getHostAddress(); //获取ip地址
int port = packet.getPort(); //获取端口号
System.out.println(ip + ":" + port + ":" + new String(arr,0,len));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Send extends Thread {
public void run() {
try {
Scanner sc = new Scanner(System.in); //创建键盘录入对象
DatagramSocket socket = new DatagramSocket(); //创建Socket相当于创建码头
while(true) {
String line = sc.nextLine(); //获取键盘录入的字符串
if("quit".equals(line)) {
break;
}
DatagramPacket packet = //创建Packet相当于集装箱
new DatagramPacket(line.getBytes(), line.getBytes().length, InetAddress.getByName("127.0.0.1"), 6666);
socket.send(packet); //发货,将数据发出去
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
21.UDP和GUI的聊天室
package com.heima.socket;
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Demo4_GUIChat extends Frame {
private TextField tf;
private Button send;
private Button log;
private Button clear;
private Button shake;
private TextArea viewText;
private TextArea sendText;
private DatagramSocket socket;
private BufferedWriter bw;
/**
* @param args
* GUI聊天
*/
public Demo4_GUIChat() {
init();
southPanel();
centerPanel();
event();
}
public void event() {
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
try {
socket.close();
bw.close();
} catch (IOException e1) {
e1.printStackTrace();
}
System.exit(0);
}
});
send.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
send();
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
log.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
logFile();
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
clear.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
viewText.setText("");
}
});
shake.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
send(new byte[]{-1},tf.getText());
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
sendText.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
//if(e.getKeyCode() == KeyEvent.VK_ENTER && e.isControlDown()) { //isControlDown ctrl是否被按下
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
try {
send();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
});
}
private void shake() {
int x = this.getLocation().x; //获取横坐标位置
int y = this.getLocation().y; //获取纵坐标位置
for(int i = 0; i < 20; i++) {
try {
this.setLocation(x + 20, y + 20);
Thread.sleep(20);
this.setLocation(x + 20, y - 20);
Thread.sleep(20);
this.setLocation(x - 20, y + 20);
Thread.sleep(20);
this.setLocation(x - 20, y - 20);
Thread.sleep(20);
this.setLocation(x, y);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void logFile() throws IOException {
bw.flush(); //刷新缓冲区
FileInputStream fis = new FileInputStream("config.txt");
ByteArrayOutputStream baos = new ByteArrayOutputStream(); //在内存中创建缓冲区
int len;
byte[] arr = new byte[8192];
while((len = fis.read(arr)) != -1) {
baos.write(arr, 0, len);
}
String str = baos.toString(); //将内存中的内容转换成了字符串
viewText.setText(str);
fis.close();
}
private void send(byte[] arr, String ip) throws IOException {
DatagramPacket packet =
new DatagramPacket(arr, arr.length, InetAddress.getByName(ip), 9999);
socket.send(packet); //发送数据
}
private void send() throws IOException {
String message = sendText.getText(); //获取发送区域的内容
String ip = tf.getText(); //获取ip地址;
ip = ip.trim().length() == 0 ? "255.255.255.255" : ip;
send(message.getBytes(),ip);
String time = getCurrentTime(); //获取当前时间
String str = time + " 我对:" + (ip.equals("255.255.255.255") ? "所有人" : ip) + "说\r\n" + message + "\r\n\r\n"; //alt + shift + l 抽取局部变量
viewText.append(str); //将信息添加到显示区域中
bw.write(str); //将信息写到数据库中
sendText.setText("");
}
private String getCurrentTime() {
Date d = new Date(); //创建当前日期对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
return sdf.format(d); //将时间格式化
}
public void centerPanel() {
Panel center = new Panel(); //创建中间的Panel
viewText = new TextArea();
sendText = new TextArea(5,1);
center.setLayout(new BorderLayout()); //设置为边界布局管理器
center.add(sendText,BorderLayout.SOUTH); //发送的文本区域放在南边
center.add(viewText,BorderLayout.CENTER); //显示区域放在中间
viewText.setEditable(false); //设置不可以编辑
viewText.setBackground(Color.WHITE); //设置背景颜色
sendText.setFont(new Font("xxx", Font.PLAIN, 15));
viewText.setFont(new Font("xxx", Font.PLAIN, 15));
this.add(center,BorderLayout.CENTER);
}
public void southPanel() {
Panel south = new Panel(); //创建南边的Panel
tf = new TextField(15);
tf.setText("127.0.0.1");
send = new Button("发 送");
log = new Button("记 录");
clear = new Button("清 屏");
shake = new Button("震 动");
south.add(tf);
south.add(send);
south.add(log);
south.add(clear);
south.add(shake);
this.add(south,BorderLayout.SOUTH); //将Panel放在Frame的南边
}
public void init() {
this.setLocation(500, 50);
this.setSize(400, 600);
new Receive().start();
try {
socket = new DatagramSocket();
bw = new BufferedWriter(new FileWriter("config.txt",true)); //需要在尾部追加
} catch (Exception e) {
e.printStackTrace();
}
this.setVisible(true);
}
private class Receive extends Thread { //接收和发送需要同时执行,所以定义成多线程的
public void run() {
try {
DatagramSocket socket = new DatagramSocket(9999);
DatagramPacket packet = new DatagramPacket(new byte[8192], 8192);
while(true) {
socket.receive(packet); //接收信息
byte[] arr = packet.getData(); //获取字节数据
int len = packet.getLength(); //获取有效的字节数据
if(arr[0] == -1 && len == 1) { //如果发过来的数组第一个存储的值是-1,并且数组长度是1
shake(); //调用震动方法
continue; //终止本次循环,继续下次循环,因为震动后不需要执行下面的代码
}
String message = new String(arr,0,len); //转换成字符串
String time = getCurrentTime(); //获取当前时间
String ip = packet.getAddress().getHostAddress(); //获取ip地址
String str = time + " " + ip + " 对我说:\r\n" + message + "\r\n\r\n";
viewText.append(str);
bw.write(str);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Demo4_GUIChat();
}
}
22.TCP传输
三次握手:
请求,响应,传输
过程:
1.客户端
* 创建Socket连接服务端(指定ip地址,端口号)通过ip地址找对应的服务器
* 调用Socket的getInputStream()和getOutputStream()方法获取和服务端相连的IO流
* 输入流可以读取服务端输出流写出的数据
* 输出流可以写出数据到服务端的输入流
2.服务端
* 创建ServerSocket(需要指定端口号)
* 调用ServerSocket的accept()方法接收一个客户端请求,得到一个Socket
* 调用Socket的getInputStream()和getOutputStream()方法获取和客户端相连的IO流
* 输入流可以读取客户端输出流写出的数据
* 输出流可以写出数据到客户端的输入流
案例代码
Client:
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 12345);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream ps = new PrintStream(socket.getOutputStream());
System.out.println(br.readLine());
ps.println("我要报名啊");
System.out.println(br.readLine());
ps.println("能不能给个机会?");
socket.close();
}
Server:
public static void main(String[] args) throws IOException {
// demo1();
ServerSocket serverSocket = new ServerSocket(12345);
while (true) {
Socket socket = serverSocket.accept();
new Thread() {
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("欢迎咨询!");
System.out.println(br.readLine());
ps.println("不好意思,名额已满~");
System.out.println(br.readLine());
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
}
23.TCP传输案例
一。键盘录入字符串,通过服务端获取字符串的反转
Client:
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
// 创建客户端,指定ip地址和端口号
Socket socket = new Socket("127.0.0.1", 12345);
// 获取输入输出流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream ps = new PrintStream(socket.getOutputStream());
// 将键盘录入的字符串发送到服务端
ps.println(sc.nextLine());
System.out.println(br.readLine());
socket.close();
}
Server:
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("服务器启动,绑定的端口为12345");
while (true) {
Socket socket = serverSocket.accept();
new Thread() {
@Override
public void run() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream ps = new PrintStream(socket.getOutputStream());
String message = br.readLine();
message = new StringBuilder(message).reverse().toString();
ps.println(message);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
}
二。从客户端向服务端上传文件
Client:
public static void main(String[] args) throws IOException {
// 1.提示输入要上传的文件路径, 验证路径是否存在以及是否是文件夹
File file = getFile();
// 2.发送文件名到服务端
Socket socket = new Socket("127.0.0.1", 12345);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println(file.getName());
// 6.接收结果, 如果存在给予提示, 程序直接退出
String result = br.readLine();
if ("存在".equals(result)) {
System.out.println("文件已存在,请勿重复上传");
socket.close();
return;
}
// 7.如果不存在, 定义FileInputStream读取文件, 写出到网络
FileInputStream fis = new FileInputStream(file);
byte[] arr = new byte[8192];
int len;
while ((len = fis.read(arr)) != -1) {
ps.write(arr, 0, len);
}
fis.close();
socket.close();
}
@SuppressWarnings("resource")
public static File getFile() {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个文件路径:");
while (true) {
File file = new File(sc.nextLine());
if (!file.exists()) {
System.out.println("您输入的文件路径不存在,请重新输入!");
} else if (file.isDirectory()) {
System.out.println("您输入的文件是文件夹,无法上传,请重新输入!");
} else {
return file;
}
}
}
Server:
public static void main(String[] args) throws IOException {
// 3,建立多线程的服务器
ServerSocket server = new ServerSocket(12345);
// 4.读取文件名
while (true) {
Socket socket = server.accept();
new Thread() {
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
PrintStream ps = new PrintStream(socket.getOutputStream());
// 5.判断文件是否存在, 将结果发回客户端
String name = br.readLine();
File dir = new File("Update.txt");
dir.mkdirs();
File file = new File(dir, name);
if (file.exists()) {
ps.println("存在");
socket.close();
return;
} else {
ps.println("不存在");
}
// 8.定义FileOutputStream, 从网络读取数据, 存储到本地
FileOutputStream fos = new FileOutputStream(file);
byte[] arr = new byte[8192];
int len;
while ((len = is.read(arr)) != -1) {
fos.write(arr, 0, len);
}
fos.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}.start();
}
}