多线程

在操作系统中,每运行一个程序就是一个进程,一个进程可以有多个线程

进程与线程

进程与多进程

当我们编写完一个项目时,它们只是一堆静态的代码,但当我们他项目运行起来工作时,它会在操作系统中创建出一个进程,也就是每个正在运行的程序就是一个进程。它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程。

操作系统并不是同一时间只能运行一个软件,而当你同时打开多个软件时就是多进程。多进程操作系统能同时达运行多个程序,由于CPU具备分时机制,所以每个进程都能循环获得自己的CPU时间片。由于CPU执行速度非常快,使得所有程序好像是在同时运行一样。

线程与多线程

线程比进程小一个执行单位,进程是相对于操作系统,而线程只相对于一个独立的进程。

由于CPU是以时间片的方式为进程分配CUP处理时间的,当一个进程以同步的方式去完成几件事情时,此进程必须完成了第一件事情以后再做第二件事,如此按顺序地向CPU请求完成要做的事情。在此单线程的工作模式下,如果把CUP看作是一共有100个时间片的话,CPU可能一直都只是花了其中的10个时间片来处理当前进程所要做的事情,只是用到了CPU的10%的时间片,而其他时间都白白浪费了,当然,实际上CPU的工作模式还是做完一件事以后再去做另一件事,只是CUP的处理速度非常快,很快就处理完成所请求的情事。

为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而互不干扰,如当前进程要完成三件事情,那么CPU会分别用10%的时间来同时处理这3件事情,从而让CPU的使用率达到了30%,大大地提高了CPU的利用率。多线程的好处在处理一些特殊的场合其优势尤其明显。比如下载文件,你要一边下载一边显示进度一边保存,在这种情况下,如果没有用多线程的话,没有意外的话一般都会把主线程阻塞,比如进度条的进度根本没有随着已下载的量而变化,堪至是整个窗体都动不了,用多线程就可以很好地解决这个问题。

多线程的好处

在程序中使用多线程有三大好处

  • 更好的资源利用
  • 在某些场景下程序的设计会更简单
  • 提升程序的响应性

java中的多线程

其实java项目运行的时候并不是单线程运行,而是至少有两个以上线程,最基本的两线程就是主线程main和垃圾回收线程gc

但java程序不仅仅需要这两线程,以jdk1.8为例,下面是展示线程信息的代码

package top.coor.threaddemo.demo;

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

/**
 * @program: thread-demo
 * @description:
 * @author: coortop
 * @create: 2020-06-25 18:28
 **/
public class ThreadShow {
    public static void main(String[] args) {
        //构建 ThreadMXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        //获取所有存活的线程信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        for (ThreadInfo threadInfo : threadInfos) {
            //打印线程的id和名字
            System.out.println(threadInfo.getThreadId()+"   "+threadInfo.getThreadName());
        }
    }
}

执行结果

6   Monitor Ctrl-Break
5   Attach Listener
4   Signal Dispatcher
3   Finalizer
2   Reference Handler
1   main

根据结果显示,我启动这个项目启动了6个线程,不同jdk版本会有少许差异

当我们不使用多线程编写项目时,所有我们写的代码只在mian线程中执行,但我们可以使用多线程技术改变这一点

实现方式

在java中,提供了3中实现方式

1.继承Thread类

  • 子类继承Thread类可具备多线程能力
  • 启动线程方式:子类对象.start()
  • 不建议使用,避免java单继承的局限性,导致使用线程后不能继承其他类

2.实现Runnable接口

  • 实现Runnable接口可具备多线程能力
  • 启动线程方式:传入目标对象+Thread对象.start() 或 创建线程池
  • 推荐使用,避免java单继承的局限性,灵活方便,方便同一个对象被多个线程使用

3.实现Callable<T>接口

  • 实现Callable接口可具备多线程能力
  • 启动线程方式:传入目标对象+Thread对象.start()
  • 具有返回值,在需要获取返回值的情况下使用

实现代码

Thread类

package top.coor.threaddemo.demo.t1;

/**
 * @program: thread-demo
 * @description:
 * @author: coortop
 * @create: 2020-06-25 19:05
 **/
public class ThreadTest {
    public static void main(String[] args) {
        //实例化拥有多线程能力的类
        Demo demo1 = new Demo("a");
        Demo demo2 = new Demo("b");
        //启动多条线程
        demo1.start();
        demo2.start();
    }
}

class Demo extends Thread {
    //用于打印识别哪条线程在执行
    private String name;
    private int count = 0;

    public Demo() {
    }

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

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程名:" + name + "正在执行-> " + count++);
        }
    }
}

Runnable接口

package top.coor.threaddemo.demo.t2;

/**
 * @program: thread-demo
 * @description:
 * @author: coortop
 * @create: 2020-06-25 19:29
 **/
public class RunnableTest {
    public static void main(String[] args) {
        Demo demo = new Demo();
        //启动多条线程 可指定线程名称
        new Thread(demo, "a").start();
        new Thread(demo, "b").start();
        new Thread(demo, "c").start();
    }
}

class Demo implements Runnable {
    private int count = 0;

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            //Thread.currentThread().getName() 可获取线程名称
            System.out.println("线程名:" + Thread.currentThread().getName() + "正在执行-> " + count++);
        }
    }
}

Callable接口

传入目标对象+Thread对象.start() 方式
package top.coor.threaddemo.demo.t3;

import java.security.SecureRandom;
import java.util.concurrent.*;

/**
 * @program: thread-demo
 * @description:
 * @author: coortop
 * @create: 2020-06-25 19:41
 **/
public class CallableTest {
    public static void main(String[] args) {
        Demo demo = new Demo();
        //模拟启动10个线程
        for (int i = 0; i < 10; i++) {
            FutureTask futureTask = new FutureTask(demo);
            new Thread(futureTask, "ID" + i).start();
            try {
                //获取子线程的返回值
                System.out.println("子线程获取的返回值:" + futureTask.get() + "\n");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

class Demo implements Callable<String> {
    private Integer i = 0;
    private String str = "我是返回的字符串";

    @Override
    public String call() throws Exception {
        System.out.println("线程类:" + Thread.currentThread().getName() + "正在执行-> " + i++ + "(来自线程类打印)");
        return Thread.currentThread().getName() + "正在执行-> " + str + "(来自返回值)";
    }
}
创建线程池方式
package top.coor.threaddemo.demo.t5;

import java.util.concurrent.*;

/**
 * @program: thread-demo
 * @description:
 * @author: coortop
 * @create: 2020-06-26 15:16
 **/
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Demo demo1 = new Demo("a");
        Demo demo2 = new Demo("b");
        Demo demo3 = new Demo("c");

        //创建执行服务
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        //提交执行
        Future<String> submit1 = executorService.submit(demo1);
        Future<String> submit2 = executorService.submit(demo2);
        Future<String> submit3 = executorService.submit(demo3);

        //获取结果
        String r1 = submit1.get();
        String r2 = submit2.get();
        String r3 = submit3.get();

        System.out.println(r1);
        System.out.println(r2);
        System.out.println(r3);

        //关闭服务
        executorService.shutdownNow();
    }
}

class Demo implements Callable<String> {
    //将被返回的字符串
    private String str;

    public Demo(String str) {
        this.str = str;
    }

    @Override
    public String call() throws Exception {
        System.out.println("线程类:" + Thread.currentThread().getName() + "正在执行-> " + str + "(来自线程类打印)");
        return Thread.currentThread().getName() + "返回结果-> " + str + "(来自返回值)";
    }
}

synchronized

以上几种代码实现都未使用安全锁,这将导致这些启动的线程将会出现同时操作同一资源的现象,java中称之为线程不安全

synchronized关键字是java中定义的一种互斥锁,当一个线程运行到使用了synchronized关键字的代码段时,首先检查当前资源是否已经被其他线程所占用,如果已经被占用,那么该线程则阻塞在这里,直到拥有资源的线程释放锁,其他线程才可以继续申请资源。

代码实现

package top.coor.threaddemo.demo.t4;

/**
 * @program: thread-demo
 * @description:
 * @author: coortop
 * @create: 2020-06-25 20:36
 **/
public class RunnableTest {
    public static void main(String[] args) {
        Demo demo = new Demo();
        //启动多条线程 可指定线程名称
        new Thread(demo, "a").start();
        new Thread(demo, "b").start();
        new Thread(demo, "c").start();
    }
}

class Demo implements Runnable {
    //用于代表被操作的资源
    private int count = 0;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //只需要在操作资源的地方加上锁,就可以解决资源被同时操作的安全问题
            synchronized (this) {
                System.out.println("线程名:" + Thread.currentThread().getName() + "正在执行-> " + count++);
            }
        }
    }
}

synchronized关键字不仅能修饰代码块,还能修饰方法

public synchronized void fun() {}

修饰代码块时,传入被修饰的对象

synchronized (Object object) {}

Lock

从jdk1.5开始,Java提供了更加强大的线程同步机制,通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当。

代码实现

package top.coor.threaddemo.demo.t6;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @program: thread-demo
 * @description:
 * @author: coortop
 * @create: 2020-06-27 14:06
 **/
public class LockTest {
    public static void main(String[] args) {
        Demo demo = new Demo();

        new Thread(demo, "a").start();
        new Thread(demo, "b").start();
        new Thread(demo, "c").start();
    }
}

class Demo implements Runnable {

    private int num = 10;
    //定义Lock锁
    private final ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //加锁
            reentrantLock.lock();
            try {
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "   " + num--);
                } else {
                    break;
                }
            } finally {
                //解锁
                reentrantLock.unlock();
            }

        }
    }
}

一般形式为

//创建锁
Lock lock = ...;
//加锁
lock.lock();
try{
    //处理任务
}catch(Exception e){
    //处理异常
}finally{
    //释放锁
    lock.unlock();
}

synchronized与Lock的对比

  • Lock是显式锁(手动开启和关闭),synchronized是隐式锁,出了作用域就自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:Lock -> 同步代码块(已经进入方法体,分配了相应资源) -> 同步方法(在方法体之外)

Over,enjoy it~