【Java】左右互搏?上下左右互搏?(多线程使用基础)

【Java】左右互搏?上下左右互搏?(多线程使用基础)

多线程基础知识

线程和进程

  1. 进程:在内存中执行的应用程序
  2. 线程:进程中最小的执行单元
    1. 作用:负责当前进程中程序的运行,一个进程中至少有一个线程,一个进程还可以有多个线程,这样的应用程序就称之为多线程程序。
  3. 使用场景:软件中的耗时操作:拷贝大文件,加载大量资源。聊天软件、后台服务器等。
    1. 一个线程可以干一件事,我们可以同时做多件事,提高CPU的利用率。

并发和并行

  1. 并行
    1. 在同一个时刻,有多个执行在多个CPU上面执行(多个人做不同的事情)
  2. 并发
    1. 同一个时刻,有多个指令,在单个CPU上(交替)执行
  3. 细节:
    1. 之前的CPU是单核,但是在执行多个程序的时候好像是在同时执行,原因是CPU在多个线程之间作高速切换。
    2. 现在咱们的CPU都为多核多线程的,例如2核4线程,则CPU可以同时运行4个线程,此时不用切换,但是如果多了超出了,CPU就要切换了,所以现在CPU在执行程序的时候并发和并行都存在。

CPU调度

  1. 分时调度:让所有线程轮流获取CPU使用权,并且平均分配每个线程占用CPU的时间片
  2. 抢占式调度:多个县城轮流抢占CPU使用权,哪个线程抢到了就先执行,一般都是优先级较高的先抢到CPU使用权几率大。

主线程介绍

  1. CPU和main方法之间专门为其开辟的线程通道

创建线程的方式(重点)

第一种方式extends Thread

  1. 定义一个类,继承Thread
  2. 重写run方法,在run方法中设置线程任务(所谓线程任务指的是此线程要干的具体的事,具体执行的代码)
  3. 创建自定义线程类的对象
  4. 调用Thread中的start方法,开启线程,JVM自动调用run方法

Thread中的方法

  1. 常用方法
   void run() 设置线程任务,这个run方法是Thread重写的接口Runnable中的run方法
   String getName() 获取线程名字
   void setName() 给线程设置名字
   static Thread currentThread() 获取正在执行的线程对象(此方法在哪个线程中使用,获取哪个的线程对象)
   static void sleep(long millis) 线程睡眠,超时后自动醒来继续执行,传递毫秒值

常用方法使用示例:

   
   public class test {
       public static void main(String[] args) {
           MyThread t1 = new MyThread();
           t1.setName("柳真阳学长的线程");
           t1.start();
           for (int i = 0; i < 100; i++) {
               System.out.println(Thread.currentThread().getName() + "main线程执行了"+i);
           }
       }
   }
   
   public class MyThread extends Thread{
       @Override
       public void run() {
           for (int i = 0; i <100; i++) {
               System.out.println(getName()+ "Thread...执行了"+i);
           }
       }
   }
  1. 优先级&守护&礼让&插入线程方法
   void setPriority(int newPriority)
   //获取线程优先级
   int getPriority()
   //设置为守护线程,当非守护线程执行完毕,守护线程停止执行
   //注:但守护线程并非立刻结束,当非守护线程结束之后,系统会告知守护线程结束
   void setDaemon(boolean on)
   //礼让线程(让当前线程让出CPU使用权)
   static void yield()
   //插入线程,或者插队线程
   void join()

获取两个线程优先级

MIN_PRIORITY = 1 最小优先级 1
NORM_PRIORITY = 5 默认优先级 5
MAX_PRIORITY = 10 最大优先级 10
  1. 优先级相关方法示例:
package com.ham.testhahathread;

public class Test01 {
    public static void main(String[] args) {
        MyThreadha t1 = new MyThreadha();
        t1.setName("lzy");
        MyThreadha t2 = new MyThreadha();
        t2.setName("dyp");

        t1.setPriority(1);
        t2.setPriority(10);

        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());

        t1.start();
        t2.start();
    }
}
package com.ham.testhahathread;

public class MyThreadha extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "执行一下");
        }
    }
}
  1. 守护线程示例:
package com.ham.testhahathread;

public class Test01 {
    public static void main(String[] args) {
        MyThreadha t1 = new MyThreadha();
        t1.setName("lzy");
        MyThreadha11 t2 = new MyThreadha11();
        t2.setName("dyp");

        //将t2设置为守护线程
        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}
package com.ham.testhahathread;

public class MyThreadha11 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "执行一下heiheihie");
        }
    }
}
package com.ham.testhahathread;

public class MyThreadha extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "执行一下");
        }
    }
}
  1. 礼让线程

场景说明:如果两个线程一起执行,可能会执行一会线程A,再执行一会线程B,或者可能线程A都执行完毕,线程B再执行,那么我们是否可以让两个线程尽可能的平衡一些(尽量让两个线程交替执行)

注意:只是尽可能地平衡,不是绝对的你来我往,有可能线程A先执行。

package com.ham.threadhahha;

public class Test01 {
    public static void main(String[] args) {
        MyThreadha t1 = new MyThreadha();
        t1.setName("lzy");
        MyThreadha t2 = new MyThreadha();
        t2.setName("dyp");

        t1.start();
        t2.start();
    }
}
package com.ham.threadhahha;

public class MyThreadha extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "执行一下");
            Thread.yield();
        }
    }
}

创建线程的方式2(实现Runnable接口)

  1. 创建类,实现Runnable接口
  2. 重写run方法,设置线程任务
  3. 利用Thread类中的构造方法:Thread(Runnable target),创建Thread对象(线程对象),将自定义的类当参数传递到Thread的构造中 让我们自己定义的类成为一个真正的线程类对象
  4. 调用Thread中的start方法,开启线程,JVM自动调用run方法

package com.ham.takeatest;

import com.ham.testhahathread.MyThreadha;

public class test01 {
    public static void main(String[] args) {
        MyRunnable m = new MyRunnable();
        Thread t1 = new Thread(m);

        t1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "Running");
        }
    }
}
package com.ham.takeatest;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "Running!!!");
        }
    }
}

两种实现多线程的方式区别

  1. 继承Thread:继承只支持单继承,有继承的局限性
  2. 实现Runnable:没有继承的局限性,MyThread extends Fu implements Runnable

匿名内部类创建多线程

  1. 严格意义上说,匿名内部类方式,不属于创建多线程方式其中之一,因为匿名内部类形式建立在实现Runnable接口的基础上完成的
匿名内部类回顾:
new 接口/抽象类(){
    重写方法
}.重写的方法();
==================
接口名/类名 对象名 = new 接口/抽象类(){
    重写方法
}
对象名.重写的方法

线程安全

同步代码块

  1. 线程安全问题示例:
 package com.thread.atest;
 
 public class test {
     public static void main(String[] args) {
         MyTicket myTicket = new MyTicket();
         Thread t1 = new Thread(myTicket,"张三");
         Thread t2 = new Thread(myTicket,"李四");
         Thread t3 = new Thread(myTicket,"lzy");
 
         t1.start();
         t2.start();
         t3.start();
 
     }
 }
public class MyTicket implements Runnable{
    int ticket = 100;
    @Override
    public void run() {
        while (ticket>0){
            if(ticket > 0){
                System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                ticket--;
            }
        }
    }
}
  1. 原因:CPU在多个线程之间做高速切换导致的
  2. 使用同步代码块解决线程安全问题
//格式
synchronized(任意对象){
    线程可能出现不安全的代码
}
//任意对象:就是我们的锁对象
  1. 执行:一个线程拿锁之后,会进入到同步代码块中进行,在此期间,其他线程拿不到锁,就进不去同步代码块,需要在同步代码块外面等待排队,需要等着执行的线程执行完毕,除了同步代码块,相当于将锁释放,等待的线程才能抢到锁,才能进入到同步代码块中进行。

  2. 使用实例:

    package com.thread.btest;
    
    public class test {
        public static void main(String[] args) {
            MyTicket myTicket = new MyTicket();
            Thread t1 = new Thread(myTicket,"张三");
            Thread t2 = new Thread(myTicket,"李四");
            Thread t3 = new Thread(myTicket,"lzy");
    
            t1.start();
            t2.start();
            t3.start();
    
        }
    }
    
    package com.thread.btest;
    public class MyTicket implements Runnable{
        int ticket = 100;
    
        //任意new一个对象
        Object obj = new Object();
    
        @Override
        public void run() {
            while (ticket>0){
                try {
                    Thread.sleep(100L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (obj) {
                    if (ticket > 0) {
                        ticket--;
                        System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                    }
                }
            }
        }
    }
    

同步方法

普通同步方法_非静态

格式:
修饰符 synchronized 返回值类型 方法名(参数){
    方法体
    return 结果
}

默认锁:this

使用示例:

public class test {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket,"张三");
        Thread t2 = new Thread(myTicket,"李四");
        Thread t3 = new Thread(myTicket,"lzy");

        t1.start();
        t2.start();
        t3.start();

    }
}
public class MyTicket implements Runnable{
    int ticket = 100;


    @Override
    public void run() {
        while (ticket>0){
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            method01();
        }
    }
    public synchronized void method01(){
        if(ticket > 0){
            ticket--;
            System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
        }
    }
}

静态同步方法

格式:
修饰符 static synchronized 返回值类型 方法名(参数){
    方法体
    return 结果
}

默认锁:class对象

线程生命周期

线程状态介绍

  1. 当线程被创建并且启动之后,它并非一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态?在API的java.lang.Thread.State这个枚举中给出了六种线程状态:
线程状态导致状态发生条件
NEW线程刚被创建,但并未启动,并未调用start方法
Runnable线程可以在JVM中运行的状态,可能正在运行自己的代码
Blocked当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当线程持有锁时,该线程将变成Runnable状态
Waiting一个线程在等待另一个线程执行唤醒动作,该线程进入Waiting状态,进入这个状态之后不可以自动唤醒,必须等待另一个线程调用notify或者notifyALL方法才能够唤醒。
TimedWaiting同Waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep、Object.wait。
Terminated因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop()。

线程状态图

img

等待唤醒机制

  1. 要求:一个线程生产,另一个线程消费,不能连续消费 -> 等待唤醒机制(生产者,消费者)(线程之间的通信)
方法说明
void wait()线程等待,等待过程中会释放锁,需要被其他线程调用notify方法将其唤醒,唤醒之后重新抢锁执行。
void notify()线程唤醒,一次唤醒一个等待线程;如果有多条线程等待,则随机唤醒一条等待线程。
void notifyAll()唤醒所有等待线程。

wait和notify方法需要锁对象调用,所以需要用到同步代码块中必须为同一个锁对象

等待唤醒案例如下:包子铺生产者消费者案例

package com.thread.waitornotifytest;

public class test {
    public static void main(String[] args) {
        BaoZiPu baoZiPu = new BaoZiPu();

        Product product = new Product(baoZiPu);
        Consumer consumer = new Consumer(baoZiPu);

        Thread t1 = new Thread(product);
        Thread t2 = new Thread(consumer);

        t1.start();
        t2.start();
    }
}
package com.thread.waitornotifytest;

public class Consumer implements Runnable{

    private BaoZiPu baoZiPu;

    public Consumer(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }

    @Override
    public void run() {
        while(true){

            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            synchronized (baoZiPu){
                if(baoZiPu.isFlag() == false){
                    try {
                        baoZiPu.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

                baoZiPu.getCount();
                baoZiPu.setFlag(false);

                baoZiPu.notify();
            }

        }
    }
}
package com.thread.waitornotifytest;

import java.awt.*;

public class Product implements Runnable{
    private BaoZiPu baoZiPu;

    public Product(BaoZiPu baoZiPu) {
        this.baoZiPu = baoZiPu;
    }
    @Override
    public void run() {

        while(true){

            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            synchronized (baoZiPu){
                if(baoZiPu.isFlag() == true){
                    try {
                        baoZiPu.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

                baoZiPu.setCount();
                baoZiPu.setFlag(false);

                baoZiPu.notify();
            }
        }
    }
}
package com.thread.waitornotifytest;

public class BaoZiPu {
    //代表包子的count
    private int count;
    private boolean flag;



    public BaoZiPu(boolean flag) {
        this.flag = flag;
    }

    public BaoZiPu(int count) {
        this.count = count;
    }

    public BaoZiPu() {

    }

    public void setCount() {
        count++;
        System.out.println("生产了...第" + count + "个包子");
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public void getCount() {
        System.out.println("消费了....第" + count + "个包子");
    }

    public boolean isFlag() {
        return flag;
    }

}

Lock锁

  1. Lock是一个接口
  2. 实现类:ReentrantLock
  3. 方法:
    1. lock() 获取锁
    2. unlock() 释放锁

synchronized:不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放锁对象。

Lock:通过两个方法控制需要被同步的代码,使用上更加灵活。

package com.thread.btest;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyTicket implements Runnable{
    int ticket = 100;

    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (ticket>0){
            try {
                Thread.sleep(10L);
                lock.lock();;
                if(ticket > 0){
                    ticket--;
                    System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票");
                }


            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }finally {
                lock.unlock();
            }
        }
    }
}
package com.thread.btest;

public class test {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();
        Thread t1 = new Thread(myTicket,"张三");
        Thread t2 = new Thread(myTicket,"李四");
        Thread t3 = new Thread(myTicket,"lzy");

        t1.start();
        t2.start();
        t3.start();
    }
}

Callable接口

Callable是一个接口,类似于Runnable

方法:

V call() -> 设置线程任务,类似于run方法

call方法和run方法的区别:

  1. 相同点:都用于设置线程任务
  2. 不同点:call方法有返回值,而且有异常可以throws,run没有返回值,也不可以throws
  3. 1. 叫做泛型 2. 泛型用于指定我们操作什么类型的数据,<>中只能写引用数据类型,如果反省不写,默认是Object类型数据。 3. 实现Callable接口时,执行泛型是什么类型的,重写的call方法,返回值就是什么类型。
  4. 获取call方法返回值:FutureTask
    1. FutureTask 实现了一个接口:Future
      1. FutureTask 中有一个方法:V get() -> 获取call方法的返回值

img

使用示例:

package com.thread.CallableTest;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<String> stringFutureTask = new FutureTask<>(myCallable);

        //创建Thread类对象 -> Thread(Runnable target)
        Thread t1 = new Thread(stringFutureTask);
        t1.start();

        System.out.println(stringFutureTask.get());
    }
}
package com.thread.CallableTest;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "柳真阳学长别压力我了";
    }
}

线程池

问题:之前来一个线程任务,就需要创建一个线程对象去执行,用完还要销毁线程对象,如果线程任务多了,就需要频繁创建线程对象和销毁线程对象

线程池操作流程示例:

  1. 创建线程池对象,指定池子中最多能有多少条线程对象 -> 2个
  2. 来了第一个线程任务,看池子中有无线程对象,如果没有,则创建线程对象,给线程任务用,用完还回去
  3. 来了第二个线程任务,如果有直接用,用完还回去,如果没有则创建线程对象,给线程任务用,用完还回去。
  4. 来了第三个线程任务,如果池子中有空闲线程,直接用,用完还回去,如果没有,则等待之前线程任务执行完毕,归还线程对象再使用,用完还回去。

线程池的使用

  1. 如何创建线程池对象:用工具类 -> Executors

  2. 获取线程池对象:Executors中的静态方法:

   static ExecutorService newFixedThreadPool(int nThreads)
  1. 参数:指定线程池中最多创建的线程对象条数
  2. 返回值ExecutorService 是线程池,用于管理线程对象

执行线程任务:ExecutorService中的方法

  Future<?> submit(Runnable task) 提交一个Runnable任务
  Future<T> submit(Callable<T> task) 提交一个Callable任务用于执行
  1. submit方法的返回值:Future接口。

    1. 用于接收run方法或者call方法返回值的,但是run方法没有返回值,所以可以不用Future接收,执行call方法需要用Future接收。
    2. Future中有一个方法:V get() 用于获取call方法返回值或者run方法返回值
  2. ExecutorService中的方法:

   void shutdown() 启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务

定时器Timer

  1. 概述:定时器
  2. 构造:Timer()
方法:
void schedule(TimerTask task,Date firstTime,long period)
    task: 抽象类,是Runnable的实现类
    firstTime:从什么时间开始执行
    period:每隔多长时间执行一次,设置的是毫秒值 
package com.thread.Timer;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class Demo01Timer {
    public static void main(String[] args){
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("haha");
            }
        },new Date(),1000L);
    }
}

  • 22
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值