多线程专题

简介: 多线程专题

多线程

GitHub - SoftLeaderGy/StartThread

一、线程的相关简介


二、线程的创建(Thread、Runnable)

(Thread、Runnable、Callable)

1、方式一(继承Thread类)

  • 步骤:
  • 继承Thread类
  • 重写run()方法
  • 主线程调用start()方法开启线程
  • 总结:
  • 线程开启不一定立即执行,由cpu就行调度
  • 代码:
/**
 * 创建线程类
 * 重写run()方法
 * 调用start开启线程
 */
public class TestThread01 extends Thread{
    @Override
    public void run() {
        // run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码--" + i);
        }
    }
    public static void main(String[] args) {
        // 创建一个线程对象
        TestThread01 testThread01 = new TestThread01();
        // 调用start方法开启线程
        testThread01.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习--" + i);
        }
    }
}

2、方式二(实现Runnable)

  • 步骤:
  • 实现Runnable接口具有多线程的能力
  • 启动线程:传入目标对象 + Thread对象.start()
  • 代码
package com.yang.demo01;
/**
 * 实现Runnable接口,实现多线程
 * 实现Runnable接口、重写run方法、执行线程需要丢入Runnable接口的实现类、调用start方法
 */
public class TestThread03 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("我在看书 -->" + i);
        }
    }
    public static void main(String[] args) {
        TestThread03 testThread03 = new TestThread03();
        // 开启线程
        new Thread(testThread03).start();
        for (int i = 0; i < 100; i++) {
            System.out.println("我在学习 -->" + i);
        }
    }
}

总结
推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用

三、初识并发问题

  • 买票案例(10张车票,三个人同时买票)
  • 代码
package com.yang.demo01;
public class TestThread04 implements Runnable{
    // 定义车票一共100张票
    private int TICKET = 10;
    @Override
    public void run() {
        while (true) {
            if(TICKET <= 0){
               break;
            }
            // 电脑速度过快,添加延时
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "抢到了第--->"+ TICKET-- + "张票");
        }
    }
    public static void main(String[] args) {
        TestThread04 testThread04 = new TestThread04();
        new Thread(testThread04,"小明").start();
        new Thread(testThread04,"小李").start();
        new Thread(testThread04,"小王").start();
    }
}
  • 发现问题
  • 多个线程操作同一个资源的情况下,线程不安全,数据紊乱。

四、线程创建(Callable)


  • 步骤
  • 实现Callable接口,需要返回值类型
  • 重写Callable接口,需要抛出异常
  • 创建实现Callable接口的目标对象
  • 创建执行服务:
  • // 创建服务 Executors.newFixedThreadPool(3); 创建线程数为3个
  • ExecutorService executorService = Executors.newFixedThreadPool(3);
  • 提交执行
  • // 提交执行任务,c1为实现Callable接口实现类的对象
  • Future s1 = executorService.submit(c1);
  • 获取结果
  • // 获取执行线程返回结果
  • System.out.println(s1.get());
  • 关闭服务
  • executorService.shutdownNow();
  • 代码
package com.yang.demo02;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class TestCallable01 implements Callable<Boolean> {
    public TestCallable01(String url, String name) {
        this.url = url;
        this.name = name;
    }
    private String url;
    private String name;
    @Override
    public Boolean call() throws Exception {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载文件完成,文件名为--"+ name);
        return true;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable01 c1 = new TestCallable01("https://lmg.jj20.com/up/allimg/4k/s/02/2109242332225H9-0-lp.jpg", "1.jpg");
        TestCallable01 c2 = new TestCallable01("https://lmg.jj20.com/up/allimg/tp01/1ZZQ20QJS6-0-lp.jpg","2.jpg");
        TestCallable01 c3 = new TestCallable01("https://lmg.jj20.com/up/allimg/tp04/1Z92G92I25110-0-lp.jpg","3.jpg");
        // 创建服务 Executors.newFixedThreadPool(3); 创建线程数为3个
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 提交执行任务
        Future<Boolean> s1 = executorService.submit(c1);
        Future<Boolean> s2 = executorService.submit(c2);
        Future<Boolean> s3 = executorService.submit(c3);
        // 获取执行线程返回结果
        System.out.println(s1.get());
        System.out.println(s2.get());
        System.out.println(s3.get());
        // 关闭服务
        executorService.shutdownNow();
    }
}
/**
 * 下载器
 */
@Slf4j
class WebDownloader {
    public void downloader(String url, String name) {
        try {
            // commons.io包 下载文件工具包
            // 通过URL下载图片
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            log.info("IO异常,downloader方法出现异常!");
        }
    }
}

五、守护线程(daemon)


  • 线程
  • 用户线程(main)
  • 守护线程(gc)
  • 虚拟机必须要确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等。
  • 测试代码
package com.yang.state;
/**
 * @Description: 测试守护线程
 * @Author: Guo.Yang
 * @Date: 2022/02/24/23:01
 */
public class TestDaemon {
    public static void main(String[] args) {
        You you = new You();
        God god = new God();
        // 开启上帝线程
        Thread thread = new Thread(god);
        // 设置上帝线程为守护线程
        thread.setDaemon(true);
        // 开启用户线程
        thread.start();
        //开启用户线程
        new Thread(you).start();
    }
}
// 你(用户进程)
class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("开心的活着");
        }
        System.out.println("good bye");
    }
}
// 上帝线程(守护线程)
class God implements Runnable {
    @Override
    public void run() {
        // 正常的线程 应该为上帝线程会一直执行下去
        // 但由于是守护线程,也就是用户线程停止后,守护线程会自动结束
        while (true) {
            System.out.println("上帝守护着你");
        }
    }
}
  • God线程在不设置守护线程的情况下会是一直执行下去的进程
  • 但是设置成守护线程后,用户线程执行完毕后会自动结束线程
  • 测试截图

六、线程不安全性


1、测试List集合


  • 测试List集合线程不安全性
  • 众所周知List集合不安全分析下原因
  • 测试代码
/**
 * @Description: 测试List 的线程不安全性
 * @Author: Guo.Yang
 * @Date: 2022/02/25/21:05
 */
public class TestList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        System.out.println(list.size());
    }
}
// 输出结果:9999

预计输出情况:10000
但是在运行后,有时后出现 小于 10000


  • 原因
  • 当两个线程同时操作list的同一个位置的时候,会覆盖掉上一个线程添加的元素

2、测试买票


  • 测试代码
import lombok.SneakyThrows;
/**
 * @Description:
 * @Author: Guo.Yang
 * @Date: 2022/02/25/21:48
 */
public class BuyTicket{
    public static void main(String[] args) {
        A buyticket = new A();
        new Thread(buyticket, "张").start();
        new Thread(buyticket, "王").start();
        new Thread(buyticket,"李").start();
    }
}
class A implements Runnable{
    private int ticketNums = 10;
    private Boolean flag = true;
    @SneakyThrows
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    public void buy() throws InterruptedException {
        if(ticketNums <= 0){
            flag = false;
            return;
        }
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + "拿到了"+ ticketNums--);
    }
}
  • 结果输出
  • 数据出现问题
// 张拿到了10
// 王拿到了9
// 李拿到了9
// 李拿到了8
  • 解决方案
  • 同步方法
  • synchronized方法
  • synchronized方法控制“对象”的方法,每个对象对应一把锁,每个synchronized凡法规都必须获得到该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后边被阻塞的线程才能获得这个锁,继续执行
  • 缺陷;若将一个大的方法申明为synchronized将会影响效率
  • synchronized块
  • synchronized(obj){}
  • obj称之为同步监视器
  • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需执行同步监视器,因为同步方法的同步监视器就是this,就是对像本身,活着是class
  • 同步监视器的执行过程
  • 第一个线程访问,锁定同步监视器,执行其中代码
  • 第二个线程防伪,发现同步监视器被锁定,无法访问
  • 第一个线程访问完毕,解锁同步监视器
  • 第二个线程访问,发现同步监视器没锁锁,然后锁定并访问
  • synchronized方法
  • 测试代码
import lombok.SneakyThrows;
/**
 * @Description:
 * @Author: Guo.Yang
 * @Date: 2022/02/25/21:48
 */
public class BuyTicket{
    public static void main(String[] args) {
        A buyticket = new A();
        new Thread(buyticket, "张").start();
        new Thread(buyticket, "王").start();
        new Thread(buyticket,"李").start();
    }
}
class A implements Runnable{
    private int ticketNums = 10;
    private Boolean flag = true;
    @SneakyThrows
    @Override
    public void run() {
        while (flag) {
            buy();
        }
    }
    public void buy() throws InterruptedException {
        if(ticketNums <= 0){
            flag = false;
            return;
        }
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + "拿到了"+ ticketNums--);
    }
}
* synchronized块
    * 代码块锁对象(一定要锁共享资源的变量,谁变锁谁)
    * 测试代码
/**
 * @Description: 测试List 的线程不安全性
 * @Author: Guo.Yang
 * @Date: 2022/02/25/21:05
 */
public class TestList {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<String> list = new ArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
            Thread.sleep(1);
        }
        System.out.println(list.size());
    }
}

七、线程池


  • 线程池的API:ExecutorService 和 Executors
  • ExecutorService:真正的线程池接口。常见的子类ThreadPoolExecutor
  • void execute(Runnable command); 执行任务、命令没有返回值,一般用来执行Runable
  • Future submit(Callable task); 执行任务,有返回值,一般用来执行Callable
  • void shutdown(); 关闭链接
  • Excutors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
相关文章
|
安全 Java 调度
|
安全 Java
多线程02
多线程02
|
22天前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
|
6月前
|
安全 Java C++
多线程问题(四)
多线程问题(四)
39 0
|
11月前
|
安全 Linux C++
C++多线程
C++多线程
52 1
|
Linux 调度 C++