并发编程(一)

1.进程和线程的概念

1>什么是进程:站在操作系统角度,进程是程序运行资源分配(以内存为主)的最小单位。

2>什么是线程:线程是cpu调度的最小单位。

3>二者的关系:进程包含多个线程,可以认为是父与子的关系,父亲可以没有儿子(C中可以没有

如 NGINX 只有多进程,Java中一定有 因为JVM本身就是一个进程),但是儿子肯定有父亲。

2.进程之间的通信有几种方式

1>管道:分为匿名管道(pipe)及命名管道(named pipe):匿名管道可用 于具有亲缘关系的父子进程间的通信,命名管道除了具有管道所具有的功能外, 它还允许无亲缘关系进程间的通信

2>信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较 复杂的通信方式,用于通知进程有某事件发生

3>消息队列

4>共享内存 

5>信号量

6>套接字(socket)

3.CPU的核心数和线程数之间的关系 

线程是CPU调度的最小单位,同一时刻一个CPU核心只能运行一个线程,即八核CPU可以同时执行8个线程(这就是为什么要用多线程的原因,单线程太浪费多核服务器了不是么?),但是iIntel 引入了超线程技术后,产生了逻辑处理器的概念,使核心数和线程数形成了1:2 的关系 , 我们可以用

Runtime.getRuntime().availableProcessors()

获取到当前的逻辑处理器数目,如下图所示

 

 4.上下文切换(context Switch :不同线程执行程序的位置和对应数据不同,所以需要切换)
暂停一个进程的处理,并将该进程的 CPU 状态(即上下文)存储在内存中的 某个地方
从内存中获取下一个进程的上下文,并在 CPU 的寄存器中恢复它
返回到程序计数器指示的位置(即返回到进程被中断的代码行)以恢复进程
5.并发和并行的区别

    并行是同时进行的,并发是交替进行的

6.Java中的线程

1>Java程序天生就是多线程的?

当我们启动一个main方法时,他会有几个线程?上代码

package com.concurrent;

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

public class Onee {
    public static void main(String[] args) {
        //当一个程序只有一个main方法时 它有几个线程?
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();//jvm线程管理接口
        //不需要获取同步的monitor和synchronizer的信息 只获取线程和线程的堆栈信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println(threadInfo.getThreadId() + ":" + threadInfo.getThreadName());
        }

    }


}

结果如下图所示:

[6] Monitor Ctrl-Break //监控 Ctrl-Break 中断信号的

[5] Attach Listener //内存 dump,线程 dump,类信息统计,获取系统属性等

[4] Signal Dispatcher // 分发处理发送给 JVM 信号的线程
[3] Finalizer // 调用对象 finalize 方法的线程
[2] Reference Handler// 清除 Reference 的线程
[1] main //main 线程,用户程序入口

由此可见Java程序天生就是多线程的!

2>线程的启动 与 终止

启动方式:

1.        X extends Thread 然后 通过 X.start() 

2.        X implements  Runnable , 然后交给Thread 运行

         

上代码:

package com.concurrent;

/**
 * 类说明:新启线程的方式
 */
public class CreateThread {

    private static class Thread_1 extends Thread {
        @Override
        public void run() {
            //do my work
            System.out.println("extends Thread");
        }
    }

    private static class Thread_2 implements Runnable {
        @Override
        public void run() {
            //do my work
            System.out.println("implements Runnable");
        }
    }

    public static void main(String[] args) {
        Thread_1 thread_1 = new Thread_1();
        thread_1.start();
        Thread_2 thread_2 = new Thread_2();
        new Thread(thread_2).start();
        System.out.println("main end!");
    }

}

 Thread 和 Runnable 的区别 Thread 才是 Java 里对线程的唯一抽象,Runnable 只是对任务(业

务逻辑) 的抽象。Thread 可以接受任意一个 Runnable 的实例并执行。

3> Callable 、Future、 FutureTask 

Runnable 是一个接口,在它里面只声明了一个 run() 方法,由于 run() 方法返 回值为 void 类型,所
以在执行完任务之后无法返回任何结果。
Callable 位于 java.util.concurrent 包下,它也是一个接口,在它里面也只声明 了一个方法,只不过
这个方法叫做 call() ,这是一个泛型接口, call() 函数返回的类型就是传递进来的 V 类型。
Future 就是对于具体的 Runnable 或者 Callable 任务的执行结果进行取消、查询是否完成、获取
结果。必要时可以通过 get 方法获取执行结果,该方法会阻塞 直到任务返回结果。

因为 Future 只是一个接口,所以是无法直接用来创建对象使用的,因此就 有了FutureTask

FutureTask 类实现了 RunnableFuture 接口,RunnableFuture 继承了 Runnable 接口和 Future

口,而 FutureTask 实现了 RunnableFuture 接口。所以它既可以 作为 Runnable 被线程执行,又

可以作为 Future 得到 Callable 的返回值。

 

因此我们通过一个线程运行 Callable ,但是 Thread 不支持构造方法中传递 Callable 的实例,所以
我们需要通过 FutureTask 把一个 Callable 包装成 Runnable , 然后再通过这个 FutureTask 拿到
Callable 运行后的返回值。
上代码:
package com.concurrent;

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

public class UseCallable {
    private static class UsCallable implements Callable<Integer> {
        private int sum = 0 ;

        @Override
        public Integer call() throws Exception {
            for (int i = 0; i < 501; i++) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("Callable 子线程执行任务中断!");
                    return null;
                }
                sum = sum+i;
                System.out.println("sum = " + sum);
            }
            System.out.println("子线程计算结束!结果为" + sum);
            return sum;
        }
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        UsCallable usCallable = new UsCallable();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(usCallable);
        new Thread(futureTask).start();
        //未来的某一时刻 获取到运行结果
        Thread.sleep(1);
        Random random = new Random();
        if (random.nextInt(100) > 25) {
            System.out.println(futureTask.get());
        }

    }

}

问:创建线程的方式有几种?

官方说法是在 Java 中有两种方式创建一个线程用以执行,一种是派生自 Thread 类,另一种是实
Runnable 接口。 当然本质上 Java 中实现线程只有一种方式,都是通过 new Thread() 创建线程
对象,调用 Thread#start 启动线程。 而线程池的方式,本质上是池化技术,是资源的复用,和新
启线程没什么关系。 所以,比较赞同官方的说法,有两种方式创建一个线程用以执行。

4> 中止

线程自然终止
要么是 run 执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
suspend()、resume() 和 stop() 暂停 恢复 停止 为什么是过期且不建议使用呢?
以suspend 为例 在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠

状态,这样容易引发死锁问题 ;stop 这个方法 在终结一个线程时不会保证线程的资源正常释放,

通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下,比如写

文件时会让文件损坏

通过gpt 翻译官网(Java Thread Primitive Deprecation)的原话 如下

 2 interrupt()的使用

安全的中止则是其他线程通过调用某个线程 A 的 interrupt() 方法对其进行中断操作,中断好比其他线程对该线程 A 打了个招呼:“A,你要中断了”,不代表线程 A 会立即停止自己的工作,同样的 A 线程完全可以不理会这种中断请求。

线程通过检查自身的中断标志位是否被置为 true 来进行响应,线程可以使用 isInterrupted() 方法来判断是否被中断,也可以调用静态方法 Thread.interrupted() 来判断当前线程是否被中断,不过 Thread.interrupted() 会同时将中断标识位改写为 false。

如果一个线程处于了阻塞状态(如线程调用了 thread.sleep、thread.join、thread.wait 等),则在线程在检查中断标示时如果发现中断标示为 true,则会在这些阻塞方法调用处抛出 InterruptedException 异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为 false。

不建议自定义一个取消标志位来中止线程的运行。因为 run 方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,

一、一般的阻塞方法,如 sleep 等本身就支持中断的检查,

二、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

注意:处于死锁状态的线程无法被中断。

上代码:

package com.concurrent;

/**
 * @Author coder huang
 * @Date 2023 08 06 17 56
 **/
public class EndThread {
    private static class UseRunnable implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()
                    + " interrupt flag is " + Thread.currentThread().isInterrupted());
            while (!Thread.currentThread().isInterrupted()) {
                //资源释放的业务代码
                System.out.println(Thread.currentThread().getName()
                        + " I am implements Runnable.");
            }
            System.out.println(Thread.currentThread().getName()
                    + " interrupt flag is " + Thread.currentThread().isInterrupted());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        UseRunnable useRunnable = new UseRunnable();
        Thread endThread = new Thread(useRunnable, "endThread");
        endThread.start();
        Thread.sleep(20);
        endThread.interrupt();
    }

}

深入理解 run() 和 start()方法

Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()其实只是创建了一个 Thread 的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()方法后,才实现了真正意义上的启动线程。

从 Thread 的源码可以看到,Thread 的start()方法中调用了start0()方法,而start0()是个 native 方法,这就说明Thread#start一定和操作系统是密切相关的。start()方法让一个线程进入就绪队列等待分配CPU,分到CPU后才调用实现的run()方法。start()方法不能重复调用,如果重复调用会抛出异常(注意,此处可能有面试题:多次调用一个线程的start方法会怎么样?)。

run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值