Java多线程基础

线程简介

程序进程线程

程序指令数据有序集合静态概念

进程:程序的一次执行过程,动态概念。系统资源分配单位

线程独立执行的路径(运行程序时,即使没有创建线程后台会有多个线程,主线程,gc线程)

注意:真正的多线程是有多个CPU(多核),如服务器。模拟多线程优于切换速度很快,就会有多线程的错觉

线程实现(重点)

三种创建方式

  1. Thread class(继承Thread类)

  2. Runnable接口(实现Runnable接口)

  3. Callable接口(实现Callable接口)

注意线程开启不一定执行,由CPU调度

Thread class

  1. 创建类继承自Thread类

  2. 重写run()方法

  3. 调用start()方法开启线程

public class TestThread1 extends Thread {
    @Override
    public void run() {
        // run方法体
    }

    public static void main(String[] args) {
        
        TestThread1 t1 = new TestThread1();
        // 调用start()方法
        t1.start();
    }
}

Runnable(推荐使用)

  1. 声明类实现Runnable接口

  2. 重写run()方法

  3. 创建类对象

  4. 创建Thread时作为参数传递,调用start()方法

public class TestThread3 implements Runnable{
    @Override
    public void run() {
        // run方法体
    }

    public static void main(String[] args) {
        // 创建一个线程对象
        TestThread3 t3 = new TestThread3();

        // 线程丢入Thread类,调用start方法
        new Thread(t3).start();
    }
}

龟兔赛跑 - Race

静态代理

StaticProxy静态代理,Runnable实现的原理

public class StaticProxy {
    public static void main(String[] args) {
        // 创建对象,相当于创建实现了Runnable接口的对象
        You y1 = new You();
        // 相当于将对象传入Thread类并调用start方法
        new WeddingCompany(y1).HappyMarry();
        
        //可简写为:    new WeddingCompany(new You()).HappyMarry();
    }
}

// marry接口,相当于Runnable接口
interface Marry{
    void HappyMarry();
}

// You类,相当于实现Runnable接口的类
class You implements Marry {
    @Override
    public void HappyMarry(){
        System.out.println("你要结婚了");
    }
}

// 代理角色,帮助你结婚,相当于Thread类
class WeddingCompany implements Marry{
	
    private Marry target;
    
    // 传入you对象,相当于有参构造Thread类
    public WeddingCompany(Marry target){
        this.target = target;
    }
    // 具体的实现细节,相当于重写Run方法
    @Override
    public void HappyMarry(){
        before();
        this.target.HappyMarry();
        after();
    }

    private void after() {
        System.out.println("结婚后,补尾款");
    }

    private void before() {
        System.out.println("结婚前,布置现场");
    }
}

lambda表达式在接口中的使用

函数式接口:只包含一个抽象方法的接口

lambda表达式在函数式接口中的使用:类名 对象名 = (参数类型 形参名) -> { 需要重写的语句块; };

简化重写方法的部分代码

可简化的部分

  1. 参数类型

  2. 括号

  3. 花括号

public class Test {
    public static void main(String[] args) {

        Person person = (int a)-> {System.out.println("person eat" + a);};
        
        // 1.  (a)-> {System.out.println("person eat" + a);};
        
        // 2.  a-> {System.out.println("person eat" + a);};
        
        // 3.  a-> System.out.println("person eat" + a);
        person.eat(1);
        
    }
}


interface Person{
    void eat(int a);
}

Callable

  1. 实现Callable接口,需要返回值

  2. 重写call()方法,需要抛出异常

  3. 创建目标对象

  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool()

  5. 提交执行:Future result1 = ser.submit(t1)

  6. 获取结果:boolean r1 = result1.get()

  7. 关闭服务:ser.shutdownNow()

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

public class TestCallable implements Callable<Boolean> {
    private String url;
    private String name;

    public TestCallable(String url, String name){
        this.name = name;
        this.url = url;
    }

    @Override
    public Boolean call() {
        // 创建 WebDownloader 对象
        WebDownloader webDownloader = new WebDownloader();
        // 调用downloader方法,传入
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为:" + name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 新建对象并传参 url,name
        TestCallable t1 = new TestCallable("https://img2020.cnblogs.com/blog/2459624/202107/2459624-20210728230730532-2056940147.png","1.png");
        TestCallable t2 = new TestCallable("https://img2020.cnblogs.com/blog/2459624/202107/2459624-20210728203411378-1829797592.png", "2.png");
        TestCallable t3 = new TestCallable("https://img2020.cnblogs.com/blog/2459624/202107/2459624-20210728203430153-280296762.png","3.png");

        // 创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);

        // 提交执行
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);

        // 获取结果

        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();

        // 关闭服务
        ser.shutdownNow();

    }
}


// 下载器
class WebDownloader{
    // 下载方法
    public void downloader(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downloader方法出现问题");
        }
    }

}

线程状态

线程五大状态

  • 创建状态

  • 就绪状态

  • 阻塞状态

  • 运行状态

  • 死亡状态

线程停止

设置一个标志位,通过公开的方法转换标志位

//1.建议线程正常停止-->利用次数,不建议死循环
//2.建议使用标志位--->设置一个标志位
//3.不要使用stop或者destroy等过时或JDK不建议使用的方法

public class TestStop implements Runnable{

    //1.设置一个标志位
    private boolean flag = true;

    @Override
    public void run(){
        int i = 0;
        while (flag){
            System.out.println("run...Thread" + i++);
        }
    }

    //2.设置一公开方法停止线程,转换标志位
    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop ts1 = new TestStop();

        new Thread(ts1).start();

        for (int i = 0; i < 200; i++) {
            System.out.println("main" + i);
            if (i==50){
                ts1.stop();
                System.out.println("Thread线程停止了");
            }
        }
    }

}

线程休眠sleep方法

  • sleep方法不会释放对象的
// 模拟倒计时
// 打印系统当前时间

Thread.sleep(1000);//参数为毫秒

线程礼让yield方法

Thread.currentThread().getName():获取当前线程的name

//测试礼让线程
// 礼让不一定成功,看CPU心情
public class TestYield {
    public static void main(String[] args) {
        MyYield my1 = new MyYield();

        new Thread(my1,"a").start();
        new Thread(my1,"b").start();
    }

}

class MyYield implements Runnable {
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"线程开始");
        Thread.yield();  //礼让
        System.out.println(Thread.currentThread().getName()+"线程停止");
    }
}

注意:线程礼让会让线程重新进入就绪状态,重新和其他线程进行资源的抢夺

强制执行:join方法

注意:在调用join方法前,线程是同步执行的,调用join方法后其他方法进入阻塞状态

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 500; i++) {
            System.out.println("vip线程来到了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException{

        TestJoin tj1 = new TestJoin();
        
        Thread thread = new Thread(tj1);
        
        thread.start();

        // 主线程
        for (int i = 0; i < 250; i++) {
            if (i == 200) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("主线程"+i);

        }
    }
    
}

线程优先级

注意优先级高的线程,执行还是看CPU调度

// 线程优先级用数字表示
Thread.MIN_PRIORITY = 1;

Thread.MAX_PRIORITY = 10;

Thread.NORM_PRIORITY = 5;  // 线程优先级默认为5


Thread t1 = new Thread();

// 获取优先级方法
t1.getPriority();

// 改变优先级方法
t1.setPriority(int 8);

守护线程

daemon:守护线程(音同demon)

  1. 线程分为用户线程守护线程
  2. 虚拟机不用等待守护线程执行完成
  3. 虚拟机必须确保用户线程执行完毕
Thread t1 = new Thread();
t1.setDaemon(true);  //默认为false表示用户线程

线程同步(重点)

并发,锁机制与死锁

并发同一个对象多个线程 同时操作

锁机制:当一个线程获得排它锁独占资源其他线程必须等待,使用后释放锁即可。

锁机制可能出现的问题

  • 优先级倒置

  • 性能问题

  • 其他需要某线程挂起

死锁两个(或多个)线程占有对方所需的锁形成僵局

  • 某一同步块同时拥有两个以上对象的锁时,可能会发生死锁问题

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
  • 上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

synchronized同步

同步机制:synchronized方法必须获得对象的才可以执行

每个对象有一把锁!!!

// 该方法为同步方法
public synchronized void method(int args) {}

synchronized同步块

// 同步块
synchronized(Obj) {}
  • Obj称之为同步监视器(就是对象),推荐使用共享资源作为同步监视器

  • 同步方法中无需指定同步监视器,其Obj为this,即对象本身

  • Obj为属性需要进行修改(同步)的对象

同步监视器执行过程

  1. 第一个线程访问,锁定同步监视器,执行其中代码.

  2. 第二个线程访问 ,发现同步监视器被锁定,无法访问.

  3. 第一个线程访问完毕,解锁同步监视器.

  4. 第二个线程访问, 发现同步监视器没有锁,然后锁定并访问

Lock同步

Lock锁ReentrantLock类的lock方法

private final ReentrantLock lock = new ReentrantLock();

public void m() {
while (true){
    
    lock.lock();  // 加锁
    try{
        // 保证线程安全的代码
    }
    finally {
        // 解锁
        lock.unlock();
    }

synchronized与Lock对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁, 出了
    作用域自动释放
  • Lock只有代码块锁synchronized代码块锁方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展
    性(提供更多的子类)
    • 优先使用顺序:Lock >同步代码块(已经进入了方法体,分配了相应资源) >同步方法(在方法体之外)

线程通信问题

生产者消费者问题

相关方法

wait()线程等待,直到其他线程通知,与sleep不同,会释放锁

notify()唤醒一个处于等待状态线程

notifyAll():唤醒同一个对象所有调用wait()方法的线程优先级别高的线程优先调度

注意:均是Object类的方法,都只能在同步方法或者同步代码块
使用,否则会抛出异常llegalMonitorStateException

管程法

并发协作模型:“生产者/消费者模式”--->管程法

Producer生产数据模块

Consumer处理数据模块

缓冲区:消费者间接从生产者拿数据

Producer --> 数据缓存区 --> Consumer

信号灯法

并发协作模型:“生产者/消费者模式”--->信号灯法

线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize: 核心池的大小
    • maximumPoolSize: 最大线程数
    • keepAliveTime: 线程没有任务时最多保持多长时间后会终止

用到ExecutorsExecutorService

//1.创建服务,创建线程池
//newFixedThreadPool  参数:线程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);

//执行(4个线程)
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());

//2.关闭连接
service.shutdownNow();
posted @ 2021-08-16 00:45  Coline1  阅读(44)  评论(0编辑  收藏  举报