24.线程
发布日期:2025-03-30 22:11:05 浏览次数:9 分类:精选文章

本文共 8154 字,大约阅读时间需要 27 分钟。

线程基础

进程概念:运行中的程序

线程概念:是进程中的一条执行路径,往往一个进程中会有多条执行路径--多线程

JVM从main方法入口开始执行,这就是一个执行路径,我们叫做主线程;可以在主线程中,创建其他线程,我们叫做子线程

进程与线程的关系:

线程是CPU调度的基本单位,CPU切片切到谁就谁执行
一个进程可以包含多个线程,至少有一个线程
进程具有独立的资源空间,但是一个进程中的多个线程共享资源

线程的特点:随机互抢资源

线程的组成:

CPU时间片:由操作系统分配每个线程执行的时间
运行数据:
堆数据:实例化出来的线程对象
栈数据:线程引用指向实例化的对象

线程的逻辑代码

线程的创建方式:1.创建一个类继承Thread,2.创建一个类实现Runnable任务

线程创建方式1

案例:主线程和子线程,各打印200次(互抢资源方式)

//案例:主线程和子线程,各打印200次(互抢资源方式)
//分析:1.创建线程继承Thread类,重写run方法,该方法就是子线程执行的区域
//2.实例化子线程对象,调用run方法执行
class MyThread extends Thread {
@Override
public void run() {
for(int i=1;i<=200;i++) {
System.out.println("子线程执行-->"+i);
}
}
}
public class Test1 {
public static void main(String[] args) {
//线程的启动:将当前线程对象放入线程组供CPU调度,当CPU调度到你,在内部调用run方法;当CPU调度不到你,则是就绪状态
MyThread thread = new MyThread();
thread.start();
new MyThread().start(); /实例化一个线程对象,多次start() 不可以/
for(int i=1;i<=200;i++) {
System.out.println("主线程执行-->"+i);
}
}
}

线程创建方式2

案例:实现一个任务的方式

//创建线程方式2:实现一个任务的方式
class Task implements Runnable {
@Override
public void run() {
for(int i=1;i<=200;i++) {
System.out.println("子线程执行-->"+i);
}
}
}
public class Test2 {
public static void main(String[] args) {
Thread thread = new Thread(new Task());
thread.start();
for(int i=1;i<=200;i++) {
System.out.println("主线程执行-->"+i);
}
}
}

线程状态

线程可以处于以下几种状态:

  • 就绪状态:线程准备好要执行,但没有CPU调度
  • 运行状态:线程正在执行CPU任务
  • 等待状态:线程等待I/O操作完成,例如Blocking操作
  • �tagName状态:线程等待被唤醒
  • 停止状态:线程被停止执行
  • 线程的休眠方法:

    语法:Thread.sleep(毫秒)
    用法:可以用在主线程或子线程中
    目的:在线程中可以复现互抢资源的现象

    线程优先级设置:

    线程优先级设置:给线程设置优先级,可以大概率的确定谁先执行完;但不是绝对的
    MyThread thread1 = new MyThread("线程1"); thread1.setPriority(Thread.MIN_PRIORITY); thread1.start();
    MyThread thread2 = new MyThread("线程2"); thread2.setPriority(Thread.MAX_PRIORITY); thread2.start();

    线程的礼让:

    线程礼让:yield
    设置了礼让的线程,会将线程资源让一次出去,继续跟其他线程争抢
    class A extends Thread {
    @Override
    public void run() {
    for(int i=1;i<=200;i++) {
    System.out.println("设置礼让线程1===》"+i); Thread.yield();
    }
    }
    }
    同样的设置B

    线程的合并:

    线程的合并(插队):join
    在线程中设置了插队后,插队的线程绝对性地先执行完

    线程安全

    多线程存储数组元素的数据安全问题

    线程安全案例:在线程中给定一个数组,两个线程同时往数组中存元素

    分析:先执行的线程应该存储第一个下标位置,后执行的线程,应该存储第二个下标位置
    问题:还没来得及下标的累加,可能都存储到了第一个位置——因为线程具有随机互抢特性

    解决:

    问题1:打印数组时,可能数据还没有存储
    解决: 想个办法先执行子线程的存储;再执行主线程的打印---join
    问题2:new两个对象,线程中对象的属性都有两份,没办法共享数据
    解决:将线程的属性变为static
    问题3:数据出现问题,都存储到第一个位置了
    解决:加锁:同步代码块,同步方法

    class MyThread extends Thread {

    static String[] s = {"","","","",""};
    static int index;
    String value;
    public MyThread(String value) {
    this.value = value;
    }
    @Override
    public void run() {
    synchronized("lock") {
    s[index] = value;
    index++;
    }
    }
    }
    public class Test {
    public static void main(String[] args) throws InterruptedException {
    MyThread my1 = new MyThread("hello"); my1.start();
    MyThread my2 = new MyThread("world"); my2.start();
    my1.join(); my2.join();
    System.out.println(Arrays.toString(MyThread.s));
    }
    }

    继承Thread方式实现线程安全

    卖票问题:

    *问题1:每个对象都会卖1000张,共5000张
    解决方案:ticket属性+static修饰,变为了5个线程只卖1000张
    *问题2:出现部分重票的问题——数据安全问题
    解决方案:加锁
    锁的注意事项:

  • 同一把锁(静态属性,常量值)
  • 锁的范围,锁整个while还是while里面的语句
  • class MyThread extends Thread {

    private static int ticket = 1000;
    public MyThread(String name) { super(name); }
    @Override
    public void run() {
    while(true) {
    synchronized("lock") {
    if(ticket>0) {
    System.out.println(super.getName()+"窗口正在卖第"+ticket+"张票");
    ticket--;
    } else {
    System.out.println(super.getName()+"窗口已经卖完了");
    break;
    }
    }
    }
    }
    }
    public class Test2 {
    public static void main(String[] args) throws InterruptedException {
    MyThread myTicket1 = new MyThread("窗口1"); myTicket1.start();
    MyThread myTicket2 = new MyThread("窗口2"); myTicket2.start();
    myTicket1.join(); myTicket2.join();
    System.out.println("票已经卖完了");
    }
    }

    实现任务方式完成线程安全

    通过实现任务的方式完成卖票系统的案例:

    分析:创建一个Task类实现Runnable接口
    与继承Thread的区别:
    1.多个线程操作同一个Task任务,所以属性无需加static
    2.同步代码块的锁对象可以用this
    3.同步方法无需加static

    class Task implements Runnable {

    private int ticket = 1000;
    @Override
    public void run() {
    while(true) {
    synchronized(this) {
    if(ticket>0) {
    System.out.println(Thread.currentThread().getName()+"窗口正在卖第"+ticket+"张票");
    ticket--;
    } else {
    System.out.println(Thread.currentThread().getName()+"窗口已经卖完了");
    break;
    }
    }
    }
    }
    }
    public class Test3 {
    public static void main(String[] args) throws InterruptedException {
    ExecutorService es = Executors.newFixedThreadPool(5);
    for(int i=0;i<10;i++) { es.submit(new Task()); }
    es.shutdownNow();
    System.out.println("所有任务已提交");
    System.out.println("等待任务完成..."); es.awaitTermination(1, TimeUnit.SECONDS);
    }
    }

    死锁

    死锁案例:

    线程的双方都握着对方的资源,都退不出去,最后形成了死锁——锁嵌套
    创建一个类继承Thread,里面有一个属性,用于做判断的
    实例化两个线程,传递参数,1个为true,一个为false
    判断中,一个先执行A锁,一个先执行B锁的锁嵌套

    class MyThread extends Thread {

    private boolean flag;
    public MyThread(boolean flag) {
    this.flag = flag;
    }
    @Override
    public void run() {
    if(flag) {
    synchronized("A") {
    System.out.println(super.getName()+"--进入了A锁");
    synchronized("B") {
    System.out.println(super.getName()+"--进入了B锁");
    }
    }
    } else {
    synchronized("B") {
    System.out.println(super.getName()+"--进入了B锁");
    synchronized("A") {
    System.out.println(super.getName()+"--进入了A锁");
    }
    }
    }
    }
    }

    生产者消费者模型

    多线程模型:安全锁机制,等待与唤醒

    生产者负责生产
    消费者负责消费
    我们需要准备一个仓库存钱,消费者看库存,库存有钱才能消费
    库存没钱——等待消费
    库存满了——生产者停止生产,等待消费者消费后,有库存空间才生产

    //仓库类

    public class Store {
    private int num; //库存数量
    public void push() throws InterruptedException {
    synchronized(Store.this) {
    while(num>=20) {
    this.wait();
    }
    num++;
    System.out.println("生产者已经生产了第"+num+"件货");
    this.notify();
    }
    }
    public void pop() throws InterruptedException {
    synchronized(Store.this) {
    while(num<=0) {
    this.wait();
    }
    System.out.println("消费者已经消费了第"+num+"件货");
    num--;
    this.notify();
    }
    }
    }

    public class Test4 {

    public static void main(String[] args) {
    Store store = new Store();
    new Producter(store).start();
    new Customer(store).start();
    //添加更多生产者和消费者线程
    new Producter(store).start();
    new Customer/store>.start();
    }
    }

    线程高级功能

    线程池

    概念:可以认为是线程对象的容器,预先创建多个线程对象,然后根据这些对象可以实现复用

    good处:减少创建和销毁线程的数目,提升性能

    复用机制:用完了线程对象后,重新回收到线程池中

    //线程池的创建方式:

    ExecutorService es = Executors.newCachedThreadPool(); //带缓冲区的线程池
    ExecutorService es = Executors.newSingleThreadExecutor(); //单线程池
    ExecutorService es = Executors.newFixedThreadPool(2); //固定线程数的池

    public class Test5 {

    public static void main(String[] args) {
    ExecutorService es = Executors.newFixedThreadPool(2);
    Task task = new Task();
    es.submit(task);
    es.submit(task);
    es.submit(task);
    es.shutdown();
    while(!es.isTerminated()) { }
    }
    }

    Callable接口

    Callable接口:和Runnable类似,是处理任务的接口

    区别:Callable是带返回值的接口,可以将线程中处理完的数据返回

    Future接口:该对象接收submit的返回值,可以求出call返回值

    //案例:用两个线程计算1~50,51~100的和,并进行汇总

    ExecutorService es = Executors.newFixedThreadPool(2);
    Future
    future = es.submit(new Callable
    () {
    @Override
    public Integer call() throws Exception {
    int sum = 0;
    for(int i=1;i<=50;i++) sum +=i; return sum;
    }
    });
    Future
    future2 = es.submit(new Callable
    () {
    @Override
    public Integer call() throws Exception {
    int sum=0; for(int i=51;i<=100;i++) sum +=i; return sum;
    }
    });
    int sum = future.get() + future2.get();
    System.out.println("两个线程的和:"+sum);

    读写锁

    读写锁:一般用在读远远高于写的情况,可以提升线程执行的性能

    读写锁:在读与读之间是不互斥,不阻塞的,所以性能会提升

    //读写锁的使用案例

    public class MyClass {
    private ReadWriteLock lock = new ReadWriteLock();
    ReadLock rl = lock.readLock(); WriteLock wl = lock.writeLock();
    private Integer value;

    public Integer getValue() {  
    try {
    rl.lock(); return value;
    } finally { rl.unlock(); }
    }
    public void setValue(Integer value) {
    try {
    wl.lock(); this.value = value;
    } finally { wl.unlock(); }
    }

    }

    public class Test6 {
    public static void main(String[] args) {
    MyClass myClass = new MyClass();
    Runnable rt = new Runnable() { @Override public void run() { myClass.getValue(); } };
    Runnable wt = new Runnable() { @Override public void run() { myClass.setValue(1); } };
    ExecutorService es = Executors.newFixedThreadPool(20);
    for(int i=0;i<18;i++) es.submit(rt);
    for(int i=0;i<2;i++) es.submit(wt);
    es.shutdown(); while(!es.isTerminated()){}
    System.out.println(System.currentTimeMillis()-start);
    }
    }

    线程安全集合

    Collections工具类中提供了针对List,Set,Map的安全集合

    //同步列表的使用:

    List list = Collections.synchronizedList(new ArrayList());

    //优化并发安全集合:

    CopyOnWriteArrayList:安全且性能优越的手动维护的列表

    CopyOnWriteArraySet:类似CopyOnWriteArrayList,但相当于一个高级集合

    ConcurrentHashMap:线程安全的HashMap

    队列

    Queue队列接口:特点:先进先出

    //普通队列的实现:

    Queue queue = new LinkedList(); queue.offer(1); queue.offer(3); queue.offer(2);
    System.out.println(queue.peek()); System.out.println(queue.poll()); System.out.println(queue);

    ConcurrentLinkedQueue:

    上一篇:#Leetcode# 28. Implement strStr()
    下一篇:#3194. 去月球

    发表评论

    最新留言

    路过,博主的博客真漂亮。。
    [***.116.15.85]2025年04月22日 16时07分55秒