[Java进阶] 线程相关

本文详细介绍了Java中的线程概念,包括进程与线程的区别、线程的创建(通过Thread、Runnable、Callable接口)以及线程的常用方法。还深入讨论了线程的内存结构、共享资源、线程安全问题的解决(如加锁策略、死锁)、线程的六种状态以及线程池的创建和使用。通过实例展示了如何创建线程池,以及线程池的两种创建方式。
摘要由CSDN通过智能技术生成

目录

线程概念

创建线程

常用方法

内存结构

共享资源

解决线程不安全问题(加锁)

加锁过程中的异常

死锁

线程的六种状态

线程池(创建线程池的两种方式)


线程概念

进程:

1. 是指内存中运行的应用程序        

2. 每个进程都有一个独立的内存空间        

3. 一个应用程序可以同时运行多个进程

线程:

1. 是指进程中的一个子程序        

2. 进程中的一条执行路径/执行单元        

3. 线程也有独立的内存空间

并行: 同一时刻, 多个程序一起执行

并发: 同一时刻,多个程序交替执行

创建线程

1. 如何创建线程对象

public class ThreadDemo extends Thread{
    //1.要让自定义线程和thread有联系
    //2.重写线程中的run方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName()+"跑了:"+i+"米.");

        }
    }

    public ThreadDemo(String name) {
        super(name);
    }

    public ThreadDemo() {
    }
}  

class Test {
    public static void main(String[] args) {
        //3.创建自定义线程对象
        ThreadDemo threadDemo = new ThreadDemo();
        //4.调用线程的start方法, 启动线程
        threadDemo.start();

    }
}
  


2. 使用Runnable创建线程任务, 解耦合

public class RunnableDemo implements Runnable{
//    1.实现Runnable接口
//    2.实现run方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
//            Thread.currentThread().setName("李四");
//            使用Thread类中的静态获取方法,调用的线程名
            System.out.println(Thread.currentThread().getName()+"执行了"+i+"次循环.");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Test {
    public static void main(String[] args) {
        //Thread的静态方法.currentTread获取当前线程对象 , 从而获取线程名/ 设置线程名
//        3.创建Runnable线程任务
//        4.创建Thread线程, 将线程任务放进去
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start();

        //也可以通过线程的构造参数 , 传入线程名
        Thread thread1 = new Thread(runnableDemo,"张三");
        thread1.start();

    }
}

3.使用Callable接口, 执行带有返回值的线程任务

public class CallableTask implements Callable<Integer> {
//    1.创建Callable实现类, 实现Callable接口, 明确泛型
//    2.实现call方法
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum+=i;
        }
        return sum;
    }
}

class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
//       3.创建Callable实现类对象
        CallableTask callableTask = new CallableTask();
//       4.封装一个FutureTask 装载带有返回值的Callable对象
// 因为thread的start方法没有返回值, call是有返回值的, 因此需要futureTask进行包装
        FutureTask<Integer> futureTask = new FutureTask<>(callableTask);
//       5.创建thread线程对象, 装载futureTask对象
        Thread thread = new Thread(futureTask);
//       6.启动线程
        thread.start();
//       7.使用futureTask的get方法, 获取到返回值
        Integer integer = futureTask.get();

        System.out.println(integer);
    }
}

常用方法

设置 / 获取线程对象的名字

Thread.currentThread().setName("李四");
Thread.currentThread().getName();

Tread线程对象的sleep(): 让当前执行代码的线程休眠指定毫秒数

存在编译器异常需要try..catch处理

try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
}

为什么Thread和Runnable中不能抛出异常, 而main方法却可以?

实现类实现接口/ 子类继承父类:
方法重写里面子类不能抛出比父类更大的异常.如果父类的方法里面没抛. 子类重写不能抛. 
父类方法声明的时候抛出了异常. 子类重写可以抛也可以不抛. 

内存结构

MyThread myThread = new MyThread();
myTread.start();

创建一个线程对象, 调用start(), 通知Java虚拟机为当前线程开辟一个线程栈用于存储当前线程的数据 , 栈内存相当于主线程的线程栈

共享资源

使用Thread类重写run方法, 是不能完成同一个线程任务的两个线程共享资源的效果的

使用Runnable接口, 共享资源:

1. 将共享资源定义成(静态)成员变量

2. 初始化成员变量, 或者创建包含该成员变量的构造方法

解决线程不安全问题(加锁)

多线程在访问共享资源的过程中会出现线程不安全的问题:

线程不安全问题出现的前提:   1. 多线程        2.有共享资源        3.对共享资源修改

加锁的三种方式: 同步代码块/ 同步方法 / lock锁.

同步代码块: 锁对象只能是多线程公用的对象, 一人一个锁达不到加锁的效果

同步方法: 将加锁的部分抽取出来成一个锁方法

lock锁: 将锁封装成对象, 通过手动调用加锁和解锁的方法控制锁的范围

加锁过程中的异常

在同步代码块和同步方法中遇到运行时异常, 会终止当前线程,释放锁和cpu执行权, 其他线程仍能继续执行

在使用lock锁对象时遇到运行时异常, 是没有机会执行到解锁方法的, 没有释放锁和cpu执行权, 其他线程也就不能继续执行, 因此程序停止...

针对这种情况, 我们需要无论如何也要让解锁的方法执行.try...catch...finally 代码块就符合这种要求,finally也经常应用在一些释放资源的场景中

public class TryCatchFinallyDemo1 implements Runnable{

    private int ticket = 100;
    private static final Lock LOCK =new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                //一直卖票 , 窗口不关
                LOCK.lock();
                if (ticket>0){
                    //当票卖完, 则不卖了
                    try {
                        //模拟卖票等待时间
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (Thread.currentThread().getName().equals("窗口B")&& ticket==50){
                        throw new RuntimeException("窗口B 正在卖第50张票!");
                    }
                    //当使用lock锁遇到异常时, 来不及运行释放锁的代码
                    //这个时候可以使用try catch finally 将无论如何也要执行的释放锁代码 放在finally中
                    System.out.println(Thread.currentThread().getName()+":当前再买第"+ticket+"张票.");
                    ticket--;
                }
            } finally {
//                将无论如何也要执行的释放锁代码 放在finally中
                LOCK.unlock();
            }
        }
    }
}

 finally和return的应用

public class Test {
    public static void main(String[] args) {
        int result = finallyReturn();
        System.out.println(result);

    }
    public static int finallyReturn(){
        try {
            return 100;
        }finally {
            return 200;
        }
    }
}

 这段代码执行的时候是会先返回100到缓存区, 最后执行finally返回200,将之前的返回值冲掉 

死锁

概念:

存在两把或以上的锁, 多个线程持有对方线程需要的锁, 又在等对方释放锁

发现死锁:

使用java工具 jps查看线程栈编号, 再通过jstack 栈编号 查看对应线程状态,以及锁情况

处理:

1.加锁: 在最外层加一把锁, 谁拿到谁先执行(性能低)

2.减锁: 存在无意间多加锁的情况

3.加锁的顺序: 统一加锁的顺序, 避免卡死

线程的六种状态

  • New: 创建新线程,还没有调用start()
  • Runnable: 就绪状态,已经调用start(), 在抢夺和抢夺后执行中都是Runnable状态
  • Blocked: 阻塞状态,当线程需要的锁被其他线程抢了, 进入阻塞状态,不会再抢cpu执行权, 当锁被释放,所有处于等待这把锁的线程都会变成就绪状态, 重新争夺锁和cpu执行权
  • Waiting: 无限等待, 直到被唤醒进入就绪状态,进入无限等待的线程会释放cpu执行权,也释放锁
  • Timed Waiting:计时等待, 计时结束进入就绪状态, 进入计时等待的线程会释放cpu执行权,但不释放锁
  • Terminated: 终止状态,等待回收

线程池

通过使用线程池中的线程, 大大降低了创建销毁线程所浪费的资源, 当我们需要一个线程时, 从线程池中取出, 用完再放回, 提高代码复用性

创建线程池的两种方式

1.使用Executors工具类的方法创建线程:

public ExecutorService newFixedThreadPool(int n): 创建一个指定线程数可重用的线程池

public class ThreadPoolDemo {
    public static void main(String[] args) {
//      使用工具类创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
//          循环使用线程中的线程执行线程任务
            pool.submit(()->System.out.println(Thread.currentThread().getName()+"线程池中的线程执行了!"));
        }
        //线程池中需要手动停止  不然会一直运行等待接收线程任务
        pool.shutdown();
    }
}

2.自定义线程池通过创建ThreadPoolExecutor对象(7个参数)

1. 核心线程数 (CPU密集型: 建议核心数+1; IO密集型: 建议核心数*2)

2. 最大线程数 (核心线程数+临时线程数= 总和)

3. 临时线程存活时间 (存活具体数值)

4. 临时线程存活时间 (存活单位 eg: TimeUnit.SECONDS)

5. 阻塞队列 (有界阻塞队列 / 无界阻塞队列)

6. 线程工厂 (通过使用Executor的静态方法defaultThreadFactory获得线程工厂)

7. 拒绝策略 (AbortPolicy / DiscardPolicy / DiscardOldPolicy / CallerRunsPolicy)

public class ThreadPoolDemo2 {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(5,10,30, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());

        for (int i = 0; i < 10; i++) {
            pool.submit(()-> System.out.println(Thread.currentThread().getName()+"执行了"));
            Thread.sleep(200);
        }

        pool.shutdown();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值