多线程基础

什么是线程?线程与进程区别?

进程:是程序的基本执行实体。
线程:是进程的一个实体,是cpu调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。
电脑系统每一个正在运行的软件都可以看作是一个进程,而每个软件里面包含了很多功能,而这些互相独立且可以同时运行的功能就形成了多线程。

并行与并发

并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。

多线程三种实现方式

第一种:继承Thread类

继承Thread类重写run方法创建线程,可以很方便通过创建对象调用方法,实现简单但不可以继承其他类;
1.创建MyThread类,继承Thread类重写run方法

public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println("这是继承Thread类");
    }
}

2.创建对象,调用start方法启动线程

public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
}

第二种:实现Runnable接口

实现Runnable接口并重写run方法,可以避免单继承局限性,编程更加灵活,实现解耦。
1.创建MyThread类,实现Runnable接口并重写run方法

public class MyThread implements Runnable{
    @Override
    public void run(){
        System.out.println("这是实现Runnable接口");
    }
}

2.启动线程

public static void main(String[] args) {
        MyThread myThread = new MyThread();
        //创建线程对象
        Thread thread = new Thread(myThread);
        //启动线程
        thread.start();
}

第二种:实现Callable接口

实现Callable接口并重写run方法,可以获得线程执行结果的返回值,并可以抛出异常。
1.创建MyThread类,实现Runnable接口并重写call方法

public class MyThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("这是实现Callable接口");
        return "这里是返回值";
    }
}

2.启动线程

  public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
       //创建FutureTask对象,管理多线程运行结果
        FutureTask<String> stringFutureTask = new FutureTask<>(myThread);
        //创建线程对象
        Thread thread = new Thread(stringFutureTask);
        //启动线程
        thread.start();
        //获取线程返回值
        String result = stringFutureTask.get();
        System.out.println(result);
    }

多线程常用方法

多线程常用方法

线程优先级

线程优先级范围是(1-10),默认是5。理论上优先级越高,线程抢到执行权的概率越高,但是注意:因为线程抢到执行权是概率问题,所以即使线程1比线程2优先级更高,也可能是线程2抢到执行权.

public class MyTest extends Thread {
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(String.format("%s线程:执行到了%d",Thread.currentThread().getName(),i+1));
        }
    }
}
public static void main(String[] args) {
    MyTest myTest = new MyTest();
    //创建线程对象,并指定线程名称
    Thread thread1 = new Thread(myTest,"线程1");
    Thread thread2 = new Thread(myTest,"线程2");
    //打印线程优先级(此时是默认的,也就是5)
    System.out.println(thread2.getPriority());
    System.out.println(thread1.getPriority());
    //设置线程优先级
    thread1.setPriority(10);
    thread2.setPriority(1);
    //启动线程
    thread1.start();
    thread2.start();
}

守护线程

守护线程与非守护线程区别:当所有非守护线程执行完,守护线程如果没执行完,会直接结束。

public class MyTest1 extends Thread {
    @Override
    public void run(){
        for (int i = 0; i < 10; i++) {
            System.out.println(String.format("%s线程:执行到了%d",Thread.currentThread().getName(),i+1));
        }
    }
}
public class MyTest2 extends Thread {
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(String.format("%s线程:执行到了%d",Thread.currentThread().getName(),i+1));
        }
    }
}
public static void main(String[] args) {
    MyTest1 myTest1 = new MyTest1();
    MyTest2 myTest1 = new MyTest2();
    //指定线程名称
    myTest1.setName("非守护")
    myTest2.setName("守护")
    //指定myTest2为守护线程
    myTest2.setDaemon(true);
    //启动线程(当线程1执行完毕,线程2跟随线程1结束)
    thread1.start();
    thread2.start();
}

出让线程

出让当前CPU的执行权,让线程重新抢占执行权。(全部线程抢占执行权,也有可能是当前出让的线程再次抢到执行权)

public class MyTest extends Thread {

        @Override
        public void run(){
            for (int i = 0; i < 100; i++) {
                System.out.println(String.format("%s线程:执行到了%d",getName(),i+1));
                //出让线程:出让当前CPU的执行权,让线程重新抢占执行权
                Thread.yield();
            }
        }

}

插入线程

如下代码,想等myTest线程执行完毕,再执行最后一句打印。

public class MyTest extends Thread {
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(String.format("%s线程:执行到了%d",Thread.currentThread().getName(),i+1));
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    MyTest myTest = new MyTest();
    myTest.start();
    //插入线程
    myTest.join();
    System.out.println("我想等前面代码执行完,在执行!");
}

线程生命周期

线程生命周期
注意:线程在运行代码时出现其他状况进入阻塞等待后,如果线程恢复后,不会接着进入运行状态,而是进入就绪状态,等待抢占执行权.

在多线程编程中,锁的作用是保护临界区的代码,避免多个线程同时访问共享资源而导致的数据不一致、线程竞争、死锁等问题。在定义上一般分为悲观锁乐观锁
悲观锁:认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。悲观锁在Java中,synchronized关键字和Lock的实现类都是悲观锁。
乐观锁:认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法。
代码示例(synchronized):


public class MyTest extends Thread {
    //static静态变量(多个对象下可以共享数据)
    private static int count = 100;
        @Override
        public void run(){
            while (true) {
                synchronized (MyTest.class) { //这里将该类当做锁对象,注意锁对象要唯一
                    try {
                        Thread.sleep(100);//这里睡眠100毫秒
                        if (count == 0) {
                            break;
                        } else {
                            System.out.println(getName() + "线程:获得数据" + count);
                            count--;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
}
public static void main(String[] args) throws InterruptedException {
    //此处我创建了两个对象,这里也是为什么将count变成静态变量,如果是一个就不需要了
    MyTest myTest1 = new MyTest();
    MyTest myTest2 = new MyTest();
    //设置线程名称
    myTest1.setName("A");
    myTest2.setName("B");
    //启动线程
    myTest1.start();
    myTest2.start();
}

代码示例(lock):


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

public class MyTest extends Thread {
    //static静态变量(多个对象下可以共享数据)
    private static int count = 100;
    static Lock lock = new ReentrantLock();//创建lock锁的实现类
    @Override
    public void run(){
        while (true) {
            lock.lock();//调用加锁方法
            try {
                Thread.sleep(100);//这里睡眠100毫秒
                if (count == 0) {
                    break;
                } else {
                    System.out.println(getName() + "线程:获得数据" + count);
                    count--;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //把锁放到finally里面防止锁未被释放造成死锁,finally里面代码最终都会被执行
                lock.unlock();//释放锁
            }
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    //此处我创建了两个对象,这里也是为什么将count变成静态变量,如果是一个就不需要了
    MyTest myTest1 = new MyTest();
    MyTest myTest2 = new MyTest();
    //设置线程名称
    myTest1.setName("A");
    myTest2.setName("B");
    //启动线程
    myTest1.start();
    myTest2.start();
}

线程池

线程池是一组已经初始化并等待执行任务的线程集合,通过使用线程池,我们可以避免频繁地创建和销毁线程,从而节省资源和减少系统开销。线程池的核心思想是通过复用线程来提高性能
线程池工作流程图如下:
线程池工作流程图
Java 提供了 java.util.concurrent 包来支持并发编程。创建线程池的方法很多,以下代码利用ThreadPoolExecutor创建自定义线程池:

// 创建全局线程池
public class GlobalThreadPool {
    private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            5, // 最大核心线程数
            10, // 最大线程数,要大于核心线程数
            60, // 空闲线程存活时间
            TimeUnit.SECONDS, // 时间单位
            new ArrayBlockingQueue<>(3),//任务队列
            Executors.defaultThreadFactory(),//创建线程工厂
            new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
            
    );
 
    // 将线程池设置为全局变量
    public static ThreadPoolExecutor getThreadPool() {
        return threadPool;
    }
}

线程池常见方法

  • 执行无返回值的线程任务void execute(Runnable command):execute()只能提交Runnable类型的任务,没有返回值
  • 提交有返回值的线程任务Future submit(Callable task):submit()既能提交Runnable类型任务也能提交Callable类型任务,可以返回Future类型结果,用于获取线程任务执行结果
  • 关闭线程池:void shutdown(); 或 shutdownNow();
    • shutdown 会等待正在执行的任务先完成,然后再关闭
    • shutdownNow 会立刻停止正在执行的任务
  • 等待线程池关闭:boolean awaitTermination(long timeout, TimeUnit unit);
    具体区别可参考:https://blog.youkuaiyun.com/2301_79694645/article/details/142531117

线程池任务拒绝策略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值