Java面向对象之多线程

前言

       什么是线程?

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

 public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }

多线程是什么?

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

如何在程序中创建出多条线程?

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

多线程的创建方式一:继承Thread

定义一个子类 MyThread 继承线程类 java.lang.Thread ,重写 run() 方法
创建 MyThread 类的对象
调用线程对象的 start() 方法启动线程(启动后还是执行 run 方法的)
优点:编码简单
缺点:线程类已经继承Thread, 无法继承其他类 ,不利于功能的扩展
public class ThreadDemo1 {
    // main方法启动时,默认是由一条主线程负责执行。
    public static void main(String[] args) {
        // 3、创建线程类的对象代表一个线程。
        Thread t = new MyThread();
        // 4、启动这个线程对象,才真正开启了一个线程。(线程会自动执行run方法来跑)
        t.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("main线程输出 ===> " + i);
        }
    }
}

// 1、定义一个线程类继承Thread类。
class MyThread extends Thread{
    // 2、重写Thread类提供的run方法(线程执行任务的方法)
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程输出 ===> " + i);
        }
    }
}

多线程的创建方式二:实现Runnable接口

定义一个线程任务类  MyRunnable  实现  Runnable 接口,重写  run()  方法
创建  MyRunnable  任务对象
把  MyRunnable  任务对象交给 Thread 处理。
调用线程对象的  start()  方法启动线程
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
缺点:需要多一个 Runnable对象。

 

public class ThreadDemo2 {
    public static void main(String[] args) {
        // 3、用任务类创建任务对象
        Runnable target = new MyRunnable();
        // 4、把线程任务对象交给一个线程对象。
        //    a、我们就得到一个线程了
        //    b、线程启动后,可以去执行任务对象的run方法执行。
        Thread t = new Thread(target);
        // 5、启动线程
        t.start();

        for (int i = 0; i < 5; i++) {
            System.out.println("main线程输出:" + i);
        }
    }
}

// 1、定义一个线程任务类实现Runnable接口
class MyRunnable implements Runnable{
    // 2、重写run方法。
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程输出:" + i);
        }
    }
}

    多线程的创建方式三:利用Callable接口、FutureTask类来实现

、创建任务对象
定义一个类实现 Callable 接口,重写 call 方法,封装要做的事情,和要返回的数据。
Callable 类型的对象封装成 FutureTask (线程任务对象)。
、把线程任务对象交给 Thread 对象。
、调用 Thread 对象的 start 方法启动线程。
、线程执行完毕后、通过 FutureTask 对象的的 get 方法去获取线程任务执行的结果。
                
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强; 可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
public class ThreadDemo4 {
    public static void main(String[] args) {
        // 3、创建Callable对象
        Callable<String> call = new MyCallable(100);
        // 4、创建一个真正的任务对象叫:未来任务对象。
        // 未来任务对象的作用:
        //    a、未来任务对象本质是一个Runnable类型的对象,可以交给线程了!
        //    b、可以在线程执行完毕后去获取线程执行完毕后返回的结果
        FutureTask<String> task = new FutureTask<>(call);
        // 5、把未来任务对象交给线程对象。
        // public Thread(Runnable task)
        Thread t = new Thread(task);
        // 6、启动线程
        t.start();


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

        // 怎么获取线程执行完毕后的结果呢?建议大家在全部线程启动后再拿结果
        try {
            // 注意:当主线程执行到这里取第一个线程的结果时,如果第一个线程没有执行完毕
            // 会暂定等待第一个线程执行完毕后,再往下走!
            String rs = task.get();
            System.out.println(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        try {
            String rs = task2.get();
            System.out.println(rs);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

// 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 {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return "子线程求出了1-" + n + "的和是:" + sum;
    }
}

 

一、多线程常用方法

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

下面我们演示一下getName()setName(String name)currentThread()sleep(long time)这些方法的使用效果。

public class MyThread extends Thread{
    public MyThread(String name){
        super(name); //1.执行父类Thread(String name)构造器,为当前线程设置名字了
    }
    @Override
    public void run() {
        //2.currentThread() 哪个线程执行它,它就会得到哪个线程对象。
        Thread t = Thread.currentThread();
        for (int i = 1; i <= 3; i++) {
            //3.getName() 获取线程名称
            System.out.println(t.getName() + "输出:" + i);
        }
    }
}

再测试类中,创建线程对象,并启动线程

public class ThreadAPIDemo1 {
    public static void main(String[] args) {
        // 目标:理解Thread类的常用方法
        Thread t1 = new MyThread("1号->");
        // t1.setName("1号");
        t1.start();
        System.out.println("t1 - name :" + t1.getName());

        Thread t2 = new MyThread("2号->");
        // t2.setName("2号");
        t2.start();
        System.out.println("t2 - name :" + t2.getName());

        // 主线程的名称???
        // 哪个线程执行这个代码,这个方法就返回哪个线程对象给你
        Thread m = Thread.currentThread();
        m.setName("最牛的线程");
        System.out.println("m - name :" + m.getName());
    }
}

执行上面代码,效果如下图所示,我们发现每一条线程都有自己了名字了。

t1 - name :1号->
t2 - name :2号->
m - name :最牛的线程
1号->线程输出 ===> 0
1号->线程输出 ===> 1
1号->线程输出 ===> 2
1号->线程输出 ===> 3
1号->线程输出 ===> 4
2号->线程输出 ===> 0
2号->线程输出 ===> 1
2号->线程输出 ===> 2
2号->线程输出 ===> 3
2号->线程输出 ===> 4

二、线程安全问题

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

线程安全问题出现的原因?
1、存在多个线程在同时执行
2、同时访问一个共享资源
3、存在修改该共享资源

三、线程同步方案

为了解决前面的线程安全问题,我们可以使用线程同步思想。同步最常见的方案就是加锁,意思是每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他线程才能再加锁进来。

Java提供了三种方案:

      1、同步代码块
作用 :把 访问共享资源的 核心代码给上锁 ,以此保证线程安全。
原理 每次 只允许 一个线程 加锁后 进入 执行完毕后自动解锁,其他线程才可以进来执行

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

    public static void main(String[] args) {
        synchronized("同步锁") {        
            //访问共享资源的核心代码
        }

    }
2、同步方法
作用 :把 访问共享资源的 核心 方法 给上锁 ,以此保证线程安全。
原理 每次只能一个线程进入 执行完毕以后自动解锁,其他线程才可以进来执行
    修饰符 synchronized 返回值类型 方法名称(形参列表) {

	        //操作共享资源的代码

    }
 3、Lock
Lock 锁是 JDK5 开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock 是接口,不能直接实例化,可以采用它的实现类 ReentrantLock 来构建 Lock 锁对象。

Lock的常用方法:

四、线程池

1、什么是线程池?

线程池就是一个可以 复用线程的技术

2、不使用线程池的问题 

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

        3、如何得到线程池对象? 

方式一:使用 ExecutorService 的实现类 ThreadPoolExecutor 自创建一个线程池对象。
    public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler handler){}

//参数一:corePoolSize : 指定线程池的核心线程的数量。                          
//参数二:maximumPoolSize:指定线程池的最大线程数量。
//参数三:keepAliveTime :指定临时线程的存活时间。
//参数四:unit:指定临时线程存活的时间单位(秒、分、时、天)
//参数五:workQueue:指定线程池的任务队列。
//参数六:threadFactory:指定线程池的线程工厂。
//参数七:handler:指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理)

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

Executors 是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值