线程与进程

进程

一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程(至少有一个),比如window系统中运行的WeChat.exe就是一个进程。

进程是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位,至少有 5 种基本状态,它们是:初始态,执行态,等待状态,就绪状态,终止状态。

在这里插入图片描述

线程

线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

比如我们开了一个进程Word,写字,自动保存就是线程负责的

JAVA天生就是多线程程序,默认至少有两个线程main gc

JAVA能不能开启线程?
不能,上源码

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

Java开启线程是调用的本地方法,而本地方法的底层是C++,JAVA无法直接操作硬件。

进程和线程的区别

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

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

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:系统为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

并发与并行

并发:多线程操作同一个资源

  • CPU一核,模拟出来多条线程,天下武功,唯快不破,快速交替执行

并行:多个线程一起执行

  • CPU多核,多个线程可以同时执行

并发编程的本质:充分利用CPU的资源

线程的状态

public enum State {
        //新生
        NEW,

        //运行
        RUNNABLE,

        //阻塞
        BLOCKED,

        //等待,死死地等
        WAITING,

        //超时等待
        TIMED_WAITING,

        //终止
        TERMINATED;
    }

线程创建的方法

  • 继承 Thread
  • 实现 Runable
  • 实现 Callable

1、继承java.lang.Thread, 重写run()方法

package com.yizhan.demo;

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

class ExThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

2、实现java.lang.Runnable接口,重写run()方法,然后使用Thread类来包装

package com.yizhan.demo;

public class Demo2 {
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        }).start();
    }
}

两种方式都是围绕着Thread和Runnable,继承Thread类把run()写到类中,实现Runnable接口是把run()方法写到接口中然后再用Thread类来包装, 两种方式最终都是调用Thread类的start()方法来启动线程的。
两种方式在本质上没有明显的区别,在外观上有很大的区别,第一种方式是继承Thread类,因Java是单继承,如果一个类继承了Thread类,那么就没办法继承其它的类了,在继承上有一点受制,有一点不灵活,第二种方式就是为了解决第一种方式的单继承不灵活的问题,所以平常使用就使用第二种方式

3、 实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread
Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕

package com.yizhan.callable;

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

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // new Thread(new Runnable()).start();
        // new Thread(new FutureTask<V>()).start();
        // new Thread(new FutureTask<V>( Callable )).start();
        // new Thread().start(); // 怎么启动Callable

        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread);

        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();   //结果被缓存,效率高,所以只打印一个call()

        Integer o = (Integer) futureTask.get(); //这个get 方法可能会产生阻塞!把他放到最后
        // 或者使用异步通信来处理!
        System.out.println(o);

    }
}

class MyThread implements Callable<Integer>{

    @Override
    public Integer call(){
        System.out.println("call");  // 会打印几个call
        //耗时的操作
        return 1024;
    }
}

三种方式比较:

  • Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活

  • Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制

  • Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行

  • 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程

  • Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现

Thread常用方法

1. start() 与 run()

start()

package com.yizhan.demo;

public class Demo4 {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + i);
            }
        },"A").start();

        new Thread(()->{
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + i);
            }
        },"B").start();
    }
}

start(): 启动一个线程,线程之间是没有顺序的,是按CPU分配的时间片来回切换的。
在这里插入图片描述
run()

package com.yizhan.demo;

public class Demo4 {
    public static void main(String[] args) {
        new Thread(()->{
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + i);
            }
        },"A").run();

        new Thread(()->{
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + i);
            }
        },"B").run();
    }
}

执行结果都是main主线程
在这里插入图片描述
run(): 调用线程的run方法,就是普通的方法调用,虽然将代码封装到两个线程体中,可以看到线程中打印的线程名字都是main主线程,run()方法用于封装线程的代码,具体要启动一个线程来运行线程体中的代码(run()方法)还是通过start()方法来实现,调用run()方法就是一种顺序编程不是并发编程。

2. sleep() 与 interrupt()

sleep(long millis): 睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到就会立刻执行(因为睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行)。

  • sleep(): 睡眠指定时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到就要醒需要使用interrupt()来随时唤醒
  • interrupt(): 唤醒正在睡眠的程序,调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常,当sleep()方法抛出异常就中断了sleep的方法,从而让程序继续运行下去
package com.yizhan.demo;

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class Demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡个5秒");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t醒了");
            }
        },"A");
        thread.start();

        //这里的睡眠保证让上面线程先执行
        TimeUnit.SECONDS.sleep(5);

        new Thread(()->{
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t您起来吧");
            // 无需获取锁就可以调用interrupt
            thread.interrupt();
        }).start();
    }
}

在这里插入图片描述
3. wait() 与 notify()
wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中可以通过this或者super来调用this.wait(), super.wait()

  1. wait(): 导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
  2. wait(long timeout): 时间到了自动执行,类似于sleep(long millis)
  3. notify(): 该方法只能在同步方法或同步块内部调用,随机选择一个(注意:只会通知一个)在该对象上调用wait方法的线程,解除其阻塞状态
  4. notifyAll(): 唤醒所有的wait对象

4. sleep() 与 wait()

1、来自不同的类

wait  -> Object
sleep  -> Thread

2、关于锁的释放

wait 会释放锁
sleep抱着锁睡觉,不会释放

3、使用范围

wait 必须在同步代码块中
sleep想在哪里睡就在哪里睡

4、是否需要捕获异常

wait 不需要捕获异常
sleep需要捕获异常

5. join()
让当前线程加入父线程,加入后父线程会一直wait,直到子线程执行完毕后父线程才能执行。当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。

将某个线程加入到当前线程中来,一般某个线程和当前线程依赖关系比较强,必须先等待某个线程执行完毕才能执行当前线程。一般在run()方法内使用

package com.yizhan.demo;

public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 1; i <=6 ; i++) {
                System.out.println(Thread.currentThread().getName()+i);
            }
        },"A");

        thread.start();

        thread.join();

        for (int i = 0; i < 6; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
    }
}

main线程会一直等待thread执行完才会执行
在这里插入图片描述

6. yield()
交出CPU的执行时间,不会释放锁,让线程进入就绪状态,等待重新获取CPU执行时间,yield就像一个好人似的,当CPU轮到它了,它却说我先不急,先给其他线程执行吧, 此方法很少被使用到,

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值