Skip to content

只写一些Java特有的.


使用线程的方法

第一种:继承Thread

官方文档示例

a6ab46ab427864cb0103233a43c8c1c9_MD5

示例

demo

f4a5ba3bae334ceae5432acae372e691_MD5 - 注意,t1调用的不是run方法,而是start方法. - run相当于注册了方法,start是启动线程

setName

用于给线程设置名字.当然也可以getName e22dbeb2708b7a96151575033edf5671_MD5 由于Java只能单继承,因此继承了Thread就不能继承其他的了.这是它的弊端.

第二种:实现Runnable接口

官方文档示例

33b89db7d20b6e73cd41856a991d0ee4_MD5 注意,我们要把我们的对象传给一个Thread对象.

示例

demo

176fa28516c99ff2fb5e92508fbd0904_MD5

setName

注意,setName/getName都是Thread的方法,因此我们不能在run()里面直接调用. 我们可以使用Thread.currentTread这个静态方法,获取当前线程的对象,然后得到对应的线程.

第三种:利用Callable和Future接口

是对前两种方式的补充.我们可以看到前两种的run都没有返回值.而第三种可以.

过程

2da4864cd1412c20de460bc0b29d794a_MD5 开始实现: d7e5e3237afd618550dbc169a8f0f701_MD5 这个的泛型V就表示返回值的类型. 302c518532100faa835dd6f30894b5f7_MD5 FutureTask是Future接口的实现类.表示用来托管mc的结果.如果我们想要运行多个线程获得多个结果,那么就需要创建多个FutureTask,传入到不同的Thread.


常见的成员方法

2eaf1ac9dca1a7afe9000ecb0746b1a0_MD5

线程默认名字

如果线程没有起名字,那么getName会返回默认名字.默认名字是Tread-X,X表示序号,从0开始.

构造方法

构造方法也能起名字: fa90df0c47bdc2092031fdf27ddd0f87_MD5

main线程

feb1ba34cb696d1eada14f0da788e3e7_MD5

sleep

哪个线程调用Thread.sleep,哪个线程睡眠.

抢占式调度

java采用抢占式调度.优先级为1-10,默认为5(不论自己写的线程还是主线程都是这样). 优先级越高,越可能被选择.

守护线程

把一部分线程设成守护线程之后,当所有非守护线程结束时,守护线程会立刻开始陆续结束,不论是否执行完成. 注意,不是立刻结束,只是陆续结束.

出让线程/礼让线程

调用Thread.yield()后,会出让执行权.

插入线程/插队线程

在某个线程1当中调用线程对象2的.join后,那么线程1会阻塞住,等待线程2执行完,才会往后执行. 这样就解释了为什么我们经常在main函数join子线程. 上面对于线程2来说,就相对于总是在插线程1的队,让它自己总是比线程1先执行.


线程安全

同步代码块synchronized

写法:

daaf328481d9ffdb7012c93fc76da333_MD5 锁对象可以是任意的(不仅仅是像下面的代码这样用Object),但是必须保证唯一.因此我们可以设成static. f4d46ebed15c552456ce29f531fc540d_MD5 如果锁对象不是唯一的,那么就相当于不同的锁.(比如在这里我把obj换成this,由于三个线程的this不一样,因此就有三把锁) 我们一般为了保证唯一,其实一般是写MyThread.class(字节码文件对象),这一般是唯一的.

同步方法

c6dc8796573d8fb3b703491935be04bd_MD5 特点 82c2cfb1a62217c75d2c8cc880cace59_MD5 这相对于把同步代码块中的代码抽出来单独成方法了. 这里实际上是提出了一种写共享代码的方法: 1.循环(死循环) 2. 同步代码块 3. 判断共享数据是否到了末尾(到了末尾) 4. 判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)

runnable的误区

7b133b7b6bfe946f45f8d94e812c2e46_MD5 如图,我们虽然有三个线程,但是实际上只有一个runnable对象.因此,ticket这个变量其实是三个线程共享的,不需要加static. 这里相当于三个线程同时调用同一个runnable对象的run方法. 这上面也好理解:这个mr对象是在堆上的,那么三个线程都是可以看到同一个堆的. 改成同步方法: d803d7ce527d182c0820303119a80579_MD5

  • 注意如果我们使用那种继承Thread的写法,就不能用同步方法代替同步代码块了.因为那三个线程有三个不同的this.
  • 因此我们可以看出,java这里的runnable比较符合原有的印象.

StringBuffer

StringBuffer和StringBuilder的方法一模一样.但是,前者是线程安全的,后者是不安全的. 实际上,只是StringBuffer上各个方法是同步的. 5bf6d6f76b48f88ea2973e61f39890b1_MD5

锁对象Lock

于jDK5出现. 36af56828ef1bfff7bb7c3df5a86415b_MD5 3e4ff54fcd63fd2e44dadb6cef962c0a_MD5 tip:这样写有个大bug:当循环break之后,锁没有释放,导致死锁. 一般的标准写法是:把所有的加锁代码放到trycatch块当中,然后在finally里添加unlock.这样,就能保证无论如何锁都能释放.

死锁的样例

12a50fdcd23d179d98739d4850399864_MD5 面试喜欢问.


等待唤醒机制

生产消费模型

notify

其实就是条件变量 aec125ef7b5fb5de7faf9374302d3463_MD5] 这些方法都是在锁对象底下等待的. 消费者: 7cf97947f603f3c73fc7bc0aac3e2cf3_MD5 我们可以看出,任何一个对象成为锁对象之后,就会拥有上面的方法. 生产者: 935ac1cabafa4c2a272a504baafc1447_MD5 调用: 4a27e5ec68d48f0bf20cb69ff329638d_MD5

阻塞队列

60b850971ab8297fbae1a0317e829b77_MD5 这两个实现类都实现了上面四个接口.最常用的方法就是put tack 42df3ee3a97c35b3775cc7e66b9f6724_MD5 - 这里也解答了一个疑惑:如何给某个线程传递数据 这两个类天然就是线程安全的: 0c5e4d3804fb6690b9a1108526205cf7_MD5 因此,这样写就好: c8c7f4204139cfe2f72f2a14c68c9cb0_MD5 17cfc0e4b45024843d3006f294050869_MD5

  • tip:打印语句是线程不安全的.

线程状态

1f2dcd8832db472f7948a97ce4734865_MD5 但是实际上,没有运行这个状态.API帮助文档 6f7c74147ba5ec1eca7a58b476476914_MD5 为什么没有定义运行状态,因为线程就绪之后,java就把它们交给os管理了,因此没有运行状态.


线程池

Executors

5711699264820b29ddb28b05f2de2b43_MD5 其实也有上限,超过就会崩溃. 450eb8149816ba75bd414567be39b7f1_MD5 462b433e6393ff09d35e89b38f7bff90_MD5 线程池有一批准备就绪的线程.每次submit就会交给某个线程一个任务. 对于有上限的线程池,那么最多就那么多线程,如果要运行的任务过多,那么有的任务就需要等待. 如果是没有上限的线程池,也不是submit多少次就使用多少个线程,而是在submit时会观察,是否有之前submit的任务结束空出来的线程.

自定义线程池

其实是executors的另一种构造函数.

理论

线程有核心线程和临时线程之分.线程池的最大线程数量=核心线程数+临时线程数 核心线程会一直等待任务进行,是常驻的.但是临时线程只会在任务过多时被创建,并且在空闲一段时间后会被删除. 一般来说,假如任务数超过了核心线程数,那么多的任务就会在阻塞队列当中进行阻塞. 当阻塞队列也塞满了之后,那么就会创建临时线程来处理新的任务. 如果临时线程也不够了,那么多的线程会触发任务拒绝策略.默认是丢弃并抛出异常 19a18051857726ae0038f26ab7b7d1cf_MD5

构造函数参数:

55c7de1186527a5d2f933b288b82deb7_MD5 默认工厂:实际上就是new了一个线程.注意,这里没有笔误 cde17273c05817c5e2e55b4a8d39cf64_MD5 7771ae7e532875e382ff74d255296e0f_MD5 79c230115870713858a52b501b887f50_MD5 其中,最后一个参数是内部类.

最佳参数

线程池大小公式: 91fc720cf4d6c790254ab2b773af07f5_MD5

什么是最大并行数

4核8线程:4个cpu,每个cpu利用超线程技术虚拟化出可以处理两个线程. 8就是最大并行数. 由于os一般不会把所有的资源都给程序,因此最稳定的就是利用代码获取. 3b9fd3ad5e912ac80adf0b67e2817144_MD5

大小

  • 为什么cpu密集计算要+1? 因为避免某个线程阻塞(如页缺失),这样多的线程就能顶上来
  • IO密集 最后一个分式,其实就是cpu计算时间占cpu总用时的比例的倒数 这个数据是可以通过thread dump来获得的.

未完待续:volatile JMM 悲观锁乐观锁 CAS 原子性


拓展: generate BY AI


在Java里,JUC指的是java.util.concurrent包。此包由Doug Lea设计并开发,于Java 5时被引入,目的是为Java提供强大的并发编程工具,让开发者能更便捷地处理多线程编程问题。

JUC包的核心功能与组件

1. 线程池(Executor框架)

线程池能够对线程的创建和管理进行优化,避免了频繁创建和销毁线程带来的性能损耗。其核心组件有: - ExecutorService:这是线程池的主要接口。 - ThreadPoolExecutor:线程池的具体实现类。 - ScheduledExecutorService:可执行定时任务的线程池。

下面是一个简单的线程池使用示例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);

        // 提交任务给线程池执行
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println("执行任务: " + taskId + ",线程: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("完成任务: " + taskId);
            });
        }

        // 关闭线程池
        executor.shutdown();
    }
}

2. 同步工具类

这些工具类用于线程间的协调与同步: - CountDownLatch:使一个或多个线程等待其他线程完成操作。 - CyclicBarrier:让一组线程相互等待,直到所有线程都到达某个屏障点。 - Semaphore:用于控制同时访问特定资源的线程数量。 - Exchanger:可实现两个线程间的数据交换。

下面是CountDownLatch的使用示例:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int workerCount = 3;
        CountDownLatch latch = new CountDownLatch(workerCount);

        // 创建并启动多个工作线程
        for (int i = 0; i < workerCount; i++) {
            final int workerId = i;
            new Thread(() -> {
                System.out.println("工作线程 " + workerId + " 开始工作");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("工作线程 " + workerId + " 完成工作");
                latch.countDown(); // 减少计数
            }).start();
        }

        // 主线程等待所有工作线程完成
        latch.await();
        System.out.println("所有工作线程已完成,主线程继续执行");
    }
}

3. 并发集合

JUC包提供了线程安全的集合类,相比Collections.synchronizedXXX包装的同步集合,性能更优: - ConcurrentHashMap:线程安全的哈希表实现。 - CopyOnWriteArrayList:在读多写少场景下表现出色的线程安全列表。 - ConcurrentLinkedQueue:无界的线程安全队列。 - BlockingQueue接口及实现类(如ArrayBlockingQueueLinkedBlockingQueue):支持阻塞操作的队列。

下面是ConcurrentHashMap的使用示例:

import java.util.concurrent.ConcurrentHashMap;

public class ConcurrentHashMapExample {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

        // 多个线程可以安全地向map中添加元素
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                map.put("key" + i, i);
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                map.put("key" + (i + 1000), i + 1000);
            }
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Map大小: " + map.size());
    }
}

4. 原子操作类(Atomic系列)

这些类借助CAS(Compare-and-Swap)操作实现原子操作,避免了使用锁带来的开销: - AtomicIntegerAtomicLongAtomicBoolean等。

下面是AtomicInteger的使用示例:

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicExample {
    private static AtomicInteger counter = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        int threadCount = 10;
        Thread[] threads = new Thread[threadCount];

        // 创建并启动多个线程
        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    counter.incrementAndGet(); // 原子操作
                }
            });
            threads[i].start();
        }

        // 等待所有线程完成
        for (Thread t : threads) {
            t.join();
        }

        System.out.println("最终计数: " + counter.get()); // 输出应为10000
    }
}

5. 锁机制

JUC包提供了比synchronized更灵活的锁实现: - ReentrantLock:可重入锁,支持公平锁和非公平锁。 - ReentrantReadWriteLock:读写锁,允许多个读线程同时访问共享资源。 - LockSupport:用于阻塞和唤醒线程。

下面是ReentrantLock的使用示例:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private static final ReentrantLock lock = new ReentrantLock();
    private static int sharedResource = 0;

    public static void main(String[] args) throws InterruptedException {
        int threadCount = 2;
        Thread[] threads = new Thread[threadCount];

        for (int i = 0; i < threadCount; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    lock.lock(); // 获取锁
                    try {
                        sharedResource++;
                    } finally {
                        lock.unlock(); // 释放锁
                    }
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("共享资源值: " + sharedResource); // 输出应为2000
    }
}

6. 并发工具类

  • FutureCallable:用于异步计算,获取计算结果。
  • CompletableFuture:提供了更强大的异步编程功能。
  • ForkJoinPoolForkJoinTask:用于并行执行任务,采用工作窃取算法。

下面是CompletableFuture的使用示例:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "异步任务结果";
        });

        // 任务完成后执行后续操作
        future.thenAccept(result -> System.out.println("接收到结果: " + result));

        // 主线程可以继续执行其他操作
        System.out.println("主线程继续执行");

        // 等待异步任务完成并获取结果
        String result = future.get();
        System.out.println("最终结果: " + result);
    }
}

JUC包的优势

  • 简化并发编程:JUC包提供了高层抽象,降低了直接操作线程的复杂度。
  • 性能优化:通过高效的算法和数据结构,减少了锁的使用,提升了并发性能。
  • 丰富的功能:满足了多样化的并发场景需求,如异步计算、定时任务、生产者-消费者模型等。

总之,JUC包是Java并发编程的核心工具包,它极大地简化了多线程编程,提升了代码的可靠性和性能。