线程(多线程)

 概述

        线程(Thread):是一个程序内部的一条执行流程。( 程序中如果只有一条执行流程,那这个程序就是单线程的程序。)

        多线程:从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)

多线程的创建

        Java是通过java.lang.Thread 类的对象来代表线程的。

方式一:继承Thread类

写法

        ①定义一个子类MyThread继承线程类java.lang.Thread,,重写run()方法

public cLass MyThread extends Thread{
    @override
    public void run() {
        //运行内容...
    }
}

        ②创建MyThread类的对象

        ③调用线程对象的start()方法启动线程(启动后还是执行run方法的)

public class Main {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

优缺点

        优点:编码简单。

        缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。

注意事项

  1. 启动线程必须是调用start方法,不是调用run方法。
  2. 不要把主线程任务放在启动子线程之前。

方式二:实现Runnable接口

写法

        1.定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

public cLass MyRunnable implements Runnable{
    @0verride
    public void run() {
        //运行内容...
    }
}

        2.创建MyRunnable任务对象

        3.把MyRunnable任务对象交给Thread处理。

Thread类提供的构造器说明
public Thread( Runnable target)封装Runnable对 象成为线程对象

        4.调用线程对象的start()方法启动线程

public cLass Main {
    public static void main(String[] args) {
        Runnable target = new MyRunnabLe();
        new Thread(target).start();
    }
}

优缺点

        优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。

        缺点:需要多一个Runnable对象。

匿名内部类的写法(简写)

  1. 可以创建Runnable的匿名内部类对象
  2. 再交给Thread线程对象。
  3. 再调用线程对象的start()启动线程。
public cLass Main {
    public static void main(String[] args) {
        Runnable target = new RunnabLe() {
            @0verride
            public void run() {
                //运行内容...
            }
        };
        new Thread(target).start();

    //上述代码还可以进一步简化为下列形式
        new Thread(new RunnabLe() {
            @0verride
            public void run() {
                //运行内容...
            }
        }).start();

    //进一步简化
        new Thread(() -> {
            //运行内容...
        }).start();

    }
}

方式三:实现Callable接口

        假如线程执行完毕后有一些数据需要返回,前边两种方式重写的run方法均不能直接返回结果。在JDK 5.0提供了Callable接口和FutureTask类来实现。这种方式最大的优点是可以返回线程执行完毕后的结果。

写法

利用Callable接口、FutureTask类来实现

①、创建任务对象

        ➢定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。

import java.util.concurrent.Callable;

public class Time implements Callable<String> {
private String st;

    public Time(String st) {
        this.st = st;
    }

    public String call() throws Exception {
        String n = "1" + st;
        //内容...
        return n;
    }

}

        ➢把Callable类型的对象封装成FutureTask (线程任务对象,是一个任务对象,实现了Runnable对象;可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后的结果。)

②、把线程任务对象交给Thread对象。

③、调用Thread对象的start方法启动线程。

④、线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class test {
    public static void main(String[] args) throws Exception {
        Callable call = new Time("100");
        FutureTask<String> f1 = new FutureTask<>(call);
        new Thread(f1).start();
        String rs = f1.get();
        System.out.println(rs);
    }
}

FutureTask的API 

FutureTask提供的构造器说明
public FutureTask<>(Callable call)把Callable对象封装成FutureTask对象。
FutureTask提供的方法说明
public V get() throws Exception获取线程执行call方法返回的结果。

优缺点

        优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。

        缺点:编码复杂一点。

Thread的常用方法

Thread提供的常见构造器说明
public Thread(String name)可以为当前线程指定名称
public Thread( Runnable target)封装Runnable对象成为线程对象
public Thread( Runnable target, String name)封装Runnable对象成为线程对象,并指定线程名称
Thread提供的常用方法说明
public void run( )线程的任务方法
public void start()启动线程
public String getName()获取当前线程的名称,线程名称默认是Thread-索引
public void setName(String name )为线程设置名称
public static Thread currentThread( )获取当前执行的线程对象
public static void sleep(long time)让当前执行的线程休眠多少毫秒后,再继续执行
public final void join()...让调用当前这个方法的线程先执行完!

        Thread类还提供了诸如:yield、 interrupt、 守护线程、线程优先级等线程的控制方法,在开发中很少使用

线程同步

        线程安全问题:多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。可以使用线程同步解决线程安全问题

        加锁是线程同步的常见方案

        加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。

方式一:同步代码块

同步代码块

●作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

原理:每次只允许一个线程加锁后进入, 执行完毕后自动解锁,其他线程才可以进来执行

同步锁的注意事项

●对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。

锁对象的使用规范,

●建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。

●对于静态方法建议使用字节码(类名.class)对象作为锁对象。

方式二:同步方法

●作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

同步方法底层原理

●同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。

●如果方法是实例方法:同步方法默认用this作为的锁对象。

●如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

方式三:Lock锁

●Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。

●Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

构造器说明
public ReentrantLock()获得Lock锁的实现类对象
常用方法说明
void lock()获得锁
void unlock()释放锁

线程通信

        ●当多个线程 共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。

Object类的等待和唤醒方法: 

方法名称说明
void wait( )让当前线程等待并释放所占锁,直到另一个线程调用notify( )方法或notifyAll()方法 
void notify()唤醒正在等待的单个线程
void notifyAll()唤醒正在等待的所有线程

 注意:上述方法应该使用当前同步锁对象进行调用。

 线程池

概述

        线程池:一个可以复用线程的技术。

        不使用线程池的问题:用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的, 而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。

        DK 5.0起提供了代表线程池的接口:ExecutorService。

得到线程池对象:

        方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。

        方式二:使用Executors (线程池的工具类)调用方法返回不同特点的线程池对象。

ThreadPoolExecutor

构造

        构造器

        参数一:corePoolSize:指定线程池的核心线程的数量。

        参数二:maximumPoolSize:指定线程池的最大线程数量

        参数三:keepAliveTime:指定临时线程的存活时间。

        参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)

        参数五:workQueue:指定线程池的任务队列。

        参数六:threadFactory:指定线程池的线程工厂(用来创建线程)

        参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)

ExecutorService pooL = new ThreadPooLExecutor(3,5,8,
    TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy());

线程池的注意事项

●新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

●核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

ExecutorService的常用方法

方法名称说明
void execute(Runnable command)执行Runnable 任务
Future<T> submit(Callable<T> task)执行 callable 任务,返回未来任务对象,用于获取线程返回的结果
void shutdown( )等全部任务执行完毕后,再关闭线程池!
List<Runnable> shutdownNow( )立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务

新任务拒绝策略

策略详解
ThreadPoolExecutor.AbortPolicy丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常这是不推荐的做法
ThreadPoolExecutor.Discard0ldestPolicy抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor. CallerRunsPolicy由主线程负责调用任务的run(方法从而绕过线程池直接执行

      Executors

        Executors是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

方法名称说明
public static ExecutorService newFixedThreadPool(int nThreads)创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newSingleThreadExecutor()创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newCachedThreadPool()线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

        注意:这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。

配置核心线程的数量

计算密集型的任务(计算等):核心线程数量= CPU的核数+ 1

I0密集型的任务(读取/通信等):核心线程数量= CPU核数* 2

Executors使用可能存在的陷阱

●大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

阿里巴巴Java开发手册

其它细节知识

进程

  • 正在运行的程序(软件)就是一个独立的进程。
  • 线程是属于进程的,一个进程中可以同时运行很多个线程。
  • 进程中的多个线程其实是并发和并行执行的。

并发的含义

        进程中的线程 是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

并行的理解

        在同一个时刻上,同时有多个线程在被CPU调度执行。

        多线程是并发和并行同时进行的。

线程的生命周期

        线程的书名周期:线程从生到死的过程中,经历的各种状态及状态转换。

        Java线程的状态:Java总共定义了6种状态,6种状态都定义在Thread类的内部枚举类中。

线程状态说明
NEW(新建)线程刚被创建,但是并未启动
Runnable(可运行)线程已经调用了start(),等待CPU调度
Blocked(锁阻塞)线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态
Waiting(无限等待)一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒
Timed Waiting( 计时等待)同waiting状态,有几个方法(sleep,wait) 有超时参数,调用他们将进入Timed Waiting状态
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
线程的6种状态互相转换

 乐观锁(拓展)

        悲观锁:一上来就加锁,没有安全感。每次只能一个线程进入访向完毕后,再解锁。线程安全, 性能较差!(前边那几种锁就是悲观锁)

        乐观锁:一开始不上锁, 认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。(用的CAS算法)

        乐观锁用法:在定义变量时用给定的对象(整数用AtomicInteger,小数用AtomicBoolean...),在运算时也要给定的方法(在当前值上加一:incrementAndGet() 除此之外还有其它方法)

  • 55
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值