一、多线程基础

一、线程与进程

  • 线程是程序最小的执行单元。
  • 进程是操作系统进行资源分配和调度的一个基本单位。
  • 一个程序至少有一个进程,一个进程又至少包含一个线程,在程序运行时,即使自己没有创建线程,后台也会存在多个线程。如GC线程、主线程等。main线程称为主线程,为系统的入口点,用于执行整个程序。
  • 多个线程的执行是由操作系统进行调度的,执行顺序是不能人为干预的。操作系统为线程分配CPU时间片,线程之间进行争夺。
  • 每个线程都有自己的工作内存,但是加载和存储主内存(共享内存)控制不当会造成数据不一致。因此对同一份共享资源操作时,需要加入并发控制。
  • 引入线程的目的是为了充分利用CPU资源,使其可以并行处理多个任务,减少时间消耗,提升效率。但线程会带来额外的开销,如线程的创建和销毁时间,cpu调度时间,并发控制开销。因此,并不是线程创建的越多越好,为此引入了线程池,更好的进行资源控制。
  • 多线程并不一定快,受硬件、网络等其它因素的影响,比如网络带宽为2m,下载速度为1m/s,启动10个线程也不会达到10m/s。

案例:多线程下载图片

public class TDownLoader extends Thread {

    private String url;
    private String name;

    public TDownLoader(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run() {
        WebDownLoader downLoader = new WebDownLoader();
        downLoader.downLoad(url, name);
        System.out.println(name);
    }

    public static void main(String[] args) {
        TDownLoader t1=new TDownLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606411566549&di=9dfa479ec43c80d34ea4a84fc2c3f866&imgtype=0&src=http%3A%2F%2Fgss0.baidu.com%2F94o3dSag_xI4khGko9WTAnF6hhy%2Fzhidao%2Fpic%2Fitem%2F14ce36d3d539b6002ac5706de850352ac75cb7e4.jpg","C:\Users\Administrator\Desktop\ttt\t1.jpg");
        TDownLoader t2=new TDownLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606411566548&di=f901ee991c205196f80ca1f03508d31b&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fpic%2F6%2Fa6%2F39b2375640.jpg","C:\Users\Administrator\Desktop\ttt\t2.jpg");
        TDownLoader t3=new TDownLoader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1606411566547&di=a3caf55c682e66b095a4f0990ea3c0f7&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fpic%2F8%2F09%2Fa615556832.jpg","C:\Users\Administrator\Desktop\ttt\t3.jpg");
        t1.start();
        t2.start();
        t3.start();
    }
}

二、线程的分类

线程分为用户线程和守护线程:
       用户线程(User Thread):一般是程序中创建的线程
       守护线程(Daemon Thread):为用户线程服务的线程,当所有用户线程结束时才会终止,如GC线程。通过Thread的setDaemon(true)方法将一个用户线程转为守护线程。

三、线程的创建

线程的创建方式主要有四种:
       1. 继承Thread类
       2. 实现Runnable接口
       3. 实现Callable接口,可以有返回值,需要结合FutureTask一块使用
       4. 通过线程池来进行任务执行

方式一:继承Thread类

       声明一个Thread类的子类,这个子类重写run方法。使用时必须创建此子类的实例,调用start()方法启动线程

package com.whw.thread.generic;

/**
 * @description 线程的创建:继承Thread类
 */
public class MyThread01 extends Thread {
    @Override
    public void run() {
        System.out.println("我是MyThread01");
    }
}

方式二:实现Runnable接口

       声明一个类实现Runnable接口,这个类实现run方法。使用时必须new一个Thread类,然后创建此子类的实例作为Thread类的参数。调用new出来的Thread类的start()方法

package com.whw.thread.generic;

/**
 * @description 线程的创建:实现Runnable接口
 */
public class MyThread02 implements Runnable{
    @Override
    public void run() {
        System.out.println("我是MyThread02");
    }
}

使用:start()方法启动线程,并不是立即执行线程run方法,具体什么时候,需cpu调度,人为无法干预

public class TestThread {
    public static void main(String[] args) {
        //通过继承Thread类创建线程,直接new一个子类实例,调用start()方法启动线程
        MyThread01 myThread01=new MyThread01();
        myThread01.start();

        //通过实现Runnable接口创建线程,需先new一个子类实例,先后再new一个Thread类,
        //将子类实例作为参数传入后调用start()方法启动线程
        MyThread02 myThread02=new MyThread02();
        new Thread(myThread02).start();
    }
}

注意:直接调用run()方法,将以普通方法执行,依然是单线程顺序调用。

方式三、实现Callable接口

class Thread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("线程开始执行...");
        int i = 10 / 2;
        System.out.println("执行结果i=:" + i);
        System.out.println("线程执行结束...");
        return i;
    }
}

调用Callbale接口的线程

public static void main(String[] args) throws ExecutionException, InterruptedException {
	FutureTask<Integer> futureTask = new FutureTask<Integer>(new Thread3());
    new Thread(futureTask).start();
    Integer i = futureTask.get();// 获取返回值
    System.out.println(i);
}

方式四:使用线程池

public static void main(String[] args) {
        // 创建线程池
	ExecutorService executorService = Executors.newFixedThreadPool(5);
	executorService.execute(new Thread1());
}

四种方式的比较:
       1只能单继承,2、3可以多继承,扩展性更好
       1、2实现的方法是run(),3实现的方法是call()
       1、2不能得到返回值,不能抛异常,3可以获取返回值,可以抛异常
       1、2、3都不能控制资源,4可以直接控制资源,性能稳定

四、线程创建的简化方式

1、常规使用

package com.whw.thread.generic;

public class NormalThread implements Runnable {
    
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("我是MyThread01---" + i);
        }
    }

    public static void main(String[] args) {
        new Thread(new NormalThread()).start();
    }
}

2、使用静态内部类

package com.whw.thread.generic;

public class TestThread {
    // 定义静态内部类
    static class Test implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("我是MyThread01---" + i);
            }
        }
    }

    public static void main(String[] args) {
        // 使用静态内部类
        new Thread(new Test()).start();
    }
}

3、使用局部内部类

package com.whw.thread.generic;

public class TestThread {

    public static void main(String[] args) {
        // 定义局部内部类
        class Test implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println("我是MyThread01---" + i);
                }
            }
        }

        // 使用局部内部类
        new Thread(new Test()).start();
    }
}

4、使用匿名内部类

package com.whw.thread.generic;

public class TestThread {

    public static void main(String[] args) {
        // 使用匿名内部类:必须借助接口或者父类
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println("我是MyThread01---" + i);
                }
            }
        }).start();
    }
}

5、jdk8,使用Lambda表达式

package com.whw.thread.generic;

public class TestThread {

    public static void main(String[] args) {
        // 使用Lambda表达式
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                System.out.println("我是MyThread01---" + i);
            }
        }).start();
        
        new Thread(() -> {
            for (int j = 0; j < 20; j++) {
                System.out.println("我是MyThread02---" + j);
            }
        }).start();
    }
}

lambda表达式基础使用:

package com.whw.thread.generic;

class Love {
    public static void main(String[] args) {
        //此段代码相当于创建了一个ILove的实现类,{}内的为实现类重写方法的方法体
        /*ILove love = (String name) -> {
            System.out.println("I Love -->" + name);
        };*/

        //一个参数时可以不用写参数类型
        /*ILove love = (name) -> {
            System.out.println("I Love -->" + name);
        };*/

        //一个参数时可以不用写括号
        /*ILove love = name -> {
            System.out.println("I Love -->" + name);
        };*/

        //一个参数时,方法体只有一行内容时,可以不用写{},但要写在一行
        //ILove love = name -> System.out.println("I Love -->" + name);

        //love.lambda("王伟");

        //接口两个参数,并且带返回值
        IInterest interest = (a, b) -> {
            return a + b;
        };
        Integer result = interest.lambda(1, 2);
        System.out.println(result);
    }
}

interface ILove {
    void lambda(String name);
}

interface IInterest {
    Integer lambda(Integer a, Integer b);
}

五、线程的常用方法

1、currentThread()

       获取当前正在被调用的线程

2、getId()

       获取线程的唯一id号

3、getName()

       获取线程的名字

4、getState()

       获取线程的状态

5、isDaemon()

       判断是否是守护线程

6、setDaemon(boolean on)

       设为守护线程,只能在线程启动之前把它设为后台线程

7、isAlive()

       判断当前线程是否处于活动状态

8、activeCount()

       判断当前线程组中存活的线程数

9、getThreadGroup()

       获取当前线程所在的线程组

10、sleep(long)

       让线程休眠指定的毫秒数,休眠过程中资源不释放

11、停止线程

       stop():已被废弃
       interrupt():
              并不会终止正在运行的线程,需要加入一个判断才可以停止线程
              判断线程是否被中断this.isInterrupted()
       run():运行结束

异常法停止线程:

public class StopThread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 停止线程
        myThread.interrupt();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 1000000; i++) {
                System.out.println(i);
                if (this.isInterrupted()) {
                    System.out.println("我被终止了");
                    // 抛出异常,中断for循环下面代码的执行
                    throw new InterruptedException();
                }
            }
            System.out.println("我被输出,说明线程并未停止");
        } catch (InterruptedException e) {
            System.out.println("异常法结束线程的执行");
            e.printStackTrace();
        }
    }
}

标记法停止线程:

package com.whw.thread.state;

/**
 * @description 终止线程
 * 1、线程正常执行完毕-->次数
 * 2、外部干涉-->加入标识
 * 不要使用stop,destroy方法
 */
public class TerminateThread implements Runnable {

    //1、加入标识,标记线程体是否可以运行
    private boolean flag = true;
    private String name;

    public TerminateThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        int i = 0;
        //2、关联标识,true-->运行 false-->停止
        while (flag) {
            System.out.println(name + "-->" + i++);
        }
    }

    //3、对外提供方法改变标识
    public void terminate() {
        this.flag = false;
    }

    public static void main(String[] args) throws InterruptedException {
        TerminateThread tt = new TerminateThread("A");
        new Thread(tt).start();

        for (int i = 0; i <= 99; i++) {
            if (i == 88) {
                tt.terminate();//线程终止,解决死锁
                System.out.println("tt game over");
            }
            System.out.println("main-->" + i);
        }
    }
}

12、暂停与恢复线程

       暂停suspend():已被弃用,使用不当容易造成独占锁和数据不同步
       恢复resume():已被弃用,使用不当容易造成独占锁和数据不同步

13、yield()

       线程礼让,不一定每次都成功,只是让当前线程放弃本次CPU资源的争夺,有可能刚放弃马上又得到了cpu资源

14、setPriority()

       设定线程的优先级,默认是5,1~10个等级,有助于线程规划器尽可能多的将资源分给优先级高的线程
       具有继承性
       高优先级的不一定每次都先执行完

15、join(long millis)

       join合并线程,插队线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。他是一个成员方法

原文地址:https://www.cnblogs.com/giswhw/p/15533814.html