并发编程(一)

92 篇文章 0 订阅
10 篇文章 0 订阅

进程& 线程

  • 进程: 进程是用来加载指令, 管理内存和 IO的. 当一个程序被运行, 同时, 程序代码会从磁盘加载到内存, 这就是开启了一个进程. 可以理解为进程是程序的一个实例
  • 线程: 一个进程内, 会产生一或多个线程, 一个线程就是一个指令流, 在程序运行时, 会将各个指令流中的指令, 按一定的顺序交给 CPU执行
  • 二者区别:
    (-) 进程基本上是相互独立的, 而线程存在于进程内, 是进程的一个子集
    (-) 线程通信相比进程通信简单及轻量, 因为一个进程内的线程之间是共享内存的. 而进程间的通信较为复杂 1. 同一台计算机的进程通信称为 IPC(Inter-process communication) 2. 不同计算机之间的进程通信, 是需要通过网络进行
  • 线程之运行原理:
    *程序运行过程中, 为了并发执行多个任务多线程会频繁的上下文切换(Thread Context Switch)
    (*) Java程序中, 当上下文切换时, 通过程序计数器(Program Counter Register)来记录当前线程执行的字节码指令的地址, 并恢复另一个线程的运行
    (-) Java引起线程上下文切换的原因是 1. 线程的 CPU时间片用完 2. 垃圾回收 3. 有高优先级的线程需要运行时 4. 线程内调用 sleep, yield, wait, join, park, synchronized, lock等方法
  • 本地观察进程:
  1. Windows:
    (-) 查看进程: tasklist|findstr java
    (-) 杀死进程: taskkill /F /PID xxx
  2. Linux:
    (-) 查看所有进程: ps -ef
    (-) 按大写 H切换是否显示线程: top
    (-) 查看某个进程的所有线程: ps -fT -p <PID>或 top -H -p <PID>
    (-) 杀死进程: kill <PID>
  3. Java:
    (-) 查看 Java所有进程
    (-) 查看某个进程的所有线程信息: jstack <PID>
    (-) 图形界面型式, 查看某个进程中线程的运行情况
  • 远程监控:
    (-) Java项目开启参数: java -Djava.rmi.server.hostname=ip地址 -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=连接端口 -Dcom.sun.management.jmxremote.ssl=是否安全连接 -Dcom.sun.management.jmxremote.authenticate=是否认证 java类

并发(Concurrent)& 并行(Parallel)

  • 操作系统中有一个组件叫做任务调度器(JVM中与此对应的的是程序计数器), 它将 CPU的时间片(Windows的时间片最小约15毫秒)分给不同的程序使用, 每过15毫秒, CPU会切换不同的程序来保持每个程序的状态
  • 并发: 单核 CPU下, 线程轮流执行称为并发
  • 并行: 在多核 CPU下, 不同核的线程可以同时处理指令, 此时可以视为并行

同步(Synchronous)& 异步(Asynchronous)

  • 当程序执行过程中, 必须等到结果返回, 才能继续执行是同步, 否则就是异步
    *如果没有线程的调度机制, 代码执行过程都会是同步的, 也就是说多线程可以将方法执行变为异步的

线程的五种状态(操作系统层面描述线程对象)

  1. 初始状态: 仅是在语言层面创建了线程对象, 还未与操作系统线程关联
  2. 可运行状态(就绪状态): 指该线程已经被创建(已经与操作系统的线程关联), 可以由 CPU调度执行
  3. 运行状态: 指当前拥有 CPU时间片的运行中的状态. 当 CPU时间片用完, 则会从运行状态变为可运行状态, 会引起线程的上下文切换
  4. 阻塞状态: 当做 BIO操作时, 当前线程从运行状态转换至阻塞状态, 也就是会引起线程上下文切换, 直到阻塞操作结束. 会由操作系统的任务调度器唤醒该阻塞的线程, 将状态转换至可运行状态
  5. 终止状态: 线程执行完毕, 不再转换为其它状态

线程的六种状态(Java API层面描述线程对象)

  • 根据 Thread.State枚举, 分为六种状态
  1. NEW: 线程刚被创建, 但还未调用 start()方法
  2. RUNNABLE: 当调用 start()方法后, Java API层面的 RUNNABLE状态涵盖了, 操作系统层面的可运行状态, 运行状态, 阻塞状态(Java里无法区分 BIO导致的线程阻塞, 而仍然认为是可运行)
  3. BLOCKED: 阻塞状态
    (3-1): 等待执行 synchronized代码块或者使用synchronized标记的方法
    (3-2): 在 synchronized块中, 循环调用 Object对象的 wait()方法
  4. WAITING: 阻塞状态
    (4-1): 调用 Object对象的 wait()方法, 但没有指定超时值
    (4-2): 调用 Thread对象的 join()方法, 但没有指定超时值
    (4-3): 调用 LockSupport对象的 park()方法
  5. TIMED_WAITING: 阻塞状态
    (5-1): 调用 Thread.sleep方法
    (5-2): 调用 Object对象的 wait()方法, 指定超时值
    (5-3): 调用 Thread对象的 join()方法, 指定超时值
    (5-4): 调用 LockSupport对象的 parkNanos()方法
    (5-5): 调用 LockSupport对象的 parkUntil()方法
  6. TERMINATED: 当线程代码运行结束, 也就是终止状态

Java 线程状态转换过程

在这里插入图片描述

假设有线程 Thread t
1: NEW --> RUNNABLE

  • 当调用 t.start()方法时, 由 `NEW --> RUNNABLE

2: RUNNABLE <--> WAITING

  • t线程用 synchronized(obj)获取了对象锁后:
    (-) 调用 obj.wait()方法时, t线程从 RUNNABLE --> WAITING
    (-) 调用 obj.notify(), obj.notifyAll(), t.interrupt()时:
    –> 竞争锁成功, t线程从 WAITING --> RUNNABLE
    –> 竞争锁失败, t线程从 WAITING --> BLOCKED

3: RUNNABLE <--> WAITING

  • 当前线程调用 t.join()方法时, 当前线程从 RUNNABLE --> WAITING
    –> *注: 当前线程在 t线程对象的监视器上等待
  • t线程运行结束, 或调用了当前线程的 interrupt()时, 则当前线程从 WAITING --> RUNNABLE

4: RUNNABLE <--> WAITING

  • 当前线程调用 LockSupport.park()方法, 会让当前线程从 RUNNABLE --> WAITING
  • 调用 LockSupport.unpark(目标线程), 或调用了线程的 interrupt(), 会让目标线程从 WAITING --> RUNNABLE

5: RUNNABLE <--> TIMED_WAITING

  • t线程调用 synchronized(obj)获取了对象锁后:
    (-) 调用 obj.wait(long n)方法时, t线程从 RUNNABLE --> TIMED_WAITING
    (-) t线程等待时间超过了 n毫秒, 或调用 obj.notify(), obj.notifyAll(), t.interrupt()时:
    –> 竞争锁成功, t线程从 TIMED_WAITING --> RUNNABLE
    –> 竞争锁失败, t线程从 TIMED_WAITING --> BLOCKED

6: RUNNABLE <--> TIMED_WAITING

  • 当前线程调用 t.join(long n)方法时, 当前线程从 RUNNABLE --> TIMED_WAITING
    –> *注: 当前线程在 t线程对象的监视器上等待
  • 当前线程等待时间超过了 n毫秒, 或 t线程运行结束, 或调用了当前线程的 interrupt()时, 当前线程从 TIMED_WAITING --> RUNNABLE

7: RUNNABLE <--> TIMED_WAITING

  • 当前线程调用 Thread.sleep(long n), 当前线程从 RUNNABLE --> TIMED_WAITING
  • 当前线程等待时间超过了 n毫秒, 当前线程从 TIMED_WAITING --> RUNNABLE

8: RUNNABLE <--> TIMED_WAITING

  • 当前线程调用 LockSupport.parkNanos(long nanos)或 LockSupport.parkUntil(long millis)时, 当前线程从 RUNNABLE --> TIMED_WAITING
  • 调用 LockSupport.unpark(目标线程), 或调用了线程的 interrupt(), 或是等待超时, 会让目标线程从 TIMED_WAITING--> RUNNABLE

9: RUNNABLE <--> BLOCKED

  • t线程用 synchronized(obj)获取了对象锁时, 如果竞争失败, 则 RUNNABLE --> BLOCKED
  • 持 obj锁线程的同步代码块执行完毕, 会唤醒该对象上所有 BLOCKED的线程重新竞争, 如果其中 t线程竞争成功, 则 BLOCKED --> RUNNABLE, 其它失败的线程仍然 BLOCKED

10: RUNNABLE <--> TERMINATED

  • 当前线程所有代码运行完毕, 进入 TERMINATED

创建线程实例

*建议使用 Runnable来实现多线程, 因为用它可以将“任务”代码和“线程”执行代码分离, 以此保证代码扩展及对接一些如线程池等高级 API时更灵活


	// 方式1
    public static void main( String[] args ) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                System.out.println("test!");
            }
        };
        t1.start();
    }
	// 方式2
    public static void main( String[] args ) {
        Runnable task1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("test!");
            }
        };
        Thread t1 = new Thread(task1, "t1");
        t1.start();
    }
	// 方式3
    public static void main( String[] args ) {
        Thread t1 = new Thread(() -> {
            System.out.println("test!");
        }, "t1");
        t1.start();
    }
	// 方式4
    public static void main( String[] args ) {
        Runnable task1 = () -> System.out.println("test!");
        Thread t1 = new Thread(task1, "t1");
        t1.start();
    }

线程阻塞直到获取结果(同步等待)

  • 通过 FutureTask类, 实现有返回值的情况

public FutureTask(Callable<V> callable)
(-) FutureTask类实现了 Future接口, 它是通过 get()方法获取任务返回值的
(-) 参数 Callable是处理返回结果的接口


	public static void main( String[] args ) {
        FutureTask<Integer> task1 = new FutureTask<>(() -> {
            System.out.println("test!");
            TimeUnit.SECONDS.sleep(10);
            return 999;
        });
        new Thread(task1, "t1").start();
        try {
			// 同步等待直到拿到结果
            Integer result = task1.get();
            System.out.println("结果:" + result); // 结果:999
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

线程类常用方法

方法功能说明注意事项
static native Thread currentThread()获取当前线程
static native void yield()让当前工作中的线程, 让出 CPU的使用1. 调用 yield()会让当前线程的状态从 Running改为 Runnable"就绪状态", 然后调度执行其它线程 2. 主要用于测试和调试
static native void sleep(long millis)休眠(n毫秒)当前执行的线程. 让出 CPU的时间片, 给其它线程1. 调用 sleep()会让当前线程的状态从 Running改为 Timed Waiting"阻塞状态", 然后调度执行其它线程 2. 睡眠结束后的线程未必会立刻得到执行
void start()启动一个线程-该方法, 只是让线程进入就绪, 也就是任务代码不一定立刻运行(分配 CPU时间片时, 会有一定延时) -每个线程对象的 start方法只能调用一次, 如果调用了多次会出现 IllegalThreadStateException
void run()线程启动后会自动调用的方法, 任务代码放到此方法中如果未执行 start(), 而执行了 run(), 则会是同步执行任务代码
void interrupt()打断线程1. 如果打断线程正在 sleep, wait, join时, 会引起线程抛出异常 InterruptedException, 并清除"打断标记" 2. 如果打断正在运行的线程, 则会设置"打断标记" 3. park的线程被打断, 也会设置"打断标记"
static boolean interrupted()判断当前线程是否被打断判断后"打断标记"会被清除
boolean isInterrupted()判断是否被打断判断后"打断标记"会被保留
native boolean isAlive()线程是否存活
void setPriority(int newPriority)修改线程优先级java中规定线程优先级是1~10的整数, 越大优先级越高, 越能提高被 CPU调度的机率
int getPriority()获取线程优先级如果 CPU比较忙, 那么优先级高的线程会获得更多的时间片, 但 CPU比较闲时, 优先级几乎没作用
void setName(String name)修改线程名称
String getName()获取线程名称
void join(long millis)等待另外线程运行结束, 最多等待 n毫秒
void join()等待另外线程运行结束
void setDaemon(boolean on)设置该线程为守护线程
boolean isDaemon()判断当前线程是否为守护线程
long getId()获取线程 id唯一 id
State getState()获取线程状态Java中线程状态有6种表示, 分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
void stop()停止线程运行(已过时)原因为容易破坏同步代码块, 造成线程死锁
void suspend()挂起(暂停)线程运行(已过时)原因为容易破坏同步代码块, 造成线程死锁
void resume()恢复线程运行(已过时)原因为容易破坏同步代码块, 造成线程死锁

例子


	// 演示: join 同步等待直到线程运行结束
    static int r1 = 0;
    static int r2 = 0;
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r1 = 10;
        });

        Thread t2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r2 = 20;
        });
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("r1: " +r1+ " r2: " +r2+ " cost time: " + (end - start)); // r1: 10 r2: 20 cost time: 2004
    }

	// 演示: 线程 sleep, wait, join中, interrupt()打断线程
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.MILLISECONDS.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        System.out.println("打断状态");
        t1.interrupt();
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前状态: " +t1.isInterrupted());
    }
	打断状态
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at org.example.base.threads.App2.lambda$main$0(App2.java:43)
	at java.lang.Thread.run(Thread.java:748)
当前状态: false // 可以看到"标记"被清除

	// 演示: interrupt()打断运行线程, 通过 isInterrupted()判断线程是否被打断
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while(true) {
                Thread current = Thread.currentThread();
                boolean interrupted = current.isInterrupted();
                if(interrupted) { // 循环判断, 当前线程是否被打断
                    System.out.println("当前状态: " +interrupted);
                    break;
                }
            }
        }, "t1");
        t1.start();
        try {
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 0.5秒后打断了线程
        t1.interrupt();
    }

	// 演示: 线程被 park()后, interrupt()打断线程, 引起唤醒
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("park, 当前状态: " +Thread.currentThread().isInterrupted());
            LockSupport.park();
            System.out.println("unpark, 当前状态: " +Thread.currentThread().isInterrupted());
        }, "t1");
        t1.start();
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
park, 当前状态: false
unpark, 当前状态: true // 1秒后, 打断线程, 输出 unpark

	// 演示: interrupted()判断后"打断标记"会被清除
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("park, 当前状态: " +Thread.currentThread().isInterrupted());
                LockSupport.park();
                //System.out.println("unpark, 当前状态: " +Thread.currentThread().isInterrupted());
                System.out.println("unpark, 当前状态: " +Thread.interrupted()); // 此方法调用一次后打断状态会被清除
            }
        });
        t1.start();
        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.interrupt();
    }
park, 当前状态: false
unpark, 当前状态: true
park, 当前状态: false

	// 演示: 守护线程
	- 默认情况下, Java进程需要等待所有线程都运行结束, 才会结束. 但有一种特殊的线程叫做守护线程, 只要其它非守护线程都运行结束了, 即使守护线程的代码没有执行完, 也会强制结束; 如垃圾回收器的线程和 Tomcat中的 Acceptor和 Poller线程都是守护线程, 所以 Tomcat接收到 shutdown命令后, 不会等待它们处理完当前请求
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1线程运行结束!");
        }, "t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2线程运行结束!"); // 由于 t1线程和主线程运行结束, 导致 t2(守护线程)终止了运行, 也就是未输出此信息
        }, "t2");
        t2.setDaemon(true); // 设置该线程为守护线程
        t2.start();
    }
t1线程运行结束!

如果您觉得有帮助,欢迎点赞哦 ~ 谢谢!!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值