Java线程

线程

  • 线程Thread是一个程序内部的一条执行流程

  • 程序中如果只有一条执行流程,那这个程序就是单线程的程序

多线程

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

创建线程

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

package itheima.demo.thread;
​
public class demo1 {
    //main方法本身是由一条主线程负责推荐执行的
    public static void main(String[] args) {
        //创建线程类的对象,代表线程
        MyThread mt = new MyThread();
        //调用线程的start方法,启动线程
        mt.start();
        for (int i=0;i<5;i++){
            System.out.println("zhu线程执行了"+i);
        }
    }
}
​
​
//定义一个子类继承Thread类,成为一个线程类
class MyThread extends Thread{
    //重写Thread类中的run方法
    @Override
    public void run() {
        //线程要执行的任务
        for (int i=0;i<5;i++){
            System.out.println("zi线程执行了"+i);
        }
    }
}
  • 定义一个子类MyThread继承线程类java.lang.Thread,重写run方法

  • 创建MyThread类的对象

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

方式一优缺点:

  • 优点:编码简单

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

创建线程的注意事项

1.启动线程必须是调用start方法,不是调用run方法。

  • 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行

  • 只有调用start方法才是启动一个新的线程执行

2.不要把主线程任务放在启动子线程之前

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

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

2.创建MyRunnable任务对象

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

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

package itheima.demo.thread;
​
public class demo2 {
    public static void main(String[] args) {
        Runnable mr = new MyRunnable();
        //把线程任务对象交给一个线程对象来处理
        Thread t = new Thread(mr);
        t.start();
        for (int i=0;i<5;i++){
            System.out.println("主线程执行了"+i);
        }
    }
}
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<5;i++){
            System.out.println("子线程执行了"+i);
        }
    }
}
//定义一个线程任务类实现Runnable接口

方式二优缺点

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

  • 缺点:需要多一个Runnable对象,如果线程有执行结果是不能直接返回的

匿名内部类写法

1.可以创建Runnable匿名内部类对象

2.再交给Thread线程对象

3.再调用线程对象的start()启动线程

package itheima.demo.thread;
​
public class demo3 {
    public static void main(String[] args) {
        Runnable mr = new MyRunnable(){
            @Override
            public void run(){
                for (int i=0;i<5;i++){
                    System.out.println("子线程执行了"+i);
                }
            }
        };
        Thread t = new Thread(mr);
        t.start();
        for (int i=0;i<5;i++){
            System.out.println("主线程执行了"+i);
        }
    }
}

创建线程方式三:实现Callable接口

前两种线程创建方式都存在一个问题

  • 假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果

怎么解决这个问题

  • jdk5.0提供了Callable接口和FutureTask类来实现

  • 这种方式最大的优点:可以返回线程执行完毕后的结果

1.创建任务对象

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

  • 把Callable类型的对象封装成FutureTask(线程任务对象)

  • 把线程任务对象交给Thread对象

  • 调用Thread对象start方法启动线程

  • 线程执行完毕后,通过FutureTask对象的get方法获取线程任务的执行结果

package itheima.demo.thread;
​
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
​
public class demo3 {
    public static void main(String[] args) {
//        3.创建线程类对象,代表线程
        Callable<String> mc = new MyCallable(50);
//        4. 创建线程对象,代表线程
        FutureTask<String> ft = new FutureTask<>(mc);
//        未来任务对象的作用
//        本质是一个Runnable接口,可以交给Thread线程对象执行
//        可以获取线程执行结果
//        5.把FutureTask对象作为参数交给Thread线程对象执行
        Thread t = new Thread(ft);
        t.start();
        try {
            //如果发现线程还没有执行完毕,则让出CPU,等待执行完毕
            String result = ft.get();
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
​
​
​
​
}
​
//1.定义一个类实现callable接口
class MyCallable implements Callable<String>{
    private int num;
    public MyCallable(int num){
        this.num = num;
    }
//    2.实现call方法,定义线程执行体
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i=0;i<num;i++){
            System.out.println("线程执行了"+i);
            sum+=i;
        }
        return "线程执行完毕,结果是"+sum;
    }
}

线程常用方法

package itheima.demo.thread;
​
public class ApiDemo4 {
    public static void main(String[] args) {
        MyThread1 mt = new MyThread1();
        //调用线程的start方法,启动线程
        mt.setName("线程1");
        mt.start();
        System.out.println(mt.getName());
        MyThread1 mt2 = new MyThread1();
        mt2.start();
        System.out.println(mt2.getName());
​
        //获取当前线程
        Thread m=Thread.currentThread();
        System.out.println(m.getName());//main
    }
}
​
​
//定义一个子类继承Thread类,成为一个线程类
class MyThread1 extends Thread{
    //重写Thread类中的run方法
    @Override
    public void run() {
        //线程要执行的任务
        for (int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"zi线程执行了"+i);
        }
    }
}
package itheima.demo.thread;
​
public class ApiDemo5 {
    public static void main(String[] args) {
        //Sleep方法,线程休眠
        for (int i=0;i<5;i++){
            System.out.println("zhu线程执行了"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
package itheima.demo.thread;
​
public class ApiDemo6 {
    public static void main(String[] args) {
        MyThread2 mt = new MyThread2();
        mt.start();
        for (int i = 0; i < 5; i++) {
            if (i == 3){
                try {
                    mt.join();//插队,先让线程mt执行完再执行主线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
        
    }
}
class MyThread2 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"-->"+i);
        }
    }
}

线程安全

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

出现原因

  • 存在多个线程同时执行

  • 同时访问一个共享资源

  • 存在修改该共享资源

线程同步的常见方案

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

同步代码块

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

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

同步锁注意事项

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

锁对象随便选择一个唯一的对象好不好呢

  • 不好,会影响其他无关线程的执行

锁对象使用规范

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

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

synchronized (同步锁){
       访问共享资源的核心代码
    }

同步方法

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

修饰符 synchronized 返回值类型 方法名称(形参列表){
        操作共享资源的代码
    }

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

同步方法底层原理

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

  • 如果方法是实例方法,同步方法默认用this作为锁对象

  • 如果方法默认是静态方法,同步方法默认用类名.class作为锁对象

同步代码块好还是同步方法好

同步代码块效率更高

Lock锁

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

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

    private final Lock lk=new ReentrantLock();
    
​
    lk.lock();//上锁
​
    finally{
        lk.unkock();
    }
    

锁对象建议加上final修饰,防止别人篡改

释放锁操作放到fianlly代码块中,确保锁用完了一定会被释放

线程池

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

不使用线程池的问题

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

创建线程池

  • JDK5.0起提供了代表线程池的接口ExecutorService

如何创建线程池对象

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

参数一:corePoolSize 指定线程池的核心线程的数量 正式工3

参数二:maximumPoolSize 指定线程池的最大线程数量 最大员工数5 临时工2

参数三:keepAliveTime 指定临时线程的存活时间 临时工空闲多久被开除

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

参数五:workQueue 指定线程池的任务队列 客人排队的地方

参数六:threadFactory 指定线程池的线程工厂 HR

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

package itheima.demo.thread;
​
import java.util.concurrent.*;
​
public class Demo7 {
    public static void main(String[] args) {
        //1.使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
        Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //2.使用线程池对象来执行任务,看是否会复用线程
        MyRunnable1 mr = new MyRunnable1();
        pool.execute(mr);//创建线程,自动启动线程处理这个任务
        pool.execute(mr);
        pool.execute(mr);
        pool.execute(mr);
        pool.execute(mr);
​
        //关闭线程池
        pool.shutdown();//线程池不再接受新的任务,但是会等待正在执行的任务完成
        pool.shutdownNow();//线程池不再接受新的任务,并且会打断正在执行的任务
    }
}
​
​
​
package itheima.demo.thread;
​
public class MyRunnable1  implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"执行了"+i);
        }
    }
}
  • 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象

package itheima.demo.thread;
​
import java.util.concurrent.*;
​
public class Demo7 {
    public static void main(String[] args) {
        //1.使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
        ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
        Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        //2.使用线程池处理callable任务
        Future<String> f1=pool.submit(new MyCallable1(50));
        Future<String> f2=pool.submit(new MyCallable1(100));
​
        try {
            System.out.println(f1.get());
            System.out.println(f2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
​
​
package itheima.demo.thread;
​
import java.util.concurrent.Callable;
​
//1.定义一个类实现callable接口
public class MyCallable1 implements Callable<String> {
    private int num;
    public MyCallable1(int num){
        this.num = num;
    }
    //    2.实现call方法,定义线程执行体
    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i=0;i<=num;i++){
//            System.out.println(Thread.currentThread().getName()+"线程执行了"+i);
            sum+=i;
        }
        return Thread.currentThread().getName()+num+"和结果是"+sum;
    }
}

线程池的注意事项

什么时候开始创建临时线程

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

什么时候拒绝新任务

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

通过Executors创建线程池

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

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

package itheima.demo.ThreadSafe;
​
import java.util.concurrent.*;
​
public class Demo8 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        Future<String> f1=pool.submit(new MyCallable(50));
        Future<String> f2=pool.submit(new MyCallable(50));
        try {
            System.out.println(f1.get());
            System.out.println(f2.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

newFixedThreadPool(int nThread) 创建固定数量的线程

newSingleThreadExecutor() 创建只有一个线程的线程池对象

newCachedThreadPool() 线程数量随着任务数增加而增加

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

Executors使用可能存在的陷阱

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

  • CachedThreadPoo和ScheduledThreadPool允许的创建线程数量为Integer.MAX_VALUE,可能会堆积大量线程,从而导致OOM

  • 该工具类底层基于ThreadPoolExecutor实现的线程池对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值