黑马全套Java教程(九):网络编程(一)

简介: 黑马全套Java教程(九):网络编程(一)

本博客开始记录黑马教学的Java入门新视频,从d168开始,博客只做代码记录,方便后续快速复习,视频链接

36 多线程

36.1 多线程的创建

  • 方式一:继承Thread类
  • 方式二:实现Runnable接口
  • 方式三:JDK 5.0新增,实现Callable接口

例:方法一

  1. 继承Thread类
  2. 重写run方法
  3. 创建线程对象
  4. 调用start()方法启动
package d1_create;
/**
 * 目标:多线程的创建爱你方式一:继承Thread类实现
 * */
public class ThreadDemo1 {
    public static void main(String[] args) {
        //3. new一个新线程对象
        Thread t = new MyThread();
        // 4. 调用start方法启动线程(执行的还是run方法)
        t.start();
        for(int i = 0; i < 5; i++){
            System.out.println("主线程执行输出:" + i);
        }
    }
}
//1. 定义一个线程类继承Thread类
class MyThread extends Thread{
    // 2. 重写run方法,里面是定义线程以后要干啥
    @Override
    public void run(){
        for(int i = 0; i < 5; i++){
            System.out.println("子线程执行输出:" + i);
        }
    }
}

为什么调用run,而不是调用start:run会当成单线程执行

优点:代码简单

缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展


方法二:实现runnable接口

  1. 定义一个线程人物类MyRunnable实现接口,重写run()方法
  2. 创建MyRunnable对象
  3. 把MyRunnable任务对象交给Thread线程对象处理
  4. 调用线程对象的start()方法启动线程
package d1_create;
/*
* 目标:学会线程的创建方式二
* */
public class ThreadDemo2 {
    public static void main(String[] args) {
        // 3. 创建一个任务对象
        Runnable target = new MyRunnable();
        //4. 把任务对象交给Thread处理
        Thread t = new Thread(target);
        //5. 启动线程
        t.start();
        for(int i = 0; i < 10; i++){
            System.out.println("主线程执行输出:" + i);
        }
    }
}
//1. 定义一个线程任务类,实现Runnable接口
class MyRunnable implements Runnable{
    // 2. 重写run方法,定义线程的执行任务
    @Override
    public void run(){
        for(int i = 0; i < 10; i++){
            System.out.println("子线程执行输出:" + i);
        }
    }
}

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强

缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的

package d1_create;
/*
 * 目标:学会线程的创建方式二(匿名内部类方式实现,语法形式)
 * */
public class ThreadDemo2Other {
    public static void main(String[] args) {
        // 3. 创建一个任务对象
        Runnable target = new Runnable(){
            @Override
            public void run(){
                for(int i  = 0; i < 10; i++){
                    System.out.println("子线程执行输出:" + i);
                }
            }
        };
        //4. 把任务对象交给Thread处理
        Thread t = new Thread(target);
        //5. 启动线程
        t.start();
        for(int i = 0; i < 10; i++){
            System.out.println("主线程执行输出:" + i);
        }
    }
}

其他写法:

package d1_create;
/*
 * 目标:学会线程的创建方式二(匿名内部类方式实现,语法形式)
 * */
public class ThreadDemo2Other {
    public static void main(String[] args) {
        // 3. 创建一个任务对象
        Runnable target = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程1执行输出:" + i);
                }
            }
        };
        Thread t = new Thread(target);
        t.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程2执行输出:" + i);
                }
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("子线程3执行输出:" + i);
            }
        }).start();
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

前两种线程创建方式都存在一个问题:

  1. 它们重写的run方法均不能直接返回结果
  2. 不适合需要返回线程执行结果的业务场景

方案三:利用Callable、FutureTask接口实现

  1. 得到任务对象
  2. 把线程任务对象交给Thread处理
  3. 调用Thread的start方法启动线程,执行任务
  4. 线程执行完毕后,通过FutureTask的get方法去获取任务执行的结果
package d1_create;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/*
* 目标:方式三,实现Callable接口,结合FutureTask完成
* */
public class ThreadDemo3 {
    public static void main(String[] args) {
        // 3. 创建Callable任务对象
        Callable<String> call = new MyCallable(100);
        // 4. 把Callable任务对象  交给  FutureTask对象
        // FutureTask对象的作用1:是Runnable的对象(实现了Runnable接口),可以交给Thread了
        // FUtureTask对象的作用2:可以在线程执行完毕之后通过调用其get方法得到线程执行完毕的结果
        FutureTask<String> f1 = new FutureTask<>(call);
        // 5. 交给线程处理
        Thread t1 = new Thread(f1);
        // 6. 启动线程
        t1.start();
        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();
        try {
            //如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果
            String rs1 = f1.get();
            System.out.println("第一个结果:" + rs1);
        } catch (Exception e){
            e.printStackTrace();
        }
        try {
            //如果f2任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果
            String rs2 = f2.get();
            System.out.println("第一个结果:" + rs2);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
//1. 定义一个人物类,实现Callable接口  应该声明线程任务执行完毕后的结果的数据类型
class MyCallable implements Callable<String>{
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }
    //2. 重写call方法
    @Override
    public String call() throws Exception{
        int sum = 0;
        for (int i = 0; i <= n; i++) {
            sum += i;
        }
        return "子线程的结果是:" + sum;
    }
}

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。可以在线程执行完毕之后获取线程执行的结果。

缺点:编程复杂


36.2 Thread的常用方法

MyThread.java

package d1_create;
public class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出:" + i);
        }
    }
}

ThreadDemo1.java

package d1_create;
/**
 * 目标:多线程的创建爱你方式一:继承Thread类实现
 * */
public class ThreadDemo1 {
    public static void main(String[] args) {
        Thread t1 = new MyThread();
        //t1.setName("1号");
        t1.start();
        System.out.println(t1.getName());
        Thread t2 = new MyThread();
        t2.setName("2号");
        t2.start();
        System.out.println(t2.getName());
        //哪个线程执行它,它就得到哪个线程对象
        //主线程的名称叫main
        Thread m = Thread.currentThread();
        System.out.println(m.getName());
        for(int i = 0; i < 5; i++){
            System.out.println("main线程输出:" + i);
        }
    }
}

sleep()

package d1_create;
public class ThreadDemo2 {
    public static void main(String[] args) throws Exception{
        for (int i = 0; i <= 5; i++) {
            System.out.println("输出" + i);
            if(i == 3){
                Thread.sleep(3000);
            }
        }
    }
}

36.3 线程安全

线程安全问题出现的原因?

  1. 存在多线程并发
  2. 同时访问共享资源
  3. 存在修改共享资源

案例:取钱业务

Account.java

package d3_thread_safe;
public class Account {
    private String cardId;
    private double money; //账户余额
    public  Account(){}
    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }
    //小红 小明
    public void drawMoney(double money){
        // 0. 获取是谁来取钱,线程名就是人名
        String name = Thread.currentThread().getName();
        //1. 判断账户是否够钱
        if(this.money >= money){
            //2. 取钱
            System.out.println(name + "来取钱成功,吐出: " + money);
            //3. 更新余额
            this.money -= money;
            System.out.println(name + "取钱后剩余:" + this.money);
        } else{
            //4. 余额不足
            System.out.println(name + "来取钱,余额不足!");
        }
    }
    public String getCardId() {
        return cardId;
    }
    public void setCardId(String cardId) {
        this.cardId = cardId;
    }
    public double getMoney() {
        return money;
    }
    public void setMoney(double money) {
        this.money = money;
    }
}

DrawThread.java

package d3_thread_safe;
/*
* 取钱的线程类
* */
public class DrawThread extends Thread{
    private Account acc;
    public DrawThread(Account acc, String name){
        super(name);
        this.acc = acc;
    }
    @Override
    public void run(){
        //小明  小红: 取钱
        acc.drawMoney(1000000);
    }
}

ThreadDemo.java

package d3_thread_safe;
public class ThreadDemo {
    public static void main(String[] args) {
        //1. 定义线程类,创建一个共享的账户对象
        Account acc = new Account("ICBC-111", 1000000);
        //2. 创建2个线程对象,代表小明和小红同时进来了
        new DrawThread(acc, "小明").start();
        new DrawThread(acc, "小红").start();
    }
}

结果出现线程安全问题:


36.4 线程同步

加锁: 把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。

方式一:同步代码块。把出现线程安全问题的核心代码给上锁;每次只能一个线程进入,执行完毕后自动解锁,其他线程才可进来执行

选中代码块,Ctrl+Alt+T键

小明来取钱成功,吐出: 1000000.0
小明取钱后剩余:0.0
小红来取钱,余额不足!

锁对象的规范要求:

  • 规范上,建议使用共享资源作为锁对象
  • 对于实例方法建议使用this作为锁对象
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象


方式二:同步方法。把出现线程安全的核心方法给上锁;每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

//小红 小明
    public synchronized void drawMoney(double money){   //锁加在方法上
        // 0. 获取是谁来取钱,线程名就是人名
        String name = Thread.currentThread().getName();
        //1. 判断账户是否够钱
        if(this.money >= money){
            //2. 取钱
            System.out.println(name + "来取钱成功,吐出: " + money);
            //3. 更新余额
            this.money -= money;
            System.out.println(name + "取钱后剩余:" + this.money);
        } else{
            //4. 余额不足
            System.out.println(name + "来取钱,余额不足!");
        }
    }


方法三:lock锁

//final修饰后:锁对象是唯一不可替换的
    private final Lock lock = new ReentrantLock();
    
    public void drawMoney(double money){
        // 0. 获取是谁来取钱,线程名就是人名
        String name = Thread.currentThread().getName();
        lock.lock();
        try {
            //1. 判断账户是否够钱
            if(this.money >= money){
                //2. 取钱
                System.out.println(name + "来取钱成功,吐出: " + money);
                //3. 更新余额
                this.money -= money;
                System.out.println(name + "取钱后剩余:" + this.money);
            } else{
                //4. 余额不足
                System.out.println(name + "来取钱,余额不足!");
            }
        } finally {
            lock.unlock();
        }
    }

黑马全套Java教程(九):网络编程(二)+https://developer.aliyun.com/article/1556497

目录
相关文章
|
3月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
234 1
|
3月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
248 1
|
3月前
|
Oracle Java 关系型数据库
Java 简单教程
Java是跨平台、面向对象的编程语言,广泛用于企业开发、Android应用等。本教程涵盖环境搭建、基础语法、流程控制、面向对象、集合与异常处理,助你快速入门并编写简单程序,为进一步深入学习打下坚实基础。
381 0
|
3月前
|
机器学习/深度学习 分布式计算 Java
Java与图神经网络:构建企业级知识图谱与智能推理系统
图神经网络(GNN)作为处理非欧几里得数据的前沿技术,正成为企业知识管理和智能推理的核心引擎。本文深入探讨如何在Java生态中构建基于GNN的知识图谱系统,涵盖从图数据建模、GNN模型集成、分布式图计算到实时推理的全流程。通过具体的代码实现和架构设计,展示如何将先进的图神经网络技术融入传统Java企业应用,为构建下一代智能决策系统提供完整解决方案。
423 0
|
4月前
|
安全 Java
Java之泛型使用教程
Java之泛型使用教程
358 10
|
4月前
|
安全 网络协议 算法
Nmap网络扫描工具详细使用教程
Nmap 是一款强大的网络发现与安全审计工具,具备主机发现、端口扫描、服务识别、操作系统检测及脚本扩展等功能。它支持多种扫描技术,如 SYN 扫描、ARP 扫描和全端口扫描,并可通过内置脚本(NSE)进行漏洞检测与服务深度枚举。Nmap 还提供防火墙规避与流量伪装能力,适用于网络管理、渗透测试和安全研究。
742 1
|
5月前
|
JSON 移动开发 网络协议
Java网络编程:Socket通信与HTTP客户端
本文全面讲解Java网络编程,涵盖TCP与UDP协议区别、Socket编程、HTTP客户端开发及实战案例,助你掌握实时通信、文件传输、聊天应用等场景,附性能优化与面试高频问题解析。
|
5月前
|
Java 关系型数据库 数据库
Java 项目实战教程从基础到进阶实战案例分析详解
本文介绍了多个Java项目实战案例,涵盖企业级管理系统、电商平台、在线书店及新手小项目,结合Spring Boot、Spring Cloud、MyBatis等主流技术,通过实际应用场景帮助开发者掌握Java项目开发的核心技能,适合从基础到进阶的学习与实践。
879 3
|
网络协议 Java API
|
缓存 网络协议 Java