多线程基础知识预备

一、简介

1.进程与线程的区别

进程: 进程是系统进行资源分配和调度的独立单位。每一个进程都有它自己的内存空间和系统资源

线程: 线程作为资源调度的基本单位,是程序的执行单元,执行路径(单线程:一条执行路径,多线程:多条执行路径)。是程序使用CPU的最基本单位。

⼀个程序⾄少有⼀个进程,⼀个进程⾄少有⼀个线程。

并行与并发

并行:

  • 并行性是指同一时刻内发生两个或多个事件。
  • 并行是在不同实体上的多个事件

并发:

  • 并发性是指同一时间间隔内发生两个或多个事件。
  • 并发是在同一实体上的多个事件

由此可见:并行是针对进程的,并发是针对线程的

2.线程的属性与类型

包括线程:

  • 编号(ID)
  • 名称(Name)
  • 线程类别(Daemon)
  • 优先级(Priority)

线程编号ID:类型long,用于标识不同的线程,编号唯一性只存在java虚拟机的一次运行有效

线程名称Name:类型String,默认Thread-线程编号,设置该属性有助于线程调试和问题定位。

线程类别Daemon:类型boolean,值为true表示该线程为守护线程,否则为用户线程,默认值与相应线程的父线程该属性值相同,该属性必须在线程启动前设置!否则会报错

优先级Priority:类型int,该属性本质上是给线程调度器的提示,用于表示应用程序那个线程优先运行。java定义了1~10的10个优先级别。默认值为5(普通优先级别)。对应一个具体的线程而言,优先级别的默认值与父线程相同。

注:线程并不保证执行顺序按优先级进行!优先级使用不当可能导致某些线程用于无法得到运行!一般情况下不设置即可。

3.线程的两个基本类型(用户线程 (User Thread) 与守护线程 (Daemon)

用户线程会阻止Java虚拟机的正常停止,即一个Java虚拟机只有在其所有的用户线程运行都结束的情况下才能正常停止。

守护线程不会影响Java虚拟机的正停止,即应用程序中有守护线程在运行也不会影响Java虚拟机的正常停止。因此守护线程通常用于执行一些重要性不是很高的任务,如监视其他线程的运行情况。

二、Java实现多线程

1.创建多线程的方式

  • 继承Thread,重写run方法
  • 实现Runnable接口,重写run方法

通过继承Thread类创建多线程

/*
* 定义子类,继承Thread
* 重写方法run
*/
public class subThread extends Thread{
    @Override
    public void run() {//线程要执行的任务
        for(int i=0;i<50;i++){
            System.out.println("run"+i);
        }
    }
}

/*
    创建和启动一个线程
        创建Thread子类对象
        子类对象调用方法start() .
        让线程程序执行, JVM调用线程中的run
*/
public class test {
    public static void main(String[] args) {
        subThread mythread1 = new subThread();
        for(int i = 0;i<50;i++){
            System.out.println("main1"+i);
        }
        mythread1.start();//只能调用一次
        for(int i = 0;i<50;i++){
            System.out.println("main2"+i);
        }
        subThread mythread2 = new subThread();
        mythread2.start();
    }
}

通过实现Runnable接口创建多线程

public class SubRunnable implements Runnable {
    @Override
    public void run() {
        for(int i = 0; i<50; i++){
            System.out.println("Runnable  "+i);
        }
    }
}
public class test {
    public static void main(String[] args) {
        SubRunnable subRunnable1 = new SubRunnable();
        Thread myThreadRun = new Thread(subRunnable1);
        myThreadRun.start();
        for(int i = 0;i<50;i++){
            System.out.println("main2"+i);
        }
    }
}

匿名内部类创建多线程

public class test {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for(int i =0;i<30;i++){
                    System.out.println("thread"+i);
                }
            }
        }.start();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for(int i =0;i<30;i++){
                    System.out.println("Runnable"+i);
                }
            }
        };
        Thread thread2 = new Thread(runnable);
        thread2.start();
    }
}

通过Callable接口实现多线程

注:建议先看完线程池知识再来学习

实现方式与Runnable基本一致,只是Runnable重写的是run()方法没有返回值;Callable重写的是Call()方法,返回值是泛型。但是由于Thread类构造方法不能添加Callable类型,因此Callable接口只是在线程池中作为有返回值的提交线程使用。

在这里插入图片描述

线程池中的实现:

public class ThreadPool {
    public static void main(String[] args) throws Exception{
        /*ExecutorService ex = Executors.newFixedThreadPool(2);
        ex.submit(new SubRunnable());
        ex.submit(new SubRunnable());
        ex.submit(new SubRunnable());*/
        ExecutorService ex = Executors.newFixedThreadPool(2);
        //返回Future对象,Callable的返回值在Future对象中
        Future<String> f = ex.submit(new SubCollable());
        //通过Future对象的get方法获得返回值,会要抛错
        String str = f.get();
        System.out.println(str);
    }
}
/*
*Callable接口的实现类,作为线程提交任务出现
* 使用方法返回值
* */
public class SubCollable implements Callable {
    @Override
    public String call() throws Exception {//throws Exception可抛可不抛
        System.out.println("Callable运行");
        return "abdgljgl";
    }
}

一般我们使用实现Runnable接口

  • 可以避免java中的单继承的限制
  • 应该将并发运行任务和运行机制解耦,因此我们选择实现Runnable接口这种方式!

2.多线程的API

2.1设置与获取线程名

//使用父类构造方法设置线程名
public class subThread extends Thread{
    public subThread(String setName){
        super(setName);
    }
    @Override
    public void run() {//线程要执行的任务
        System.out.println(super.getName()+"  run");
    }
}


public class test {
    public static void main(String[] args) {
        Thread thread1 = new subThread();
        thread1.start();
        thread1.setName("thread1");//通过setName()方法设置线程名
        System.out.println(thread1.getName()+"   thread1+main");//通过调用对象getName()方法获取线程名
        System.out.println(Thread.currentThread().getName()+"  main");//Thread.currentThread()返回对当前正在执行的线程对象的引用
        Thread thread2 = new Thread(new SubRunnable());
        thread2.start();
        System.out.println(thread2.getName()+"   Runnable+main");
        Thread thread3 = new Thread(new SubRunnable(),"thread3");//直接创建Thread类时设置线程名
        thread3.start();
    }
}
//结果
thread1   thread1+main
main  main
Thread-1   Runnable+main
thread1  run
Thread-1   Runnable+run
thread3   Runnable+run

2.2守护线程

守护线程是为其他线程服务的

  • 垃圾回收线程就是守护线程

守护线程有一个特点

  • 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止掉了。
  • 也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行

使用线程的时候要注意的地方

  1. 在线程启动前设置为守护线程,方法是setDaemon(boolean on),在调用start()方法后再设置会报错
  2. 使用守护线程不要访问共享资源(数据库、文件等),因为它可能会在任何时候就挂掉了。
  3. 守护线程中产生的新线程也是守护线程

2.3优先级线程

每⼀个线程都是有优先级的

⼀般来说,⾼优先级的线程在运⾏时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。

thread1.getPriority();//获得优先级
thread1.setPriority(2);//设置优先级

我们可以定义线程的优先级,但是这并不能保证⾼优先级的线程会在低优先级的线程前执⾏。线 程优先级是⼀个int变量(从1-10),1代表最低优先级,10代表最⾼优先级。 java的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关,如⾮特别需要,⼀般⽆需设置线程优先 级。

2.4线程生命周期

2.4.1线程的五种状态

img

  1. 新建状态(new): 线程对象被创建后就进入了新建状态。如:Thread thread = new Thread();
  2. 就绪状态(Runable): 也被称为“可执行状态”。线程对象被创建后,其他线程调用了该对象的start()方法,从而启动该线程。如:thread.start(); 处于就绪状态的线程随时可能被CPU调度执行。
  3. 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
  4. 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权限,暂时停止运行。直到线程进入就绪状态,才有机会进入运行状态。阻塞的三种情况:
  • 等待阻塞: 通过调用线程的wait()方法,让线程等待某工作的完成。
  • 同步阻塞: 线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。
  • 其他阻塞: 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。
  1. 死亡状态(Dead): 线程执行完了或因异常退出了run()方法,该线程结束生命周期。
关于线程中的start()和run()

**启动线程使用start(),而不是run()! **

img

在执行start()方法之前,只是有一个Thread对象,还没一个真正的线程。(分配内存,初始化成员变量)

——>start()之后,线程状态从新状态到可执行状态。(调用栈和计数器,线程没运行,只是可以运行)

——>当线程获得执行机会时,其目标run()方法将运行。

**start():**他的作用是启动一个新线程,新线程会调用相应的run()方法。start()不能被重复调用。

run():和普通成员的方法一样可以被重复调用。单独调用run()会在当前线程中执行run(),而不会启动新的线程。

run()和start()方法区别:

  • run():仅仅是封装被线程执行的代码,直接调用是普通方法
  • start():首先启动了线程,然后再由jvm去调用该线程的run()方法。
?关于阻塞
关于死亡

img

—>调用run或call方法执行完成,正常结束

—>线程抛出一个为捕获的Exception或Error

—>直接调用stop()方法来结束该线程——容易导致死锁,不推荐。

不要试图对死亡的线程使用start()方法,将会抛出异常,并不会重启死亡的异常!

参考资料: https://blog.csdn.net/qq_30604989/article/details/80163366

2.4.2线程状态的切换方法
sleep()方法——线程休眠

调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态! 要等待CPU调用。

try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
wait()方法——线程等待

wait()的作用也是能够让线程从“运行状态”进入“休眠(阻塞)状态”,同时释放同步锁。但是sleep不会释放同步锁

notify()方法——唤醒等待线程
join()方法

让“主线程”线程等待“子线程”运行完之后再运行。

在主线程中调用子线程s.start()启动子线程并调用s.join(),在调用s.join()之后主线程会一直等待,直到子线程运行完毕之后才接着运行。

class MyThread1 extends Thread{
    public MyThread1(String name) {
        super(name);
    }
    @Override
    public void run(){
        System.out.println(this.getName()+" start\n");
        //延时操作
        for (int i = 0; i < 1000; i++) {

        }
        System.out.println(this.getName()+"finish\n");
    }
}
public class Demo extends Thread {
    public static void main(String[] args) throws InterruptedException {
        MyThread1 t = new MyThread1("myThread");
        t.start();
        t.join();
        System.out.println(currentThread().getName()+" finish");
    }
}


//结果
myThread start
myThreadfinish

main finish
yield()方法

和sleep类似,让线程从“运行状态”到“就绪状态”,但是不会阻塞线程,只是让当前线程停一会儿,让同优先级的其他线程获得被执行的机会,但是不保证一定会被执行。

yield&wait

1)wait让线程从“运行状态”到“阻塞状态”,yield让线程从“运行状态”到“就绪状态”

2)wait会释放同步锁,yield和sleep一样不会释放同步锁
下一篇:多线程——线程池

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值