javaSE(十)

版权声明:博主原创,转发请私信。 https://blog.csdn.net/YooFale/article/details/83032088

1.线程

线程是程序的执行路径,一个进程包含多个线程

多线程并发可以提高程序运行效率,同时完成多个工作。

实例:

      服务器处理多个客户端请求,迅雷多线程下载,多人视频,一个cpu处理多个事务。

并行:甲乙任务同时进行(需要多核CPU)

并发:甲乙快速交替运行。

java命令启动JVM,启动程序,意味着启动进程,进程启动一个主线程,主线程调用某个类的main方法。

至少启动main和垃圾回收机制,所以JVM是多线程的。

扫描二维码关注公众号,回复: 3739974 查看本文章

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();
		}

	}

猜你喜欢

转载自blog.csdn.net/YooFale/article/details/83032088