多线程学习笔记(一)

您所在的位置:网站首页 单核多线程有意义吗 多线程学习笔记(一)

多线程学习笔记(一)

2024-07-10 11:57| 来源: 网络整理| 查看: 265

1. 相关概念

程序: 操作系统可以执行的文件

进程: 操作系统找到可执行文件后,把相关信息load到内存中,称之为进程

进程是操作系统进行资源分配的基本单位 线程是调度执行的基本单位 多个线程共享进程的资源

2. 单核CPU设定多线程是否有意义

有, 并不是所有的操作都消耗CPU, 比如有些操作等待网络数据, 或者sleep

有的线程属于CPU密集型,有的属于IO密集型

3. 工作线程数是不是设置的越大越好

不是, 线程之间的切换也会消耗资源

4. 线程数设多少最合适

1). 看cpu配置,几核 2). 根据实际情况进行压测 3). 公式:N = CPU核数*,(0~1)*(1+等待时间与计算时间的比率)

CPU核数可以通过Runtime.getRuntime().avaliableProcessprs()得到

等待时间与计算时间的比率:可以通过profiler得到 profiler是性能分析工具,是一类工具 java常用的是JProfiler, 但是收费 生产环境可以用arthas

CPU密集型:N+1

IO密集型:2N+1  

5. 创建线程的方式

1). 继承Thread类, 重写run方法

启动: new 创建的Thread,然后.start()

2). 实现Runnable接口,重写run方法

启动:new Thread(new 实现Runnable接口的类),然后.start()

>>以上两种实现多线程的方法, 哪种更合适?

第二种, 实现Runnable接口的方式更合适. 因为一个类值只能继承一个类, 可以实现多个接口

3) 使用Lambda表达式

new Thread(() -> { // TODO }).start();

4) 使用线程池

ExecutorService service = Executors.newCachedThreadPool(); service.execute(()->{// TODO }); service.shutdown();

5) 带返回值的任务执行

实现Callable接口, 重写call方法,可以通过泛型指定返回值

①通过线程池

Future f = service.submit(new MyCall()); String s = f.get(); // 该方法可以获取返回值,并且该方法是阻塞的

②不通过线程池

FutureTask task = new FutureTask(new MyCall()); Thread t = new Thread(task); t.start(); String s = task.get();

import java.util.concurrent.*; public class TreadTest { public static void main(String[] args) throws ExecutionException, InterruptedException { /** * 创建多线程的5种方式 * 1. 继承Thread类 * 2. 实现Runnable接口 * 3. lambda表达式 * 4. 线程池 * 5. callable */ // 1. 继承Thread类 new MyThread().start(); // 2. 实现Runnable接口 new Thread(new MyRunnable()).start(); // 3. lambda表达式 new Thread(()->{ System.out.println("lambda..."); }).start(); // 4. 线程池 ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(()->{ System.out.println("线程池..."); }); // 5.1 利用线程池实现Callable 带返回值 Future submit = executorService.submit(new MyCallable()); // 获取返回值,阻断性的 System.out.println(submit.get()); // 5.2 不利用线程池实现Callable FutureTask futureTask = new FutureTask(new MyCallable()); Thread thread = new Thread(futureTask); thread.start(); String s = futureTask.get(); System.out.println(s); } } class MyThread extends Thread{ @Override public void run() { System.out.println("MyThread..."); } } class MyRunnable implements Runnable{ @Override public void run() { System.out.println("MyRunnable..."); } } // 返回值为String类型 class MyCallable implements Callable{ @Override public String call() throws Exception { return "callable result..."; } }

6. 线程的状态

1) new 

当一个线程刚刚被new出来,还没有调用start方法,线程处于new状态

2) runable(ready/running)

waiting进入runnable o.notify() o.notifyAll() LockSupport.unpark() Lock.unlock()

3) waiting

等待被唤醒

以下进入waiting状态 o.wait() t.join() LockSupport.park() Lock.lock()

4) timed waiting

隔一段时间自动唤醒

以下进入timed waiting状态 Thread.sleep(time) o.wait(time) t.join(time) LockSupport.parkNanos() LockSupport.parkUntil()

sleep(5)进入timed waiting状态

5) blocked

被阻塞, 等待进入synchronized同步代码块的锁

6) terminated

线程结束, run方法执行完毕,线程处于terminated状态

Thread.yield: 让出cpu资源

t1.join() 等待t1执行结束

ReentrantLock是JUC的锁,juc一般是用CAS实现的, 用CAS是一种盲等待, 盲等待不会进入blocked状态, 进入waiting状态

只有synchronized会进入blocked状态,其他的会进入waiting/timed waiting状态

synchronized是要经过操作系统的调度

7. 线程打断

interrupt()

打断某个线程(设置中断标志位)

isInterupted()

查询某个线程是否被打断过(查询标志位)

static interrupted()

查询当前线程是否被打断过, 并重置打断标志

注意***

interrupt并不会真正打断线程, 只是设置标志位, 具体的操作由线程自己决定

在执行sleep/wait/join方法时, 执行interrupt方法, 会抛出InterruptedException异常

抛出InterruptedException异常, java自动把标志位复位成false

获取synchronized锁时, 进行interrupt, 不会抛异常

获取ReentrantLock锁时(lock.lock()), 进行interrupt, 也不会抛异常

获取ReentrantLock锁时, 通过lockInterruptibly(lock.lockInterruptibly())获取锁, 再进行interrupt, 会抛异常

8. 如何优雅的结束一个线程

1) t.stop() 已被废弃

为什么不建议使用stop方法: 因为容易产生数据不一致的问题

2) suspend/resume 已被废弃(废弃原因同stop)

suspend暂停 resume恢复

3) volatile

用volatile修饰一个boolean类型的变量 private static volatile boolean running = true;

缺点:  依赖while循环中, 中间有状态的区分的, 不适应;  在循环中有 wait recv accept操作时, 会发生阻塞, 不能进入下一次循环, 所以结束不了 打断时间不是特别精确, 比如一个阻塞容器, 容量为5的时候结束生产者, 但是volitile同步线程标志位的时间控制不是很精确, 有可能生产者还能继续生产一段时间

优点:

适用于不依赖中间状态的 使用起来比较方便

4) interrupt

循环判断interrupt是否设置标志位, 设置了就退出 循环中如果有sleep/wait等方法时, 也可以结束线程(sleep/wait方法遇到interrupt会抛异常)

缺点: 也不精确, 时间上很难控制



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3