java 之 Thread

本文详细介绍了Java中的线程概念,包括线程的并发原理、Thread类的使用、线程同步、线程间通信、死锁避免、JDK1.5后的ReentrantLock和线程池的运用,以及线程的五种状态。通过实例解析了如何创建和管理线程,以及如何利用线程池提高程序性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.简介

一个进程可以有多个线程,例如打开一个手机app就是开了一个进程,在这个进程中可以使用多个线程;

线程就像是代码执行的路线,多线程就是多条代码可同时执行;例如百度搜索肯定是多线程的,不然只能一个人访问;在例如服务器的后台被客户端访问,肯定也是多线程处理客户端的请求的;迅雷下载也是多线程的;jvm也是多线程的,main线程和gc线程;

当然也有单线程的,例如:android的更新UI是单线程的

2.原理

  • 并发:开启多个线程,但是CPU只有一个啊。其实CPU是在这个多个线程中来回的切换,便面上是同时执行的。
  • 并行:除非是多核的CPU。所以多线程并行执行任务的话必须多核处理,才不会存在CPU在线程间切换;

3.实现:Thread

在java中Thread就是一个线程类

创建一个线程可以直接new Thread并实现Thread类中run方法;也可以传一个Runable接口进入,然后实现Runable接口中的run方法;

常用的方法:

getName();返回一个线程的名字,线程默认的编号,从0开始;注意这个名字不是线程的id;也可以在创建线程的时候传一名字,获取的就是这个名字;

currentThread();返回当前线程的对象;这是一个静态的方法;可以通过类名调用;

sleep();线程休眠,传一个时间进去,休眠时间;

join();当前线程暂停,等待指定线程执行结束的时候,当前线程再继续执行;就是插队,可以指定插队时间;

setPriority();设置线程的优先级;

wait();其实这个方法是Object的,所有的类都会有这个方法,那个线程调用了任何对象的wait()都会线程等待;

notify();这个方法也是Object的,所有的类都会有的,线程调用了任何对象的notify()都会唤醒等待监听器上的单个线程,如果有多个线程在等待的话,会随机唤醒一个;

notifyAll();这个方法也是Object的,所有的类都会有的,线程调用了任何对象的notify()都会唤醒等待监听器上的所有线程;

sleep();线程休眠,可以传入参数,休眠时间;

sleep和wait的区别:

  1. sleep必须传入参数,这个参数就是休眠的时间,过了时间线程就醒来了;wait可以传参也可以不传参,传参是过多少时间后休眠,不传参数的话就是立刻等待;
  2. 在同步方法和代码块中sleep不会释放锁子;但是wait会释放锁子;

4.线程同步;

线程并发的时候不想让cpu在执行某一块代码的时候cpu切换,使用的同步代码块;

 public void hahah()
    {
        //这个this是同步的锁子,是任意的对象都是可以的;
        synchronized (this)
        {
            System.out.print("aaa");
        }
        //代码执行到这里就吧锁子this释放了
    }
    public void gegege()
    {
        //这个this是同步的锁子,是任意的对象都是可以的;
        //比如先执行的hahah(),当hahah中没有释放锁子的时候,cpu执行权切换到这个方法是,检查这个锁子是锁的
        //这个方法被调用的线程就阻塞在这里了;直到hahah把锁子释放掉;
        synchronized (this)
        {
            System.out.print("bbb");
        }
        //代码执行到这里就吧锁子this释放了
    }

同步方法:

  //当两个线程同事调用这个bbb方法的时候就要使用同步方法;
    public synchronized void bbb()
    {
        System.out.print("bbb");
    }

那么静态的同步锁是什么呢?是类的字节码对象

    public void gegege()
    {
        //比如先执行的bbb(),当hahah中没有释放锁子的时候,cpu执行权切换到这个方法是,检查这个锁子是锁的
        //这个方法被调用的线程就阻塞在这里了;直到bbb把锁子释放掉;
        synchronized (test.class)
        {
            System.out.print("bbb");
        }
        //代码执行到这里就吧锁子this释放了
    }

    //静态的方法,锁子是类的字节码对象test.class;
    //当两个线程同事调用这个bbb方法的时候就要使用同步方法;
    public static synchronized void bbb()
    {
        System.out.print("bbb");
    }

死锁:两个线程中我获取你的锁,你获取我的锁就形成了死锁。是在同步代码块嵌套使用的时候出现的;为了避免死锁,不要使用同步代码块嵌套;



关于一些类的线程安全的信息:


上面的类在多线程使用的时候要注意安全性;Collections.synchroinzed();将不安全的ArrayList等传进去就可以变成安全的;

5.线程间通信(等待唤醒机制)

下面代码是一个小例子,开启两个线程使用线程等待和唤醒机制的方式交替打印两句话:

    public static void t2() {
        String[] content1 = new String[]{"a", "b", "c", "d","e", "f", "g", "h"};
        String[] content2 = new String[]{"1", "2", "3", "4","5", "6", "7", "8"};
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < content1.length; i++) {
                    synchronized (Test2.class) {
                        System.out.print(content1[i]);
                        Test2.class.notify();
                        try {
                            if (i < content1.length - 1) {
                                Test2.class.wait();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < content1.length; i++) {
                    synchronized (Test2.class) {
                        System.out.print(content2[i]);
                        Test2.class.notify();
                        try {
                            if (i < content2.length - 1) {
                                Test2.class.wait();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();
    }

输出结果:a1b2c3d4e5f6g7h8

要用test.class.wait()和test.class.notify;用什么对象锁,就用什么对象去调用wait和notify;这句是为什么这个两个方法定义在Object中,因为锁子是任意对象,Object是任意类的基类;

wait() 和 同步代码块执行完成后,这两种情况会释放锁子;

6.在JDK1.5新特性中出现了互斥锁(ReentrantLock)

在reentrantLock类中有

lock();是获取锁子;

unLock()是释放之前获取的锁子;

newCondition(); 获取线程的监视器;返回一个Condition对象;在Condition中有await和signal方法等待和唤醒线程;

可以唤醒指定的线程,不像notify随机唤醒;

这个样就可以从新设计上面交替打印的例子了:

    public static void t() {
        ReentrantLock lock = new ReentrantLock();
        Condition c1 = lock.newCondition();
        Condition c2 = lock.newCondition();
        LinkedList<Integer> queue = new LinkedList<>();
        String[] content1 = new String[]{"a", "b", "c", "d","e", "f", "g", "h"};
        String[] content2 = new String[]{"1", "2", "3", "4","5", "6", "7", "8"};
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < content1.length; i++) {
                    lock.lock();
                    try {
                        System.out.println(content1[i]);
                        c2.signal();
                        if (i < content1.length - 1) {
                            c1.await();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.unlock();
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < content1.length; i++) {
                    lock.lock();
                    try {
                        System.out.println(content2[i]);
                        c1.signal();
                        if (i < content1.length - 1) {
                            c2.await();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.unlock();
                }
            }
        }.start();
    }

7.线程组

比如在主线程中有开启了两个线程,那么这两个线程就默认是主线程组中的线程。当然也可以创建一个线程组,然后在创建多个线程添加到这个线程组中去,有些操作可以一组一组的去做;例如下面设置这个线程组为守护线程

8.线程的5种状态

9.线程池

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。

Exectors.newFixedThreadPool(int size):创建一个固定大小的线程池。 每来一个任务创建一个线程,当线程数量为size将会停止创建。当线程池中的线程已满,继续提交任务,如果有空闲线程那么空闲线程去执行任务,否则将任务添加到一个无界的等待队列中。

        ExecutorService executorService = Executors.newFixedThreadPool(4);//创建一个池子,里面有4个线程
        executorService.submit(new Runnable());//执行一个任务
        executorService.shutdown();//关闭线程池

Exectors.newCachedThreadPool():创建一个可缓存的线程池。对线程池的规模没有限制,当线程池的当前规模超过处理需求时(比如线程池中有10个线程,而需要处理的任务只有5个),那么将回收空闲线程。当需求增加时则会添加新的线程。
Exectors.newSingleThreadExcutor():创建一个单线程的Executor,它创建单个工作者线程来执行任务,如果这个线程异常结束,它会创建另一个线程来代替。
Exectors.newScheduledThreadPool():创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务。
上面都是通过工厂方法来创建线程池,其实它们内部都是通过创建ThreadPoolExector对象来创建线程池的。下面是ThreadPoolExctor的构造函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值