黑马全套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

目录
相关文章
|
1天前
|
XML 测试技术 数据格式
《手把手教你》系列基础篇(八十五)-java+ selenium自动化测试-框架设计基础-TestNG自定义日志-下篇(详解教程)
【7月更文挑战第3天】TestNG教程展示了如何自定义日志记录。首先创建一个名为`TestLog`的测试类,包含3个测试方法,其中一个故意失败以展示日志。使用`Assert.assertTrue`和`Reporter.log`来记录信息。接着创建`CustomReporter`类,继承`TestListenerAdapter`,覆盖`onTestFailure`, `onTestSkipped`, 和 `onTestSuccess`,在这些方法中自定义日志输出。
18 6
|
19小时前
|
Java 测试技术 Apache
《手把手教你》系列基础篇(八十六)-java+ selenium自动化测试-框架设计基础-Log4j实现日志输出(详解教程)
【7月更文挑战第4天】Apache Log4j 是一个广泛使用的 Java 日志框架,它允许开发者控制日志信息的输出目的地、格式和级别。Log4j 包含三个主要组件:Loggers(记录器)负责生成日志信息,Appenders(输出源)确定日志输出的位置(如控制台、文件、数据库等),而 Layouts(布局)则控制日志信息的格式。通过配置 Log4j,可以灵活地定制日志记录行为。
13 4
|
1天前
|
网络协议 安全 Java
Java中的网络编程:Socket编程详解
Java中的网络编程:Socket编程详解
|
1天前
|
安全 网络协议 Java
Java中的网络通信:HTTP详解
Java中的网络通信:HTTP详解
|
1天前
|
网络协议 Java 网络安全
Java中的网络编程:TCP详解
Java中的网络编程:TCP详解
|
19小时前
|
网络协议 安全 Java
深入了解Java中的网络编程与Socket通信
深入了解Java中的网络编程与Socket通信
|
1天前
|
Linux 网络安全 数据安全/隐私保护
网络安全教程-------渗透工具Kali,官网链接,ARM的介绍,Mobil,华为小米,oppe手机,是无法刷入第三方的操作系统的,E+手机,谷歌的picksoul,或者三星手机,系统盘是WSL的
网络安全教程-------渗透工具Kali,官网链接,ARM的介绍,Mobil,华为小米,oppe手机,是无法刷入第三方的操作系统的,E+手机,谷歌的picksoul,或者三星手机,系统盘是WSL的
|
2天前
|
Java 测试技术 Android开发
《手把手教你》系列基础篇(八十四)-java+ selenium自动化测试-框架设计基础-TestNG日志-上篇(详解教程
【7月更文挑战第2天】TestNG是一个用于自动化测试的Java框架,提供日志记录功能。日志有两种模式:底层级详细记录每个步骤,高层级仅记录关键事件。示例代码展示了如何在测试方法中使用`Reporter.log()`记录信息,这些信息会显示在TestNG HTML报告中。文章还提及了日志显示时可能出现的编码问题及解决办法。
|
2天前
|
网络协议 Java
Java网络编程基础与Socket实现技术
Java网络编程基础与Socket实现技术
|
Java 数据库 容器

热门文章

最新文章