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

目录
相关文章
|
22天前
|
Java
【思维导图】JAVA网络编程思维升级:URL与URLConnection的逻辑梳理,助你一臂之力!
【思维导图】JAVA网络编程思维升级:URL与URLConnection的逻辑梳理,助你一臂之力!
33 1
|
22天前
|
XML JSON 搜索推荐
【高手过招】JAVA网络编程对决:URL与URLConnection的高级玩法,你敢挑战吗?
【高手过招】JAVA网络编程对决:URL与URLConnection的高级玩法,你敢挑战吗?
43 0
|
2天前
|
Java API
Java时间戳教程
本文详细介绍Java中时间戳的处理方法,包括获取当前时间戳、使用`java.time`包、时间戳与日期的相互转换及格式化等。示例代码展示了如何利用`System.currentTimeMillis()`和`java.time.Instant`获取时间戳,以及如何通过`Date`和`ZonedDateTime`进行日期转换和时区处理。随着Java 8引入的`java.time`包,日期时间操作变得更加强大和便捷,推荐在新项目中优先采用。
|
22天前
|
Java
【实战演练】JAVA网络编程高手养成记:URL与URLConnection的实战技巧,一学就会!
【实战演练】JAVA网络编程高手养成记:URL与URLConnection的实战技巧,一学就会!
29 3
|
22天前
|
安全 Java 网络安全
【认知革命】JAVA网络编程新视角:重新定义URL与URLConnection,让网络资源触手可及!
【认知革命】JAVA网络编程新视角:重新定义URL与URLConnection,让网络资源触手可及!
31 2
|
23天前
|
存储 算法 Java
Java中的集合框架深度解析云上守护:云计算与网络安全的协同进化
【8月更文挑战第29天】在Java的世界中,集合框架是数据结构的代言人。它不仅让数据存储变得优雅而高效,还为程序员提供了一套丰富的工具箱。本文将带你深入理解集合框架的设计哲学,探索其背后的原理,并分享一些实用的使用技巧。无论你是初学者还是资深开发者,这篇文章都将为你打开一扇通往高效编程的大门。
|
21天前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
42 0
|
22天前
|
缓存 Java API
【技术前沿】JAVA网络编程黑科技:URL与URLConnection的创新应用,带你飞越极限!
【技术前沿】JAVA网络编程黑科技:URL与URLConnection的创新应用,带你飞越极限!
28 0
|
23天前
|
Java API
Java与Lua互相调用简单教程
【8月更文挑战第29天】在软件开发中,Java以其强大的稳定性和广泛的生态系统著称,而Lua则因其轻量级、灵活和嵌入式的特点在脚本编写、游戏开发等领域大放异彩。将两者结合使用,可以充分利用Java的底层能力和Lua的快速开发优势。本文将通过一个简单的教程,介绍如何在Java程序中嵌入并执行Lua脚本,以及如何在Lua中调用Java方法。
19 0
|
2天前
|
SQL 安全 算法
网络安全与信息安全:保护你的数字世界
【9月更文挑战第18天】在这个数字信息时代,网络安全和信息安全的重要性不言而喻。从网络漏洞的发现到加密技术的应用,再到安全意识的提升,每一个环节都至关重要。本文将深入探讨这些主题,并提供实用的建议和代码示例,以帮助读者更好地保护自己的数字世界。
23 11