JAVA小白的崛起之路(十三)多线程

多线程

创建线程的三常见大方法:

继承Thread 类实现:

1、创建线程类:继承**Thread ** +重写run()方法

2、构建线程对象:创建子类对象

3、启动线程:通过子类调用start()

public class TestThread extends Thread {
    @Override
    public void run() {

        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread 实现多线程");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        TestThread testThread = new TestThread();
        testThread.start();
    }
}
实现Runnable 接口实现

1、创建实现Runnable接口的实现类+重写Run()方法

2、创建一个实现对象

3、利用实现类对象创建Thread类对象

4、启动线程

public class TestRunnable implements Runnable {

    @Override
    public void run() {

        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Runnable 实现多线程");
        }
    }
}
public class Test {
    public static void main(String[] args) {
         TestRunnable testRunnable = new TestRunnable();
        Thread thread=new Thread(testRunnable);
        thread.start();

    }
}
实现Callable接口实现

1、创建实现Callable接口的实现类+重写call()方法

2、创建一个实现类对象

3、由Callable创建一个FutureTask对象

4、由FutuerTask创建一个Thread对象

5、启动线程

public class TestCallable implements Callable {

    @Override
    public Object call() throws Exception {
        for (int i = 0; i <10 ; i++) {
            Thread.sleep(1000);
            System.out.println("Callable 实现多线程 "+(i+1));
        }
        return null;
    }
}

public class Test {
    public static void main(String[] args) {
      
        TestCallable testCallable = new TestCallable();
        FutureTask<Integer> futureTask=new FutureTask<>(testCallable);
        Thread thread1=new Thread(futureTask);
        thread1.start();
    }
}
  • Thread: 继承方式, 不建议使用, 因为Java是单继承的, 继承了Thread就没办法继承其它类了,不够灵活
  • Runnable: 实现接口,比Thread类更加灵活,没有单 继承的限制
  • Callable: Thread和Runnable都是重写的run()方法并且 没有返回值,Callable是重写的call()方法并且有返回 值并可以借助FutureTask类来判断线程是否已经执行 完毕或者取消线程执行
  • 当线程不需要返回值时使用Runnable,需要返回值时 就使用Callable,一般情况下不直接把线程体代码放 到Thread类中,一般通过Thread类来启动线程

用线程池创建(了解)

  1. 使用Executors类中的 newFixedThreadPool(int num)方法创建一个线 程数量为num的线程池
  2. 调用线程池中的execute()方法执行由实现 Runnable接口创建的线程;调用submit()方法执 行由实现Callable接口创建的线程
  3. 调用线程池中的shutdown()方法关闭线程池
线程池

概念:线程池就是首先创建一些线程,它们的集合称为线程 池。

作用:(1) 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;

​ (2) 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;

​ (3) 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳 定性,使用线程池可以进行统一的分配,调优和监控。

**创建:**常见的创建线程池:

newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。
newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。
newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。
newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

线程的五种状态

在这里插入图片描述

新建状态
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态
阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

同步阻塞:线程在获取 synchronized同步锁失败(因为同步锁被其他线程占用)。

其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
死亡状态

一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

线程状态的转变

如何控制线程进入终止状态:
1.正常运行结束 2.stop ,destroy() 过时 3.通过标识判断
如何进入就绪状态:
1.start 2.线程切换 3.yield 礼让线程 4.阻塞解除
如何进入阻塞状态
1.sleep 2.join 插队 3.wait 4.IO

sleep:"抱着"资源睡觉

sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是 sleep() 方法不会释放“锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据

yield:让出CPU资源

yield() 方法和 sleep() 方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield() 方法只是使当前线程重新回到可执行状态,所以执行yield() 的线程有可能在进入到可执行状态后马上又被执行。让出CPU的使用权,从运行态直接进入就绪态。让CPU重新挑选哪一个线程进入运行状态。

join:等另一个线程执行

方法会使当前线程等待调用 join() 方法的线程执行结束之后,才会继续往后执行。

注意:
一个线程如果进入阻塞状态,阻塞解除没有办法直接恢复到运行,会直接恢复就绪
一个线程如果一旦终止,没有办法恢复

sleep 线程休眠|睡眠 (ms)
静态方法
“抱着”资源睡觉
当一个执行到sleep方法,当前线程就会休眠指定时间,在休眠过程中,让出CPU的资源
作用:
1.放大问题的可能性 2.模拟网络延迟 倒计时

线程同步

在Java里面,通过synchronized进行同步的保证。
它包括两种用法:synchronized 方法和 synchronized 块

synchronized 方法
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
public synchronized void accessVal(int newVal);

synchronized 块
在代码块前加上synchronized关键字,并指定加锁的 对象

synchronized(syncObject){    
    //允许访问控制的代码    
    }

同步锁: synchronized

同步方法 : 同步静态方法 同步成员方法
同步块{} :

synchronized(this|类名.class|资源){
                    代码段...
            }


{} -> 排队执行的代码块
​ this|类名.class|资源 : 线程要求的执行{}中代码的执行条件

注意:
​ 同步的代码范围如果 太大,效率低,如果太小,锁不住
​ 锁不变的内容(this|资源…|类…)

同步块: 类的class对象,相当于把真个类锁住了,效率相对较低–>推荐使用this

死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞 争资源或者由于彼此通信而造成的一种阻塞的现象

如何解决死锁问题:

  1. 往往是程序逻辑的问题。需要修改程序逻辑。
    程要求的执行{}中代码的执行条件

    注意:
    ​ 同步的代码范围如果 太大,效率低,如果太小,锁不住
    ​ 锁不变的内容(this|资源…|类…)

    同步块: 类的class对象,相当于把真个类锁住了,效率相对较低–>推荐使用this
死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞 争资源或者由于彼此通信而造成的一种阻塞的现象

如何解决死锁问题:

  1. 往往是程序逻辑的问题。需要修改程序逻辑。
  2. 尽量不要同时持有两个对象锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值