JavaSE笔记23:多线程(一)

28 篇文章 1 订阅

多线程(一)

1.线程简介

任务、进程、线程、多线程

多任务,例如:边吃饭边玩手机、边开车边打电话…

进程:在操作系统中运行的程序就是进程,如QQ、播放器、游戏…

线程:例如,播放器可以听见声音,播放的声音就是一个线程

多线程:一个进程可以有多个线程,如视频中同时听声音、看图像、看弹幕…

进程与线程的关系

举个例子:

A:进程 B:进程

​ a1:A的一个线程 b1:B的一个线程

​ a2:A的一个线程 b2:B的一个线程

注意1:

  1. 进程A和进程B的内存独立不共享
  2. java语言中,A的线程和B的线程在堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈

如:XX游戏是一个进程,XX音乐是一个进程,这两个进程是独立的,不共享资源

假设启动10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发。

java中之所以有多线程机制,目的就是为了提高程序的处理效率。

在这里插入图片描述

注意2:

  1. 很多的多线程是模拟出来的,真正的多线程是指多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

    例如:T1线程执行T1的,T2线程执行T2的,T1不会影响T2,T2也不会影响T1,这是多线程并发。对于多核的CPU电脑来说,真正的多线程并发是没有问题的,例如4核CPU,表示同一时间点上,可以真正的有4个进程并发执行。对于单核的CUP来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换,给人的感觉是同时在做。

  2. 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程。

package se5.thread;
/*
分析以下程序有几个线程(除垃圾回收线程)
    1个线程,因为程序只有1个栈
    (注意:一个方法并不是一个线程,如果是,那不扯么?!)
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        System.out.println("main begin");
        m1();
        System.out.println("main over");
    }
    private static void m1() {
        System.out.println("m1 begin");
        m2();
        System.out.println("m1 over");
    }
    private static void m2() {
        System.out.println("m2 begin");
        m3();
        System.out.println("m2 over");
    }
    private static void m3() {
        System.out.println("m3 execute!");
    }
}

2.线程创建

三种创建方式:

  1. Thread class ------> 继承Thread类(重点)
  2. Runnable接口 ------> 实现Runnable接口(重点)
  3. Callable接口 ------> 实现Callable接口(目前作为了解)
第一种方式
  1. 自定义线程类继承Thread类
  2. 重写run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
//定义线程类
public class MyThread extends Thread{
    public void run(){...}
}
//创建线程对象
MyThread t = new MyThread();
//启动线程
t.start();
package se5.thread;
/*
实现线程的第一种方式:编写一个类直接继承java.lang.Thread,重写run方法
怎么创建线程对象? new就行了
怎么启动线程? 调用线程对象的start()方法
 */
public class ThreadTest02 {
    public static void main(String[] args) {
        //这里是main方法,这里的代码属于主线程,在主栈中运行
        //新建一个分支流对象
        MyThread t = new MyThread();
        //启动线程
        //start方法的作用是:启动一个线程分支,在JVM中开辟一个新的栈空间,这段代码任务完成之后瞬间就结束了。
        //这段代码的任务只是为了开辟一个新的栈空间,只有新的栈空间开出来,start方法就结束了,线程就启动成功了。
        //启动成功的线程会自动调用run方法,并且run方法在分支线的栈底部(压栈)
        //run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的
        t.start();//start方法不结束,是不会往下执行其他代码的
        //t.run();//不会启动线程,不会分配新的分支线(这种方法就是单线程)
        //这里的代码还是运行在主线程中
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程--->" + i);
        }
        /**
         * 部分输出结果:
         * 分支线程--->172
         * 分支线程--->173
         * 主线程--->19
         * 分支线程--->174
         * 分支线程--->175
         */
    }
}
class MyThread extends Thread{
    @Override
    public void run(){
        //编写程序,这段程序运行在分支线程(分支栈)中
        for (int i = 0; i < 1000; i++){
            System.out.println("分支线程--->"+ i);
        }
    }
}
图片下载例子

注意:此例子下载了commons-io-2.6工具包

package com.thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

//练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread{

    private String url;//网络图片地址
    private String name;//保存的文件名
    //有参构造方法
    public TestThread2(String url,String name){
        this.url = url;
        this.name = name;
    }
    //下载图片线程的执行体
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的图片文件名是:" + name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("http://img1.gamersky.com/upimg/pic/2018/01/18/201801181052241016_small.jpg","南天之怒.jpg");
        TestThread2 t2 = new TestThread2("http://img1.gamersky.com/upimg/pic/2017/06/06/201706061152547794_small.jpg","神拳.jpg");
        TestThread2 t3 = new TestThread2("http://img1.gamersky.com/upimg/pic/2018/01/18/201801181052251036_small.jpg","北辰之威.jpg");
        //启动线程
        t1.start();
        t2.start();
        t3.start();
        /**
         * 本次运行结果:
         * 下载的图片文件名是:北辰之威.jpg
         * 下载的图片文件名是:神拳.jpg
         * 下载的图片文件名是:南天之怒.jpg
         */
    }
}
//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }
}

在这里插入图片描述

第二种方式
  1. 定义MyRunnable类实现Runnable接口
  2. 实现run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
public class MyRunnable implements Runnable{
    public void run(){...}
}
//创建线程对象
Thread t = new Thread(new MyRunnable());
//启动线程
t.start;
package com.thread;
/*
创建线程方式二:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口实现类,调用start方法
 */
public class TestThread3 implements Runnable{

    @Override
    public void run() {
        //run方法线程体

        for (int i = 0; i < 1000; i++) {
            System.out.println("卧尼玛--->" + i);
        }
    }

    public static void main(String[] args) {
        //main线程,主线程

        //创建一个线程对象
        TestThread3 testThread3 = new TestThread3();
        //创建线程对象,通过线程对象来开启我们的线程,代理。
        Thread thread = new Thread(testThread3);
        thread.start();
        //以上两句可简写为:new Thread(testThread3).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("卧你妈--->" + i);
        }
        /**
         * 本次运行结果:
         * 卧尼玛--->768
         * 卧尼玛--->769
         * 卧你妈--->721
         * 卧你妈--->722
         */
    }
}

图片下载例子也可改为实现Runnable接口的方式

图片下载例子
package com.thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/*
练习Thread,实现多线程同步下载图片
 */
public class TestThread2 implements Runnable{

    private String url;//网络图片地址
    private String name;//保存的文件名
    //有参构造方法
    public TestThread2(String url,String name){
        this.url = url;
        this.name = name;
    }
    //下载图片线程的执行体
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载的图片文件名是:" + name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("http://img1.gamersky.com/upimg/pic/2018/01/18/201801181052241016_small.jpg","南天之怒.jpg");
        TestThread2 t2 = new TestThread2("http://img1.gamersky.com/upimg/pic/2017/06/06/201706061152547794_small.jpg","神拳.jpg");
        TestThread2 t3 = new TestThread2("http://img1.gamersky.com/upimg/pic/2018/01/18/201801181052251036_small.jpg","北辰之威.jpg");
/*
        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t2);
        Thread thread3 = new Thread(t3);

        thread1.start();
        thread2.start();   
        thread3.start();
        可简写,如下
 */
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
    }
}
//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }
}

在这里插入图片描述

还有使用匿名内部类的方式,了解下
package se5.thread;
/*
采用匿名内部类方式
 */
public class ThreadTest04 {
    public static void main(String[] args) {
        //采用匿名内部类方式创建线程对象
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("t线程--->" + i);
                }
            }
        });
        //启动线程
        t.start();
        for (int i = 0; i < 100; i++) {
            System.out.println("main线程--->" + i);
        }
    }
}
小结

继承Thread类

  1. 子类继承Thread类具备多线程能力
  2. 启动线程:子类对象.strat()
  3. 不建议使用:避免OOP单继承局限性

实现Runnable接口

  1. 实现接口Runnable具有多线程能力
  2. 启动线程:传入目标对象 + Thread对象.start()
  3. 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
第三种方式,Callable(JDK8新特性,了解即可)

这种方式实现的线程可以获取线程的返回值。之前的两种方式无法获取线程的返回值,因为run方法返回void。

package se5.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;//属于java的并发包,老JDK中没有这个包
/*
实现Callable接口
 */
public class ThreadTest15 {
    public static void main(String[] args) throws Exception {

        //第一步:创建一个未来任务类对象,这里采用匿名内部类的方式
        //参数非常重要,需要给一个Callable接口实现类对象
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {//call()方法相当于run方法,只不过这个有返回值
                //线程执行一个任务,执行之后可能会有一个执行结果
                //模拟执行
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                return a + b;//自动装箱(300结果变成Interger)
            }
        });
        //创建线程对象
        Thread t = new Thread(task);
        //启动线程
        t.start();

        //这里是main方法,主线程中
        //在主线程中如何获取t线程的返回结果?
        //get方法的执行会导致当前线程阻塞
        Object obj = task.get();
        System.out.println("线程执行结果:" + obj);

        //main方法这里的程序要执行必须等待get方法的结束
        //而get方法可能需要很久,因为get方法是为了拿另一个线程的执行结果
        //另一个线程执行是需要时间的
        System.out.println("Hello World!");
        /**
         * 运行结果:
         * call method begin
         * call method end!
         * 线程执行结果:300
         * Hello World!
         */
    }
}

优点:可以获取到线程的执行结果

缺点:效率比较低,在获取t线程结果的时候,当前线程受阻塞

例子:
package com.thread2;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/*
练习Thread,实现多线程同步下载图片
 */
public class TestCallable implements Callable {

    private String url;//网络图片地址
    private String name;//保存的文件名
    //有参构造方法
    public TestCallable(String url,String name){
        this.url = url;
        this.name = name;
    }
    //下载图片线程的执行体
    @Override
    public Boolean call() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下载的图片文件名是:" + name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://img1.gamersky.com/upimg/pic/2018/10/14/201810141820249816_small.jpg","KDA.jpg");
        TestCallable t2 = new TestCallable("https://img1.gamersky.com/upimg/pic/2018/07/02/201807021201141166_small.jpg","图2.jpg");
        TestCallable t3 = new TestCallable("https://img1.gamersky.com/upimg/pic/2018/09/27/201809271122535513_small.jpg","图3.jpg");

        //创建执行服务:ExecutorService ser = Executor.newFixedThreadPool(int i);
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);
        //获取结果:
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();
        //关闭服务
        ser.shutdownNow();
    }
}
//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值