个人曾经学习java的一些记录,仅供参考
java学习-day07
Socket(套接字)
Socket是由一个客户端和一个服务端组成。
TCP: 安全的协议,在通信的过程中,对方必须应答后才能通信(打电话)
UDP:广播的协议,只管发送数据,不管对方是否收到(短信、电台)
服务端
1、绑定IP地址 : 使用默认就可以。
2、指定监听的端口号:需要指定一个未使用的PORT
import java.util.Scanner;
public class ScannerUtil {
private static Scanner sc = new Scanner(System.in);
public static String PUT(String message) {
System.out.println(message);
String str = sc.next();
return str;
}
public static String PUT() {
System.out.println("请输入:");
String str = sc.next();
return str;
}
}
服务端代码:
public class MyServer {
public static void main(String[] args) {
int port = 9999;
ServerSocket server = null;
Socket socket = null;
try {
server = new ServerSocket(port);//绑定端口
//开始监听 9999
while (true) {
System.out.println("开始监听....");
socket = server.accept(); //socket 客户端和服务端之间通信的数据传输对象,数据都封装 socket 对象中
System.out.println("接受到请求.....");
//接受数据
InputStream in = socket.getInputStream();
InputStreamReader i2r = new InputStreamReader(in);
BufferedReader reader = new BufferedReader(i2r);
String msg = reader.readLine();
System.out.println(msg);
// 返回一个数据: 服务器向客户端写数据
OutputStream out = socket.getOutputStream();
OutputStreamWriter o2w = new OutputStreamWriter(out);
BufferedWriter writer = new BufferedWriter(o2w);
writer.write(ScannerUtil.PUT());
writer.newLine();
writer.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端
指定连接的IP地址和端口号
public class MyClient {
public static void main(String[] args) {
Socket socket = null;
try {
while (true){
//创建一个socket对象用来连接服务端,并放服务端发送数据
socket = new Socket("localhost",9999);
// 发送数据
OutputStream out = socket.getOutputStream();
OutputStreamWriter o2w = new OutputStreamWriter(out);
BufferedWriter writer = new BufferedWriter(o2w);
writer.write(ScannerUtil.PUT());
writer.newLine();
writer.flush();
// 接受收据
InputStream in = socket.getInputStream();
InputStreamReader i2r = new InputStreamReader(in);
BufferedReader reader = new BufferedReader(i2r);
String msg = reader.readLine();
System.out.println(msg);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
多线程
线程和进程
进程:单一顺序控制流(QQ,LOL)
线程:单一顺序控制流,是一个轻量级的进程
因为进程之间的通信,开销比较打,而我们在实际的开发过程中,很多场景需要通过线程来完成,线程的开销比较小。
如何创建多线程
创建线程和启动
public class Test {
public static void main(String[] args) {
// 程序在启动的时候会启动一个主线程main:老大
//获取线程名称
System.out.println(Thread.currentThread().getName());
// 安排小弟做事:创建了一个子线程 自定义线程
Runnable r = new MyRun();//创建了一个线程对象:小弟
// 向小弟发送命令:去收保护费 => 启动线程
// Thread 类提供了对线程的API
Thread t = new Thread(r);
// 向JVM发送了一个启动线程的指令
t.start(); // 能看到 老大打王者去了... 在小弟收保护费之前执行 说明不是顺序的
System.out.println("老大打王者去了...");
}
}
// 生命了一个线程类
class MyRun implements Runnable{
// run 方法程序员不能显示调用: JVM调用
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":小弟收保护费");
}
}
总结:
1、当程序启动时候JVM会启动一个主线程:main
2、创建线程对象需要实现Runnable接口,重写run方法,在run方法中执行需要执行的业务
3、启动线程不是run方法,而是调用Thread类的start方法。Thread才是提供了对线程的操作
4、以上代码 老大打王者 可能会在小弟收保护费之前就执行了。说明不是顺序的
创建多线程
需要多个小弟收保护费:创建多线程
public class Test {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"开始工作");
// 安排多个小弟(3)去收保护费
Runnable r1 = new MyRunnable();
Runnable r2 = new MyRunnable();
Runnable r3 = new MyRunnable();
// 通过Thread 去启动线程
Thread t1 = new Thread(r1,"小弟A"); // name是自定义线程名字
Thread t2 = new Thread(r2,"小弟B");
Thread t3 = new Thread(r3,"小弟C");
// 分别启动3个小弟线程
t1.start();
t2.start();
t3.start();
System.out.println(Thread.currentThread().getName()+"工作结束");
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"收保护费");
}
}
总结:
1、多个线程之前的执行顺序,程序员不可控。
2、要执行多线程,就需要创建多个线程对象。
线程的生命周期
public class Test {
public static void main(String[] args) {
// 生
Runnable r = () -> {
System.out.println(Thread.currentThread().getName()+"执行任务");
// 完成任务会自动销毁 死
};
Thread t = new Thread(r,"子线程");
// 启动线程
t.start(); // 可运行 -> 运行
try {
// 让当前线程休眠10毫秒,确保子线程执行完毕
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再启动
t.start();
}
}
总结:
线程的状态转换是线程控制的基础。线程状态总的可分为五大状态:分别是生、死、可运行、运行、等待/阻塞。
1、新状态:线程对象已经创建,还没有在其上调用 start()方法。
2、可运行状态: 当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当 start()方法调用
时,线程首先迚入可运行状态。在线程运行乊后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。
3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程迚入运行状态
的唯一一种方式。
4、等待/阻塞/睡眠状态 : 这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程
仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。
5、死亡态:当线程的 run()方法完成时就认为它死去。 这个线程对象也许是活的,但是,它已经丌是一个单独执行
的线程。线程一旦死亡,就丌能复生。 如果在一个死去的线程上调用 start()方法,会抛出
java.lang.IllegalThreadStateException 异常。
PS:程序不可以主动去销毁线程,必须让线程自然销毁。否则会发送不可控的安全问题。
线程的调度
sleep
sleep: 休眠- 让出线程资源也就是CPU的资源,一定时间后,再次获取CPU资源
为了防止某一个线程长期占用资源,导致启动线程不能完成任务。
public class Test {
public static void main(String[] args) {
Thread t1 = new MyThread("A同学");
Thread t2 = new MyThread("B同学");
t1.start();
t2.start();
}
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"打电话"+i);
try {
// 强制当前线程让出CPU 1毫秒
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
yield: 让出线程资源也就是CPU的资源,一个时间芯片。
public class Test {
public static void main(String[] args) {
Thread t1 = new MyThread("A同学");
Thread t2 = new MyThread("B同学");
t1.start();
t2.start();
}
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"打电话"+i);
Thread.yield();
}
}
}
join: 加入一个线程,等待加入的线程执行完后再执行
public class Test {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1("线程A");
Thread t2 = new MyThread2("线程B");
t1.setTarget(t2);
t1.start();
}
}
class MyThread1 extends Thread {
private Thread target;
// 可以通过这个构造函数执行当前线程的名称
public MyThread1(String name) {
super(name);
}
// 调用set方法传递线程对象(参数)
public void setTarget(Thread target) {
this.target = target;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
if (i == 5) {
try {
// 加入了线程2 线程1 会等待线程2执行后重新执行 => 插队
target.start();
target.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyThread2 extends Thread {
// 可以通过这个构造函数执行当前线程的名称
public MyThread2(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zUJFQLZY-1630225372879)(imgs/952KE2SEPV3_8IMN$OVB06.png)]
死锁
/**
* 线程死锁
* 2线程2把锁(2个对象)以上才可能发生死锁
* 一个线程希望获取锁A后再去获取锁B,另外一个线程希望获取锁B后再去获取锁A,这个时候有可能发生死锁
* (线程1获取了锁A的同时线程2获取了锁B这个时候会发生死锁)
*/
public class Test {
public static void main(String[] args) {
new Thread("线程1") {
@Override
public void run() {
synchronized ("A") {
System.out.println(Thread.currentThread().getName() + "获得锁A");
try {
// 等待线程2获取锁B
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized ("B") {
System.out.println(Thread.currentThread().getName() + "获得锁B");
}
}
}
}.start();
new Thread("线程2") {
@Override
public void run() {
synchronized ("B") {
System.out.println(Thread.currentThread().getName() + "获得锁B");
synchronized ("A") {
System.out.println(Thread.currentThread().getName() + "获得锁A");
}
}
}
}.start();
}
}
如何避免死锁???
- 加锁顺序(线程按照一定的顺序加锁)
- 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
- 死锁检测(用JDK的工具)
Lock
public class Test {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
MyRunnable1 r1 = new MyRunnable1();
r1.setLock(lock);
MyRunnable2 r2 = new MyRunnable2();
r2.setLock(lock);
Thread t1 = new Thread(r1, "线程A");
Thread t2 = new Thread(r2, "线程B");
t1.start();
t2.start();
}
}
class MyRunnable1 implements Runnable {
private Lock lock;
public void setLock(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
// 加锁
lock.lock();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 释放锁
lock.unlock();
}
}
class MyRunnable2 implements Runnable {
private Lock lock;
public void setLock(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
lock.lock(); // 加锁
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.unlock();// 释放锁
}
}
Lock和synchronized的区别
synchronized:当一个线程执行到synchronized的代码块或者方法的时候会自动持有该对象的锁。这种锁是隐式的。
Lock:是一个类,JDK提供的工具类,锁是显示的。更方便操作
线程池:
线程池是通过池化的技术,将已经创建的线程保存到一个集合中,当需要使用线程来完成任务的时候,直接从线程池中获取空闲的线程执行任务,执行完任务后将线程归还线程池。可以减少多线程创建的开销。
public class Test {
public static void main(String[] args) {
// 5个任务
MyRunnable r1 = new MyRunnable();
MyRunnable r2 = new MyRunnable();
MyRunnable r3 = new MyRunnable();
MyRunnable r4 = new MyRunnable();
MyRunnable r5 = new MyRunnable();
Set<Runnable> set = new HashSet<>();
set.add(r1);
set.add(r2);
set.add(r3);
set.add(r4);
set.add(r5);
// 线程池
// 1.带缓冲的线程池
// ExecutorService service = Executors.newCachedThreadPool();
// 2.固定大小的线程池
ExecutorService service = Executors.newFixedThreadPool(3);
Iterator<Runnable> its = set.iterator();
while (its.hasNext()) {
service.submit(its.next());
}
// 关闭线程池
service.shutdown();
}
}
// 任务类
class MyRunnable implements Runnable {
@Override
public void run() {
// 业务
System.out.println(Thread.currentThread().getName()+":do...");
}
}
Callable
/**
* 创建线程的方法:
* 1.实现Runnable接口
* 2.继承Thread类
* 3.实现Callable接口
* 4.线程池
*/
public class Test {
public static void main(String[] args) throws Exception {
MyCall call = new MyCall();
ExecutorService service = Executors.newFixedThreadPool(2);
Future<String> submit = service.submit(call);
String result = submit.get();
System.out.println("返回值:" + result);
service.shutdown();
}
}
class MyCall implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("hello Callable");
return "hello Callable";
}
}
定时任务
/**
* 定时任务
*/
public class Test {
public static void main(String[] args) {
// 1. Timer : 任务调度类
// 2. TimerTask : 任务类接口,这个接口是 Runnable 的子类
// 定义任务
TimerTask task = new Task();
// 调度任务
Timer timer = new Timer();
timer.schedule(task,DateUtil.parse("2019-11-30 10:00:00"),1000);
}
}
// 任务类
class Task extends TimerTask{
@Override
public void run() {
// 打印当前系统时间
System.out.println(new Date());
}
}