Java基础学习总结:多线程之(一)线程的三种创建方式与比较

1、并发和并行

并行(parallellism):

指两个或多个事件在同一时刻发生;

在同一时刻,有多条指令在多个处理器上同时执行,无论从微观还是从宏观来看,指令都是一起执行的。

并发(concurrency):

是指两个或多个事件在同一时间间隔发生;

指同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得宏观上具有多个进程同时执行的效果,但微观上并不是同时执行的,只是按时间片轮换策略快速的交替执行。 

2、进程和线程

(1)什么是进程

进程是一个正在运行的程序,是一个程序的运行状态和资源占用(内存,CPU)的描述,通过进程ID区分。 进程是程序的一个动态过程,它指的是从代码加载到执行完毕的一个完成过程。 目前操作系统支持多进程多任务。

进程的特点:

  1. 独立性:不同的进程之间是独立的,相互之间资源不共享(举例:两个正在上课的教室有各自的财 产,相互之间不共享)
  2. 动态性:进程在系统中不是静止不动的,而是在系统中一直活动的
  3. 并发性:多个进程可以在单个处理器上同时进行,且互不影响

(2)什么是线程

线程就是一条执行路径。是进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务。

线程的特点:

线程的执行是抢占式的,多个线程在同一个进程中可以并发执行,其实就是CPU快速的在不同的线程之间切换,

也就是说,当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行

(3)线程和进程的区别

根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位;

开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间切换开销较大;线程可以看做是轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(cp),线程之间切换开销小;

所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程中有一个或多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行);

内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,处了CPU外,系统不会为线程分配内存(线程所有的资源来自其所属的进程的资源),线程组之间共享资源。

包含关系:进程是线程的容器,只有一个线程的进程是单线程程序;有多个线程非线性执行的进程是多线程程序;线程也被称为轻权进程或者轻量级进程。

3、线程的三种创建方式

(1)继承 Thread 类

package basis.StuThread.Ticket_0;

public class TestMyThread {
    public static void main(String[] args) {
        System.out.println("main 方法。。。");
        //第二步,创建实例
        MyThread_1 myThread_1 = new MyThread_1();
        //开启线程
        myThread_1.start();
    }
}
//第一步:创建Thread的子类,重写run方法
class MyThread_1 extends Thread {
    @Override
    public void run() {
        System.out.println("run 方法。。。");
    }
}

(2)实现 Runnable 接口

package basis.StuThread.Ticket_0;

public class TestMyRunnable  {
    public static void main(String[] args) {
        System.out.println("main 方法。。。");
        //第二步,实例化一个Runnable的实现类
        MyThread_2 myThread_2 = new MyThread_2();
        //第三步,创建 Thread 对象,传入Runnable实现类的对象
        Thread thread = new Thread(myThread_2);
        //第四步,开启线程
        thread.start();
    }
}

//第一步创建Runnable的实现类,实现Runnable 接口的run方法
class MyThread_2 implements Runnable{
    @Override
    public void run() {
        System.out.println("run 方法。。。");
    }
}

(3)实现 Callable 接口

package basis.StuThread.Ticket_0;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class TestMyCallable {
    public static void main(String[] args) {
        System.out.println("main 方法。。。");
        //第二步,创建Callable实例类对象
        MyCallable<String> myCallable = new MyCallable<>();
        //第三步,创建FutureTask任务
        FutureTask<String> oneTask = new FutureTask<>(myCallable);
        //第四步,由FutureTask<Integer>创建Thread对象
        Thread oneThread = new Thread(oneTask);
        //第五步,开启线程
        oneThread.start();
    }
}

//第一步,创建Callablej接口实现类,重写call()方法,支持泛型
class MyCallable<String> implements Callable {
    @Override
    public Object call() throws Exception {
        System.out.println("call 方法。。。");
        return "MyCall";
    }
}

FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和 Runnable 接口。

4、start()方法和run()方法的区别

主要区别:

  • run() 方法是线程的任务处理的逻辑入口,star() 方法是启动对应的线程;
  • 线程的run() 方法是Java虚拟机直接调用的,如果我们没有启动线程(没有调用线程的start() 方法)而是在应用的代码中直接调用run() 方法,那么这个线程的run() 方法其实是运行在当前线程(即run() 方法的调用方法所在的线程)之中,而不是运行在自身的线程中,从而违背了创建线程的初衷。
  • start() 方法调结束并不表示该线程已经开始运行。

示例:

public class TestThread {
    public static void main(String[] args) {
        Thread thread_a = new SomeThread();
        thread_a.run();//输出结果:main
        System.out.println(Thread.currentThread().getName());//输出结果:main

        Thread thread_b = new SomeThread();
        thread_b.start();//输出结果:Thread-1

        //由结果看出 run() 方法没有另开线程,而 start() 方法才是真正的新开线程。
    }
}
class SomeThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

由结果看出 run() 方法没有另开线程,而 start() 方法才是真正的新开线程。 

5、继承Thread和实现Runnable的区别

实现Runnable接口的方式

  • 资源类实现了Runnable接口。如果资源类有多个操作,需要把功能提出来,单独实现Runnable接口。
  • 可以多个线程共享同一个资源,所以非常适合多个线程来处理同一份资源的情况
  • 弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()

继承Thread类的方式

  • 没有资源共享,编写简单,如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以 使用getName()获取线程名字。
  • 弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】

实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】【匿名内部类不能处理多个线程竞争共享资源的情况】

6、Runnable 和 Callable 的区别

Runnable 和 Callable的区别:

  • Callable 规定的方法是call(),Runnable 规定的方法是run();
  • Callable() 任务执行后可返回值,而 Runnable 的任务是不能有返回值的;
  • call() 方法可以抛出异常,run() 方法不可以;
  • 运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future 对象可以了解任务执行情况,可取消任务的执行,还可以获取执行结果。

7、卖票程序(无并发)

四个窗口,各卖100张票

window类

package basis.StuThread.Ticket_0;

public class Window extends Thread{
    private int ticket = 100;
    @Override
    public void run() {
        for (int i = 0;i<100;i++){
            System.out.println(this.getName()+"卖了第"+ticket+"张票");
            ticket--;
        }
    }
}

主类(测试类):

package basis.StuThread.Ticket_0;

public class Test {
    public static void main(String[] args) {
        new Window().start();
        new Window().start();
        new Window().start();
    }
}

内存图:

每开启一个线程,JVM会为线程开启一块独立的栈空间,该空间大小为1M。主线程即main()方法所在线程也是如此。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值