JAVA之多线程

  1. 进程与线程

进程:进程是程序的一次动态执行过程,它经历了从代码加载、执行、到执行完毕的一个完整过程;同时也是并发执行的程序在执行过程中分配和管理资源的基本单位,竞争计算机系统资源的基本单位

线程:线程可以理解为进程中的执行的一段程序片段,是进程的一个执行单元,是进程内可调度实体,是比进程更小的独立运行的基本单位,线程也被称为轻量级进程。

一个程序至少一个进程,一个进程至少一个线程

  1. 用户线程与守护线程

守护线程:是一种特殊的线程,在后台默默完成一些系统性的服务,比如垃圾回收线程、JIT线程都是守护线程

用户线程:可以理解为是系统的工作线程,他会完成这程序需要完成的业务操作,如我们使用Thread创建的线程默认都是用户线程

通过 Thread.setDaemon(false) 设置为用户线程
通过 Thread.setDaemon(true) 设置为守护线程
如果不设置线程属性,那么默认为用户线程

  1. 多线程的实现
  • 继承Thread类,重写run方法(无返回值)

通过继承Thread类,并重写父类的run()方法实现
public void run()
public class MyThread extends Thread{
    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        int count = 0;
        for (int i = 0; i < 50; i++) {
            System.out.println(this.name + "正在执行.." + i);
            count += 1;
            System.out.println(count+"--------------------------------------");

        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程一");
        MyThread myThread2 = new MyThread("线程二");
        MyThread myThread3 = new MyThread("线程三");
        myThread.start();
        myThread2.start();
        myThread3.start();
    }
}

多线程交替执行效率加倍,要想实现多线程,就要依靠Thread类的start()方法执行,线程启动后会默认调用run()方法。

  • 实现Runnable接口,重写run方法(无返回值)

public class MyRunnable implements Runnable{
    private String name;

    public MyRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        int count = 0;
        for (int i = 0; i < 50; i++) {
            System.out.println(this.name + "正在执行.." + i);
            count += 1;
            System.out.println(count+"--------------------------------------");
        }
    }

    public static void main(String[] args) {
        Runnable myThread = new MyThread("线程一");
        Runnable myThread2 = new MyThread("线程二");
        Runnable myThread3 = new MyThread("线程三");

        new Thread(myThread).start();
        new Thread(myThread2).start();
        new Thread(myThread3).start();
    }
}

使用Thread类的确可以实现多线程,但是也容易发现它的缺陷:(Thread)面向对象的单继承局限,因此才采用Runnable接口来实现多线程。

Thread类是Runable接口的子类,通过直接覆写Thread类的run方法实际上依然是覆写Runnable接口内的run方法,其实本质上是没有区别的,但是利用Runnable方案实现更加能体现面向对象思维,有点类似于代理设计模式

Thread 继承(extends ) Runable实现(implements

  • 实现Callable接口,重写call方法(有返回值且可以抛出异常)

public class MyCallable implements Callable<Integer> {
    private String name;

    public MyCallable(String name) {
        this.name = name;
    }

    @Override
    public Integer call() throws Exception {
        Integer sum = 0;
        for(int i = 0 ; i < 500;i++) {
            System.out.println(this.name + i);
            sum += i;
        }
        return sum;
    }




    public static void main(String[] args) throws Exception {
        // 实例化继承Callable接口的MyThread类
        MyCallable mt1 = new MyCallable("线程一");
        MyCallable mt2 = new MyCallable("线程二");
        MyCallable mt3 = new MyCallable("线程三");

        // FutureTask类接收继承Callable接口的MyThread的实例
        FutureTask<Integer> ft1 = new FutureTask<Integer>(mt1);
        FutureTask<Integer> ft2 = new FutureTask<Integer>(mt2);
        FutureTask<Integer> ft3 = new FutureTask<Integer>(mt3);
        // 启动多线程
        new Thread(ft1).start();
        new Thread(ft2).start();
        new Thread(ft3).start();
        System.out.println(ft1.get());
        System.out.println(ft2.get());
        System.out.println(ft3.get());
    }
}

使用Runnable接口实现的多线程可以避免单继承的局限,但是还有一个问题就是run方法没有返回值,为了解决这个问题,所以提供了一个Callable接口

FutureTask类常用方法:

import java.util.concurrent.ExecutionException; // 导入ExecutionException异常包
public FutureTask(Callable<T> callable) // 构造函数:接收Callable接口实例
public FutureTask(Runable runnable,T result) // 构造函数:接收Runnable接口实例,同时指定返回结果类型 
public T get() throws InterruptedException,ExecutionException // 取得线程操作返回结果

Thread类的一个构造方法:

public Thread(FutureTask<T> futuretask) //构造方法:接收FutureTask实例化对象
Callable接口实现采用泛型技术实现,继承 需要重写call方法,再通过FutureTask包装器包装,传入后实例化Thread类实现多线程
其中 FutureTask类是Runnable接口的子类,所以才可以利用Thread类的start方法启动多线程,读者可以将call方法假设为有返回值的run方法。

  1. 多线程的生命周期
1.新建状态(new)
创建一个线程对象后,该线程对象就处于新建状态,此时不能运行,于其他java对象一样,仅仅由java虚拟机为其分配了内存,没有表现出任何线程的动态特征

2.就绪状态(Runnable)
当线程对象调用了start()方法后,该线程就进入就绪状态,处于就绪状态的线程位于线程队列中,此时它知识具备了运行的条件, 能够获得CPU的使用权,还需要等待系统的调度

3.运行状态(Running)
如果处于就绪状态的线程获的了CPU的使用权,并开始执行run()方法中的线程执行体,则该线程处于运行状态,一个线程启动后,它不可能一直处于运行状态,当运行状态的线程使用完系统分配的时间后,系统就会剥夺该线程占用的CPU资源,让其他线程获得执行的机会。需要注意的是,只有一直处于就绪状态的线程才可能转换到运行状态。

4.阻塞状态(Blocked)
一个正在执行的线程在某些特殊情况下,如被人为操作挂起或执行耗时的输入/输出的时,会让出CPU的使用权并暂时终止自己的执行,进入阻塞状态。线程进入阻塞状态后,就不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态

列举线程由运行状态转换成阻塞状态的愿意,以及如何从阻塞状态转换成就绪状态
当线程试图获取某个对象的同步锁时,如果该锁被其他线程所持有,则当前线程会进入阻塞状态,如果想从阻塞状态进入就绪状态就必须获取到其他线程所持有的锁
当线程调用了一个阻塞式的I/O方法时,该线程就会进入阻塞状态,如果想进入就绪状态,就必须要等待这个阻塞的I/O方法返回。
当线程调用了某个对象的wait()方法时,也会使线程进入阻塞状态,如果想要进入就绪状态就需要使用notify()方法唤醒该线程
当线程调用了Thread的Sleep(long milis)方法时,也会使其线程进入到阻塞状态,在这种情况下只需要等待睡眠时间到了后,线程就会自动进入就绪状态
当在一个线程中调用了join()方法时,会使当前线程进入阻塞状态,在这种情况下,需要等到新加入的线程运行结束后才会结束阻塞状态,进入就绪状态
需要注意的是,线程从阻塞状态只能进入就绪状态,而不能直接进人运行状态,也就是说,结束阻塞的线程需要重新进入可运行池中,等待系统的调度。

5.死亡状态(Terminated)
如果线程调用stop()方法或run()方法执行完毕,或者线程抛出一个未捕获的异常(Exception)错误(Error),线程就进入死亡状态,一旦进入死亡状态,线程将不再拥有运行的资格,也不能转换到其他状态

  1. 多线程常用方法
  • 线程休眠

sleep方法定义在java.lang.Thread中,由Thread.sleep()调用实现。其作用是需要暂缓线程的执行速度,则可以让当前线程休眠,即当前线程从“运行状态”进入到“阻塞状态”。sleep方法会指定休眠时间,线程休眠的时间会大于或等于该休眠时间,该线程会被唤醒,此时它会由“阻塞状态”变成“就绪状态”,然后等待CPU的调度执行

public static void sleep(long millis) throws InterruptedException // 普通函数:设置休眠时间的毫秒数
public static void sleep(long millis,int nanos) throws InterruptedException // 普通函数:设置休眠毫秒数和纳秒数
  • 线程中断

interrupt方法定义在java.lang.Thread中,由Thread.interrupt()调用实现。该方法将会设置该线程的中断状态位,即设置为true,中断的结果线程是终止状态、还是阻塞状态或是继续运行至下一步,就取决于该程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(即中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。

// 以下均为Thread类的方法
public boolean isInterrupted() //普通函数:判断线程是否被中断
public void interrupt() //普通函数:中断线程执行
  • 线程强制执行

join方法定义在java.lang.Thread中,由Thread.join()调用实现。多线程启动后会交替进行资源抢占和线程体执行,如果此时某些线程异常重要,也就是说这个对象需要优先执行完成,则可以设置为线程强制执行,待其完成后其它线程继续执行。

// Thread类方法
public final void join() throws InterruptedException //普通函数:强制执行
  • 线程让步

yield方法定义在java.lang.Thread中,由Thread.yield()调用实现。多线程在彼此交替执行的时候往往需要进行资源的轮流抢占,如果某些不是很重要的线程抢占到资源但是又不急于执行时,就可以将当前的资源暂时让步出去,交给其它资源先执行。但是,因为yeild是将线程由“运行状态”转别为“就绪状态”,这样并不能保证在当前线程调用yield方法之后,其它具有相同优先级的线程就一定能获得执行权,也有可能是当前线程又进入到“运行状态”继续运行,因为还是要依靠CPU调度才可以。

public static void yield() // 静态函数:线程让步
  • 线程优先级

所有创造的线程都是子线程,所有的子线程在启动时都会保持同样的优先级权限,但是如果现在某些重要的线程希望可以优先抢占到资源并且先执行,就可以修改优先级权限来实现。
记住当线程的优先级没有指定时,所有线程都携带普通优先级。
需要理解的是:
优先级用从1到10的范围的整数指定。10表示最高优先级,1表示最低优先级,5是普通优先级,也就是默认优先级。
优先级相对最高的线程在执行时被给予优先权限。但是不能保证线程在启动时就进入运行状态。
优先级越高越有可能先执行。
public static final int MAX_PRIORITY // 静态常量:最高优先级,数值为10
public static final int NORM_PRIORITY //静态常量:普通优先级,数值为5
public static final int MIN_PRIORITY // 静态常量:最低优先级,数值为1
public final void setPriority(int newPriority) // 普通函数:设置优先级
public final int getPriority() //普通函数:获取优先级
  1. 线程的同步和锁死
  • 同步:

解决数据共享问题必须使用同步,所谓的同步就是指多个线程在同一个时间段内只能有一个线程执行指定的代码,其他线程要等待此线程完成之后才可以继续进行执行,在Java中提供有synchronized关键字以实现同步处理,同步的关键是要为代码加上“锁”。

而锁的操作有三种:

1.同步代码块

2.同步方法

  1. Lock实现

起到了安全的作用,但是执行的效率却下降了,因为每次都只有一个线程才能访问同步代码块

同步代码块实现:

synchronized(需要同步的对象){
    需要同步的操作
}




public class MyThread implements Runnable{
    private int ticket = 10; 
    @Override
    public void run() {
        while(true) {
            // 同步代码块
            synchronized(this) {
                if(ticket<0) {
                    System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);
                    break;
                }
                try {
                    Thread.sleep(10); // 延迟0.01秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);
            }
        }
    }
}

同步方法实现:

利用函数包装的形式实现,如下:
修饰符 synchronized 返回类型 函数名()






public class MyThread implements Runnable{
    private int ticket = 10; 
    @Override
    public void run() {
        while(this.sale()) {}
    }
    public synchronized boolean sale() {
            if(ticket<0) {
                System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);
                return false;
            }
            try {
                Thread.sleep(10); // 延迟0.01秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);
            return true;
        }
}

Lock锁实现:

package cn.wu;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread implements Runnable{
    private int ticket = 10;
    private final Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while(this.sale()) {}
    }
    public boolean sale() {
        lock.lock();
        try{
            if(ticket<0) {
                System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);
                return false;
            }
            Thread.sleep(200);
            System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
        return true;
    }
}

  • 线程死锁

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,死锁的操作一般是在程序运行时候才有可能出现,死锁是在多线程开发中较为常见的一种问题,过多的同步就有可能出现死锁。

  1. 线程池

线程池的概念

容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程的操作,无需反复创建线程而消耗过多的资源。

为何引入线程池?

如果并发的线程数量过多,并且每个线程都是执行一个时间很短的任务就结束,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要消耗时间,线程也属于宝贵的系统资源,因此,线程池就是为了能使线程可以复用而创建的。

线程池的好处?

  • 降低资源的消耗,减少创建和销毁线程的次数,每个工作线程都可以被重复使用,可执行多个任务

  • 提高响应速度,不需要频繁地创建线程,如果有线程可以直接使用,避免了系统僵死

  • 提高线程的可管理性

核心思想:线程复用

Runnable简单实例:

package cn.wu;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
    public static void main(String[] args) {
        // 1.创建一个线程池,指定线程的数量为4
        ExecutorService pools = Executors.newFixedThreadPool(4);
        // 2.添加线程任务
        Runnable target = new MyRunnable();
        pools.submit(target); // 第一次提交任务,此时创建新线程
        pools.submit(target); // 第二次提交任务,此时创建新线程
        pools.submit(target); // 第三次提交任务,此时创建新线程
        pools.submit(target); // 第四次提交任务,此时创建新线程
        pools.submit(target); // 第五次提交任务,复用之前的线程
        pools.shutdown(); // 当所有任务全部完成后才关闭线程池
//        pools.shutdownNow(); // 立即关闭线程池

    }
}
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i = 0 ; i<10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"正在执行任务…  "+i);
        }
    }
}

Callable简单实例:

package cn.wu;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test {
    public static void main(String[] args) {
        // 1.创建一个线程池,指定线程的数量为4
        ExecutorService pools = Executors.newFixedThreadPool(4);
        try{
            long start = System.currentTimeMillis();
            // 2.添加线程任务
            Future<String> t1 = pools.submit(new MyCallable(100000)); // 提交求出1-100000和的线程任务
            Future<String> t2 = pools.submit(new MyCallable(200000)); // 提交求出1-200000和的线程任务
            Future<String> t3 = pools.submit(new MyCallable(300000)); // 提交求出1-300000和的线程任务
            Future<String> t4 = pools.submit(new MyCallable(400000)); // 提交求出1-400000和的线程任务
            Future<String> t5 = pools.submit(new MyCallable(500000)); // 提交求出1-500000和的线程任务
            System.out.println(t1.get());
            System.out.println(t2.get());
            System.out.println(t3.get());
            System.out.println(t4.get());
            System.out.println(t5.get());

            long end = System.currentTimeMillis();
            System.out.println("采用多线程所耗时间为:"+(end-start)*1.0/1000+"s");
            start = System.currentTimeMillis();
            long sum = 0;
            for(int i = 1 ; i<=100000 ; i++) {
                sum += i;
            }
            System.out.println("最终结果为:"+sum);
            sum = 0;
            for(int i = 1 ; i<=200000 ; i++) {
                sum += i;
            }
            System.out.println("最终结果为:"+sum);
            sum = 0;
            for(int i = 1 ; i<=300000 ; i++) {
                sum += i;
            }
            System.out.println("最终结果为:"+sum);
            sum = 0;
            for(int i = 1 ; i<=400000 ; i++) {
                sum += i;
            }
            System.out.println("最终结果为:"+sum);
            sum = 0;
            for(int i = 1 ; i<=500000 ; i++) {
                sum += i;
            }
            System.out.println("最终结果为:"+sum);
            end = System.currentTimeMillis();
            System.out.println("采用单线程所耗时间为:"+(end-start)*1.0/1000+"s");
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}
class MyCallable implements Callable<String> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }
    @Override
    public String call() throws Exception {
        long sum = 0;
        for(int i = 1 ; i <= num ; i++) {
            sum += i;
        }
        return Thread.currentThread().getName()+
                "任务执行的最终结果为:"+sum;
    }
}
  1. 多线程基础图

https://download.csdn.net/download/m0_63131732/87468810?spm=1001.2014.3001.5503

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值