Java多线程

多线程

程序:为完成特定任务,用某种语言编写的一组指令的的集合。即一段静态的代码,静态对象

进程:是程序的一次执行过程,或者正在运行的程序。是一个动态的过程,是资源的分配单,

线程:是一个程序内部的一条执行路径,线程是CPU调度和执行的单位,独立的执行路径

若一个进程同一时间并行执行多个线程,就是支持多线程的

单核CPU 多核CPU

并行:多个CPU同时执行多个任务

并发:一个CPU同时执行多个任务

多线程:

优点:

  1. 提高应用程序的响应
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构

线程的优先级:

  • MAX_PRIORITY:10
  • MIN _PRIORITY:1
  • NORM_PRIORITY:5 -->默认优先级

获取和设置当前线程的优先级:

  • getPriority():获取线程的优先级
  • setPriority(int p):设置线程的优先级

说明:高优先级的线程要抢占低优先级线程CPU的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。

线程通信:wait() / notify() / notifyAll() :此三个方法定义在Object类中的

线程创建

方式一:继承Tread类

Java虚拟机允许应用程序同时执行多个执行线程,通过java.lang.Thread类实现

  1. 创建一个继承与Tread类的子类
  2. 重写Tread中的run()方法
  3. 创建Tread类的子类的对象
  4. 通过此对象调用start()
例如:
//打印100以内的偶数
public class Main {

    public static void main(String[] args) {
        MyTread myTread = new MyTread();
        myTread.start();
    }
}
class MyTread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++){
            if (i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}

网图下载

方式二:实现Runnable接口(开发中优先选择)

  1. 创建一个实现Runnable接口的类
  2. 实现run()方法
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Tread类的构造器中,创建Tread类的对象
  5. 调用start()方法启动线程
public class Main {

    public static void main(String[] args) {
        MyTread myTread = new MyTread();
        Thread thread = new Thread(myTread);
        thread.start();
    }
}
class MyTread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++){
            if (i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}
例如:
//龟兔比赛
public class Main {

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"rabbit").start();
        new Thread(race,"tortoise").start();
    }
}
class Race implements Runnable{

    //胜利者
    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++){
            //模拟兔子睡觉
            if (Thread.currentThread().getName().equals("rabbit") && i%10==0){
                try {
                    Thread.sleep(200);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag = gameover(i);
            //比赛结束,停止程序
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName() + "跑了" + i);
        }
    }

    //判断是否完成比赛
    private boolean gameover(int steps){
        //判断是否有胜利者
        if (winner != null){
            return true;
        }{
            if (steps >= 100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is " + winner);
                return true;
            }
        }
        return false;
    }
}

方式三:实现Callable接口

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象:
  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1)
  5. 提交执行:Future<Boolean> result11 = ser.submit(t1)
  6. 获取结果:boolean r1 = result1.get()
  7. 关闭服务:set.shutdownNow()
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {

    public static void main(String[] args) {
        MyTread myTread = new MyTread();
        FutureTask futureTask = new FutureTask(myTread);
        new Thread(futureTask).start();

        try {
            //get()返回值为FutureTask构造参数Callable实现类重写的call()的返回值
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class MyTread implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

好处:

  • 可以定义返回值
  • 可以抛出异常
  • 支持泛类的的返回值
  • 借助Future Task类

方式四:线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程对性能影响很大

解决方案:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用

好处:

  • 提高响应速度(减少创建新线程的时间)

  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

  • 便于线程管理

    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持的时间
    

实现方法:

  1. 提供指定线程数量的线程池
  2. 执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
  3. 关闭连接池

线程池相关的 API:ExecutorServiceExcutors

  • ExecutorService

    void execute(Runnable command):执行任务/命令,没有返回值
    <T>Future<T>submit(Callable<T>task):执行任务,有返回值
    void shutdown():关闭连接池
    
  • Excutors:工具类、线程池的工厂类,用于创建和返回不同类型的线程池

    Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
    Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
    Executors.newScheduledThreadPool(n):创建一个线程池,它可以安排在给定延迟后运行命令或定期执行
    
import java.util.concurrent.*;

public class Main {

    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPool参数为池子大小
        ExecutorService service = Executors.newFixedThreadPool(10);
        //执行
        service.execute(new MyTread());
        service.execute(new MyTread());
        service.execute(new MyTread());
        //2.关闭连接
        service.shutdown();
    }
}
class MyTread implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

代理模式

静态代理模式:真实对象和代理对象要实现同一个接口,代理对象要代理成真实角色

Lamda表达式

避免匿名内部类定义过多

new Thread(()->System.out.println("多线程")).start();

函数式接口:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口

public interface Runnable{
    public default void run();
}

对于函数式接口,可以通过lambda表达式来创建该接口的对象

public class Main {

    public static void main(String[] args) {

        ILove love = null;
        love = a-> System.out.println("i love you--->" + a);
        love.Love(520);
    }
}
interface ILove{
    void Love(int a);
}

线程的生命周期

新建,就绪,运行,阻塞,死亡

void start();//启动线程,并执行对象的run方法
run();//线程在被调用时的操作
String getName();//返回线程的名字
void setName(String name);//设置该线程的名字
static Tread current Tread();//返回当前线程,在Tread子类中就是this,通常用于主线程和Runnable实现类
static yield();//线程让步,释放当前CPU执行
join();//
static void sleep(long millitime);//让当前线程“睡眠”指定的millitime毫秒,抛出InterruptedException异常
stop();//强制线程生命周期结束
boolean isAlive();//返回boolean,判断线程是否活着

守护线程

线程分为用户线程和守护线程

虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕

线程的同步

多个线程执行的不确定性引起结果不确定性,多个线程对账本的共享,会造成操作的不完全性,会破坏数据

synchronized关键字,包括synchronized方法和synchronized块

方法一:同步代码块

synchronized(同步监视器){
    //需要被同步的代码(操作共享数据的代码,共享数据:多个线程共同操作的变量)
}

Obj称为同步监视器

同步监视器俗称锁,任何一个类的对象都可以充当锁,多个线程公用一把锁

方法二:同步方法

public synchronized void method(int args){}

synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则会阻塞,方法执行,占用该锁,方法返回释放锁

非静态的同步方法,同步监视器是this,静态的同步方法,同步监视器是当前类本身

死锁

死锁产生的四个必要条件:

  1. 互斥条件
  2. 请求与保持条件
  3. 不剥夺条件
  4. 循环等条件

Lock(锁)

通过显示定义同步锁对象来实现同步,同步锁使用Lock对象充当

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,每个只能有一个线程对Lock对象加锁,线程开始访问共享资源前应该先获得Lock对象

ReentrantLock实现了Lock,有与synchronized相同的并发性和内存语义,可以显示加锁,释放锁

import java.util.concurrent.locks.ReentrantLock;

public class Main {

    public static void main(String[] args) {
        TestLock testLock = new TestLock();
        new Thread(testLock).start();
        new Thread(testLock).start();
        new Thread(testLock).start();
    }
}
class TestLock implements Runnable{
    int ticketNums = 10;
    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();
                if (ticketNums > 0){
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }else {
                    break;
                }
            }finally {
                //解锁
                lock.unlock();
            }
        }
    }
}

Lock是显示锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放

Lock只有代码块锁,synchronized有代码块锁和方法锁

使用Lock锁,JVM调线程花费时间少,有更多的扩展性

优先使用顺序:Lock>同步代码块>同步方法

线程的通信

wait():一旦执行该方法,当前线程进入阻塞状态,并释放同步监视器
wait(long timeout):指等待的毫秒数
notify():一旦执行该方法,就会唤起被wait的一个线程,如果有多个线程,唤醒优先级高的
notifyAll():一旦执行该方法,就会唤醒所有wait的线程
  1. 只能出现在同步代码块或同步方法中
  2. 必须是同步代码块或同步方法中的同步监视器,否则会出现IllegalMonitorStateException异常
  3. 定义在java.lang.Object类中

sleep()和wait()的异同:

  • 相同点:一旦执行该方法,都可以使当前线程进入阻塞状态
  • 不同点:
    1. 声明位置不同:Tread中声明sleep,Object中声明wait
    2. 调用范围(要求)不同:sleep可以在任何需要的场景下调用,wait只能出现在同步代码块或同步方法中
    3. 关于释放同步监视器:若均使用在同步代码块或同步方法中,sleep不会释放锁,wait会释放锁

生产消费者问题

线程同步问题,生产者和消费者共享一个资源,互相依赖互为条件

synchronized可阻止并发更新同一个共享资源,实现了同步;不能用来实现不同线程之间的消息传递(通信)

解决方法:

  • 管程法

    生产者:负责生产数据的模块(可能是方法,对象,线程,进程)

    消费者:负责处理数据的模块(可能是方法,对象,线程,进程)

    缓存区:消费者不能直接使用生产者的数据,存在“缓冲区”

    生产者将生产好的数据放入缓冲区,消费者从缓冲区取出数据

    public class Main {
    
        public static void main(String[] args) {
            SynContainer container = new SynContainer();
            new Productor(container).start();
            new Consumor(container).start();
        }
    }
    
    //生产者
    class Productor extends Thread{
        SynContainer container;
        public Productor(SynContainer container){
            this.container = container;
        }
        //生产
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("生产了:" + i);
                container.push(new Chicken(i));
            }
        }
    }
    
    //消费者
    class Consumor extends Thread{
        SynContainer container;
        public Consumor(SynContainer container){
            this.container = container;
        }
        //消费
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("消费了:" + container.pop().id);
            }
        }
    }
    //产品
    class Chicken{
        int id;//产品编号
        public Chicken(int id){
            this.id = id;
        }
    }
    
    //缓冲区
    class SynContainer{
        //容器大小
        Chicken[] chickens = new Chicken[10];
        //容器计数器
        int count = 0;
    
        //生产者放入产品
        public synchronized void push(Chicken chicken){
            //容器满了
            if (count == chickens.length){
                //通知消费者消费,生产等待
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //容器没有满
            chickens[count] = chicken;
            count++;
            //可以通知消费者消费
            this.notifyAll();
        }
        //消费者消费
        public synchronized Chicken pop(){
            //判断是否可以消费
            if (count == 0){
                //等待生产者生产,消费者等待
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //若可以消费
            count--;
            Chicken chicken = chickens[count];
            //通知生产者生产
            this.notifyAll();
            return chicken;
        }
    }
    
  • 信号灯法

    通过标志位解决

    public class Main {
    
        public static void main(String[] args) {
            TV tv = new TV();
            new Actor(tv).start();
            new Audience(tv).start();
        }
    }
    
    //生产者-->演员
    class Actor extends Thread{
        TV tv = new TV();
        public Actor(TV tv){
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if (i % 2 == 0){
                    this.tv.play("抖音");
                }else {
                    this.tv.play("快手");
                }
            }
        }
    }
    
    //消费者-->观众
    class Audience extends Thread{
        TV tv = new TV();
        public Audience(TV tv){
            this.tv = tv;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                tv.watch();
            }
        }
    }
    //产品-->节目
    class TV{
        //演员表演,观众等待
        //观众观看,演员等待
        String name;//节目名
        boolean flag = true;
    
        //表演
        public synchronized void play(String name){
            if (flag != true){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("演员表演:" + name);
            //通知观众观看
            this.notifyAll();
            this.name = name;
            this.flag = !this.flag;
        }
    
        //观看
        public synchronized void watch(){
            if (flag == true){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("观众观看:" + name);
            //通知演员表演
            this.notifyAll();
            this.flag = !this.flag;
        }
    
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值