【JAVA】多线程

1 继承Thread类

创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。

该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

优点:编码简单

缺点:存在单继承的局限性,线程类继承Thread后,不能继承其它类,不便于扩展

/** 线程的创建方式一:继承Thread实现。*/
public class ThreadDemo1 {
    public static void main(String[] args) {
        // main方法本身是一个单线程在执行的,这个线程称为主线程。
        // 3、创建线程对象
        Thread t = new MyThread();
        // 4、调用线程对象的start方法启动线程(最终还是调用线程的run方法)
        t.start(); // 不能直接调用run方法,会当成普通方法执行
        // new MyThread().start();
    }
}
// 1、定义线程类继承了Thread类
class MyThread extends Thread{
    // 2、重写run方法
    @Override
    public void run() {
        System.out.println("子线程输出");
    }
}

2 实现Runnable接口

  1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  2. 创建MyRunnable任务对象
  3. 把MyRunnable任务对象交给Thread处理。
  4. 调用线程对象的start()方法启动线程

优点:线程人物类只是实现接口,可以继续继承类和实现接口,扩展性强

缺点:编程多一层对象包装,如果线程执行结果不可以直接返回

/**  目标:多线程的创建方式二:实现Runnable接口  */
public class ThreadDemo2 {
    public static void main(String[] args) {
        // 3、创建一个线程任务类的对象(此对象不是线程对象)
        Runnable target = new MyRunnable();
        // 4、把线程任务对象交给Thread线程对象。
        // public Thread(Runnable target)
        Thread t = new Thread(target);
        // 5、启动线程
        t.start();
        // new Thresd(new MyRunnable()).start;
    }
}
// 1、定义一个线程任务类实现Runnable接口
class MyRunnable implements Runnable{
    //2、重写run方法
    @Override
    public void run() {
        System.out.println("子线程任务输出");
    }
}

匿名内部类实现Runnable接口

  1. 创建Runnable的匿名内部类
  2. 交给Thread处理
  3. 调用线程对象start()启动线程
public static void main(String[] args) {

        // 匿名内部类的方式得到一个线程对象
//        Runnable target1 = new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("子线程输出" );
//            }
//        };
//        Thread t2 = new Thread(target1);
//        t2.start();
//-------------上面注释代码简化
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("子线程2输出");
//            }
//        }).start();
//-------------上面注释代码再简化
        new Thread(() -> {
            System.out.println("子线程输出" );
        }).start();
    }

3 实现Callable接口

前两种方式存在的问题:

  • 重写的run方法不能直接返回结果,不适合需要返回线程执行结果的业务场景

如何解决

  • Callable和FutureTask,可以得到线程的执行结果

步骤

  1. 得到任务对象( 一:  定义类实现Callable接口,重写call方法,封装要做的事情。二   用FutureTaskCallable对象封装成线程任务对象)
  2. 把线程任务对象交给Thread处理。
  3. 调用Threadstart方法启动线程,执行任务线
  4. 程执行完毕后、通过FutureTaskget方法去获取任务执行的结果。

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

缺点:编码复杂一点。

/**
    目标:理解线程创建方式三:Callable、FutureTask.
 */
public class ThreadDemo3 {
    public static void main(String[] args) {
        // 3、创建一个Callable的执行对象。
        Callable<String> call = new MyCallable(100);
        // 4、把Callable对象交给未来任务对象
        /**
           未来任务对象的作用:
                1、Runnable的实现类对象,可以交给Thread线程对象。
                2、可以在未来线程执行完毕后,去获取线程返回的结果的。
         */
        FutureTask<String> f1 = new FutureTask<>(call);
        // 5、交给Thread线程对象
        Thread t1 = new Thread(f1);
        // 6、启动线程
        t1.start();

        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        // 7、得到线程执行完毕后的结果。
        try {
            // 主线程执行到这儿,如果上面第一个线程没有执行完毕,这里会让出CPU,等待第一个线程执行完毕之后才能执行这个代码取结果
            String rs = f1.get();
            System.out.println(rs);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            // 主线程执行到这儿,如果上面第2个线程没有执行完毕,这里会让出CPU,等待第2个线程执行完毕之后才能执行这个代码取结果
            String rs = f2.get();
            System.out.println(rs);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


/**
   1、定义一个类实现Callable接口,
 */
class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n){
        this.n = n;
    }
    /**
       2、重写call方法,定义执行的任务和返回的结果
     */
    @Override
    public String call() throws Exception {   
        return n;
    }
}

三种方式对比

方式优点缺点

继承Thread

编程比较简单,可以直接使用Thread类中的方法

扩展性较差,不能再继承其他的类,不能返回线程执行的结果

实现Runnable接口

扩展性强,实现该接口的同时还可以继承其他的类。

编程相对复杂,不能返回线程执行的结果

实现Callable接口

扩展性强,实现该接口的同时还可以继承其他的类。可以得到线程执行的结果

编程相对复杂

4 Thread常用方法

方法说明

String getName​()

获取当前线程的名称,默认线程名称是Thread-索引

void setName​(String name)

设置线程名称

public static Thread currentThread()

返回对当前正在执行的线程对象的引用

public static void sleep(long time)

让线程休眠指定的时间,单位为毫秒。

public void run()

线程任务方法

public void start()

线程启动方法

构造器说明

public Thread(String name)

可以为当前线程指定名称

public Thread(Runnable target)

Runnable对象交给线程对象

public Thread(Runnable target String name )

Runnable对象交给线程对象,并指定线程名称

5 线程安全

多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。

出现原因:

  • 存在多线程并发
  • 同时访问共享资源
  • 存在修改共享资源

取钱问题

public void drawMoney(double money) {
        // 1、得到谁来取钱
        String name = Thread.currentThread().getName();
        // 2、判断当前账户对象的余额是否足够取钱10万
        if(this.money >= money) {
            System.out.println(name +"来取钱,吐出" + money);
            // 3、更新余额
            this.money -= money;
            System.out.println(name +"取钱后,余额剩余:" + this.money);
        }else {
            System.out.println(name +"来取钱,余额不足~~");
        }
    }
/**
    线程类(取钱)
 */
public class DrawThread extends Thread{
    private Account acc;
    public DrawThread(Account acc, String name){
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        // 小明 ,小红 : 取钱
        acc.drawMoney(100000);
    }
}
/**
     目标:模拟线程安全问题(取钱模型,整取)

     定义账户类。
     定义线程类,处理账户。
 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        // 1、创建一个共享的账户对象
        Account acc = new Account("ICBC-110",100000);
        // 2、创建2个线程对象分别代表小明和小红
        new DrawThread(acc, "小明").start();
        new DrawThread(acc, "小红").start();
    }
}

6 线程同步

加锁:让多个线程实现先后依次访问共享资源

6.1 同步代码块

把出现线程安全问题的核心代码上锁,每次只能一个线程占锁进入访问

缺点:会影响其它无关线程的执行

同步锁对象要求

  • 实例方法使用this作为锁对象
  • 静态方法建议使用字节码(类名.class)对象作为锁对象
synchronized(同步锁对象) {
	操作共享资源的代码(核心代码)
}
public void drawMoney(double money) {
    // 1、得到谁来取钱
    String name = Thread.currentThread().getName();
    synchronized(this){
    // synchronized(Account.class){
        // 2、判断当前账户对象的余额是否足够取钱10万
        if(this.money >= money) {
            System.out.println(name +"来取钱,吐出" + money);
            // 3、更新余额
            this.money -= money;
            System.out.println(name +"取钱后,余额剩余:" + this.money);
        }else {
            System.out.println(name +"来取钱,余额不足~~");
        }
    }
}

6.2 同步方法

把出现线程安全问题的核心方法给上锁

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

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
  • 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
修饰符 synchronized(锁对象) 返回值类型 方法名称(形参列表) {
	操作共享资源的代码
}
public synchronized(this) void drawMoney(double money) {
        // 1、得到谁来取钱
        String name = Thread.currentThread().getName();
        // 2、判断当前账户对象的余额是否足够取钱10万
        if(this.money >= money) {
            System.out.println(name +"来取钱,吐出" + money);
            // 3、更新余额
            this.money -= money;
            System.out.println(name +"取钱后,余额剩余:" + this.money);
        }else {
            System.out.println(name +"来取钱,余额不足~~");
        }
    }

6.3 Lock锁

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

public ReentrantLock​()

获得Lock锁的实现类对象

void lock()

获得锁

void unlock()

释放锁

public class Account {
    // 锁对象属于账户对象,而且唯一不可更改!
    private final ReentrantLock lock = new ReentrantLock();
    private String cardId;// 卡号
    private double money; // 余额
    public Account() {
    }
    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }
    public void drawMoney(double money) {
        // 1、得到谁来取钱
        String name = Thread.currentThread().getName();
        // 2、判断当前账户对象的余额是否足够取钱10万
        lock.lock(); // 上锁
        try {
            if(this.money >= money) {
                System.out.println(name +"来取钱,吐出" + money);
                // 3、更新余额
                this.money -= money;
                System.out.println(name +"取钱后,余额剩余:" + this.money);

            }else {
                System.out.println(name +"来取钱,余额不足~~");
            }
        } finally {
            lock.unlock(); // 解锁!
        }
    }
}

7 线程通信

线性通信三个常见方法

void wait​()

当前线程等待,直到另一个线程调用notify()notifyAll()唤醒自己

void notify​()

唤醒正在等待对象监视器(锁对象)的单个线程

void notifyAll​()

唤醒正在等待对象监视器(锁对象)的所有线程

8 线程池*

线程池:复用线程的技术

得到线程对象:

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

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

public ThreadPoolExecutor(int corePoolSize,// 指定线程池的核心线程数量,不能小于0
                          int maximumPoolSize,// 指定线程池可支持的最大线程数,大于等于上一个参数
                          long keepAliveTime,// 指定临时线程的最大存活时间,不能小于0
                          TimeUnit unit,// 指定存活时间的单位(秒分时天)
                          BlockingQueue<Runnable> workQueue,//指定任务队列,不能为null
                          ThreadFactory threadFactory,//指定用哪个线程工厂创建线程,不能为null
                          RejectedExecutionHandler handler) //指定线程忙,任务满时,新任务该如何,不能为null

临时线程创建时机:

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

拒绝任务的时机:

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

8.1 线程池处理Runnable任务

使用ExecutorService的execute(Runnable target)方法

// ThreadPoolExecutor创建线程池实例
ExecutorService pools = new ThreadPoolExecutor(3, 
                                               5, 
                                               8, 
                                               TimeUnit.SECONDS, 
                                               new ArrayBlockingQueue<>(6),
                                               Executors.defaultThreadFactory() , 
                                               new ThreadPoolExecutor.AbortPolicy());
pool.execute(new MyRunnable());

ExecutorService的常用方法

void execute(Runnable command)

执行任务/命令,没有返回值,一般用来执行 Runnable 任务

Future<T> submit(Callable<T> task)

执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务

void shutdown()

等任务执行完毕后关闭线程池

List<RunnableshutdownNow()

立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

新任务拒绝策略

ThreadPoolExecutor.AbortPolicy

丢弃任务并抛出RejectedExecutionException异常。是默认的策略

ThreadPoolExecutor.DiscardPolicy:

丢弃任务,但是不抛出异常 这是不推荐的做法

ThreadPoolExecutor.DiscardOldestPolicy

抛弃队列中等待最久的任务 然后把当前任务加入队列中

ThreadPoolExecutor.CallerRunsPolicy

由主线程负责调用任务的run()方法从而绕过线程池直接执行

8.2 线程池处理Callable任务

使用ExecutorService的submit(Callable<T> command)方法

ExecutorService的常用方法

void execute(Runnable command)

执行任务/命令,没有返回值,一般用来执行 Runnable 任务

Future<T> submit(Callable<T> task)

执行Callable任务,返回未来任务对象获取线程结果

void shutdown()

等任务执行完毕后关闭线程池

List<RunnableshutdownNow()

立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

Future<String> f = pool.submit(new MyCallable(100));

8.3 Executors工具类实现线程池

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。

  • Executors工具类底层是基于线程池ExecutorService的实现类ThreadPoolExecutor的方式实现线程池对象
  • Executors不适合做大型互联网场景的线程池方案,建议这种场景使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,避免资源耗尽的风险

Executors得到线程池对象的常用方法

public static ExecutorService newCachedThreadPool()

线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉。

public static ExecutorService newFixedThreadPool​(int nThreads)

创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。

public static ExecutorService newSingleThreadExecutor ()

创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。

public static ScheduledExecutorService newScheduledThreadPool​(int corePoolSize)

创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

Executors使用存在的缺陷

public static ExecutorService newFixedThreadPool​(int nThreads)

允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误( java.lang.OutOfMemoryError

public static ExecutorService newSingleThreadExecutor()

public static ExecutorService newCachedThreadPool()

创建的线程数量最大上限是Integer.MAX_VALUE

线程数可能会随着任务1:1增长,也可能出现OOM错误( java.lang.OutOfMemoryError

public static ScheduledExecutorService newScheduledThreadPool​(int corePoolSize)

线程池不允许使用Executors 去创建,而是通过ThreadPoolExecutor的方式,这样
的处理方式是为了让人更加明确线程池的运行规则,规避资源耗尽的风险。

Executors返回的线程池对象的弊端如下:

  •  FixedThreadPool和 SingleThreadPool:

允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。

  • CachedThreadPool和 ScheduledThreadPool:

允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
 

9 定时器

Timer定时器

  • 特点:单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入
  • 缺点:能会因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。

public Timer()

创建Timer定时器对象

public void schedule​(TimerTask task, long delay, long period)

开启一个定时器,按照计划处理TimerTask任务

ScheduledExecutorService

jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷, ScheduledExecutorService内部为线程池。

优点:基于线程池,某个任务的执行情况不会影响其他定时任务的执行

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

得到线程池对象Executors的方法

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

周期调度方法

ScheduledExecutorService的方法

10 并发、并行

正在运行的程序(软件)就是一个独立的进程, 线程是属于进程的,多个线程其实是并发与并行同时进行的。

  • 并发:CPU分时轮询的执行线程

CPU同时处理线程时,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

  • 并行:在同一个时刻,同时有多个线程被CPU处理并执行

11 线程的生命周期

NEW:新建状态,创建线程对象
RUNNABLE:就绪状态,star方法
BLOCKED:阻塞状态,无法获得锁对象
WAITING:等待状态,wait方法
TIMED_WAITING:计时等待,sleep方法
TERMINATED:结束状态,全部代码运行完毕

NEW(新建)

线程刚被创建,但是并未启动。

Runnable(可运行)

线程已经调用了start()等待CPU调度

Blocked(锁阻塞)

线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态;。

Waiting(无限等待)

一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能够唤醒

Timed Waiting(计时等待)

waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。带有超时参数的常用方法有Thread.sleep Object.wait

Teminated(被终止)

因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值