多线程

  • 多线程的创建
  • Thread类的方法
  • 线程安全、线程同步
  • 线程通信、线程池
  • 定时器、线程状态..

Thread类

  • java是通过java.lang.Thread类来代表线程的
  • 按照面向对象的思想,Thread类应该提供了实现多线程的方式

创建线程

方式一

1.定义一个线程类:extends Thread

public class MyThread extends Thread{
//2.重写run方法
@Override
public void run(){
    for(int i=0;i<5;i++){
        System.out.print(i);
    }
} 
}

public clss ThreadDemo1{
    main(){
        //3.new 一个新线程
        Tread t =new MyThread();
        //4.调用start方法
        t.start();
            for(int i=0;i<5;i++){
        System.out.print(i);
    }
    }
}

优缺点:

1.编码简单

2.线程类继承了Thread类不利于扩展

为什么用start()不用run()?

直接run()是普通方法执行,依然是一个主线程

不要把主线程的任务放到执行之前,还是单线程

方式二

  • 创建一个线程任务实现接口Runnable接口,重写run()方法

  • 创建线程类对象

  • 把对象任务交给Tread处理

    public MyThread implements Runnable{
    @Override
    public void run(){
    for(int i=0;i<5;i++){
    System.out.print(i);
    }
    }
    }

    public class a{
    main(){
    //创建一个任务对象
    Runnable a = new MyThread();
    //任务对象交给Thread处理
    Thread t = new Thread(a);
    t.start();
    for(int i=0;i<5;i++){
    System.out.print(i);
    }
    }
    }

Thread的构造器:

  • public Thread(String name) 可以为当线程指定名称
  • public Thread(Runnable target)分装Runnable对象为线程对象
  • public Thread(Runnable target,String name)

优缺点:

  • 实现接口,扩展性强
  • 多一层封装,如果线程有执行结果是不可以直接返回的 run()没有返回值

匿名内部类:

public class a{
    main(){
        //创建一个任务对象
        Runnable a = new Runable(){
            @Override
    public void run(){
            for(int i=0;i<5;i++){
        System.out.print(i);
    }
        };
        //任务对象交给Thread处理
        Thread t = new Thread(a);
        t.start();
                    for(int i=0;i<5;i++){
        System.out.print(i);
    }
    }
}

方式三:JDK5.0 实现Callable接口和FutureTask

  • 得到任务对象

    • 实现Callable重写call(),
    • 用FutureTask把Callable对象分装成线程的任务对象
  • 把线程对象交给Thread

  • 调用Thread的strat()启动线程

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

    pubilc class A implements Callable{
    private int n;
    public A(n){
    this.n=n;
    }
    @Override
    public String Call() throw Exception{
    String sum=0;
    for(int i=0;i<n;i++){
    sum+=i;
    System.out.print(i);
    }
    return "结果是"+sum;
    }
    }

    public class B {
    main(){
    Callable c = new A(10);
    FutureTask f = new FutureTask<>(c);
    //FutureTask实现了Runable接口
    Thread t =new Thread(f);
    t.start();
    //没有结果会等待
    try{
    String s =f.get();
    }catch(Excetion e){
    e.printStackTrace();
    }
    }
    }

优缺点:

  • 实现接口扩展性强
  • 可以得到结果
  • 较为复杂

线程的常用方法

  • getName()获取线程名字

  • setName()修改线程名字

  • currentThread()获取线程的对象
    定义一个线程类:extends Thread

    public class MyThread extends Thread{
    //2.重写run方法
    @Override
    public void run(){
    for(int i=0;i<5;i++){
    System.out.print(i);
    }
    }
    }

    public clss ThreadDemo1{
    main(){
    //3.new 一个新线程
    Tread t =new MyThread();
    //4.调用start方法
    t.setName("1号线程")
    t.start();
    Thread m =Thread,currentThread();
    for(int i=0;i<5;i++){
    System.out.print(i);
    }

      }
    

    }

主线程的名称就是main

线程的休眠方法:技术优化--百度网盘

  • public static void sleep(long time) 让当前的线程休眠,单位毫秒

    main(){
    for(int i =0;i<5;i++){
    if(i==3){
    //技术优化点
    Thread.sleep(3000);
    }
    }
    }

线程安全问题

  • 存在多线程共享

  • 同时访问共享资源

  • 存在修改共享资源

    public class ThreadDemo{
    main(){
    Account a =new Account ("郝泾钊",10000);
    //小明小红
    new DrawThread(acc,"小明").start();
    new DrawThread(acc,"小红").start();
    }
    }

    @Date
    @All..
    @Null..
    public class Account{
    private String card;
    private double money;
    public void drawMoney (double money){
    //判断是谁来取钱
    String name= Thread.currentThread().getName();
    //判断是否够钱
    if(this.money>=money){
    //这样更容易不安全
    System.out.print(name+"来取钱成功,出"+money);
    this.money-=moey;
    }else{
    //余额不足
    System.out.print(name+"来取钱,余额不足");
    }
    }
    }

    public class DrawThread extends Thread{
    private Account acc;
    public DrawThread(Account acc,String name){
    super(name);
    this.acc=acc;
    }
    @Override
    public void run(){
    acc.drawMoney(1000);
    }
    }

线程同步

  • 加锁

    @Date
    @All..
    @Null..
    public class Account{
    private String card;
    private double money;
    public void drawMoney (double money){
    //判断是谁来取钱
    String name= Thread.currentThread().getName();
    synchronized("heima"){
    //判断是否够钱
    if(this.money>=money){
    //这样更容易不安全
    System.out.print(name+"来取钱成功,出"+money);
    this.money-=moey;
    }else{
    //余额不足
    System.out.print(name+"来取钱,余额不足");
    }
    }
    }
    }

    synchronized(){}:

  • 锁用随机对象好不好? 不好 同步代码块

建议使用共享资源作为锁对象

@Date
@All..
@Null..
public class Account{
    private String card;
    private double money;
    public void drawMoney (double money){
       	//判断是谁来取钱
       String name= Thread.currentThread().getName();
        synchronized(this){
                 //判断是否够钱
        if(this.money>=money){
            //这样更容易不安全
            System.out.print(name+"来取钱成功,出"+money);
            this.money-=moey;
        }else{
            //余额不足
            System.out.print(name+"来取钱,余额不足");
        }
        }
    }
}
  • 实例方法用this
  • 静态方法用类名.class

线程同步

方法上修饰synchronized

默认用this,但是代码要高度面向对象

@Date
@All..
@Null..
public class Account{
    private String card;
    private double money;
    public synchronized void drawMoney (double money){
       	//判断是谁来取钱
       String name= Thread.currentThread().getName();
                 //判断是否够钱
        if(this.money>=money){
            //这样更容易不安全
            System.out.print(name+"来取钱成功,出"+money);
            this.money-=moey;
        }else{
            //余额不足
            System.out.print(name+"来取钱,余额不足");
        }
    }
}
  • 同步代码块好还是同步方法好?
    • 同步代码块好,性能好
  • 同步方法原理了?
    • 也是用synchronized修饰默认是this

Lock锁

JDK5 加入的,更丰富功能,Lock接口不能实例化

  • public ReentrantLock() 获得Lock的对象

方法:

  • void lock() 获得锁

  • void unlock() 释放锁

    @Date
    @All..
    @Null..
    public class Account{
    private String card;
    private double money;
    private final Lock lock =new ReentrantLock();
    public void drawMoney (double money){
    //判断是谁来取钱
    String name= Thread.currentThread().getName();
    //判断是否够钱
    lock.lock();
    if(this.money>=money){
    //这样更容易不安全
    System.out.print(name+"来取钱成功,出"+money);
    this.money-=moey;
    }else{
    //余额不足
    System.out.print(name+"来取钱,余额不足");
    }
    lock.unlock();
    }
    }

线程通信

  • 线程之间相互发送数据
  • 共享的数据的情况决定自己做什么

常见模型:

  • 生产者与消费者模型:
  • 生产者线程产生数据,唤醒消费者;然后等待自己;消费者消费完数据之后唤醒生产者,然后等待自己。

方法:

Object类中:

  • void wait() 让当前线程等待并释放占用锁,直到另一个线程调用notify或notifyAll方法
  • void notify() 唤醒末个等待的线程
  • void notifyAll()唤醒正在等待的所有线程

上述方法要用同步锁对象来调用

例子:

@Date
@All..
@Null..
public class Account{
    private String card;
    private double money;
    public synchronized void drawMoney (double money){
       	//判断是谁来取钱
       String name= Thread.currentThread().getName();
                 //判断是否够钱
        if(this.money>=money){
            //这样更容易不安全
            System.out.print(name+"来取钱成功,出"+money);
            this.money-=moey;
            //没钱了,唤醒别的线程
            this.notifyAll();
            this.wait();
        }else{
           //当前等待,唤醒别的线程  先叫醒别人在打晕自己
            this.notifyAll();
            this.wait();
        }
    }
    public synchronized void deposit(double money){
       	//判断是谁来存钱
       String name= Thread.currentThread().getName();
                 //判断是否够钱
        if(this.money==0){
            //这样更容易不安全
            System.out.print(name+"来存钱成功,存钱"+money);
            this.money+=moey;
            //有钱了,唤醒别的线程
            this.notifyAll();
            this.wait();
        }else{
           //有钱不存钱
            this.notifyAll();
            this.wait();
        }
    }
}

main(){
    Account acc =new Account("132",0);
    //创建两个取钱线程
    new DrawThread(acc,"小明");
	new DrawThread(acc,"小红");
   	//创建两个存钱线程
     new SaveThread(acc,"亲爹");
     new SaveThread(acc,"亲爹");
    new SaveThread(acc,"亲爹");
        
    
}

取钱:

public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc,String name){
    super(name);
    this.acc=acc;
}
    @Override
    public void run(){
        while(true){
                 acc.drawMoney(1000);  
            try{
                      Thread.sleep(3000);   
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

存钱:

public class SaveThread extends Thread{
private Account acc;
public DrawThread(Account acc,String name){
    super(name);
    this.acc=acc;
}
    @Override
    public void run(){
        acc.deposit(1000);
         try{
                      Thread.sleep(3000);   
            }catch(Exception e){
                e.printStackTrace();
            }
    }
}

线程池(重点)

  • 创建线程的开销很大,----线程池解决

线程池的接口:ExecutorService

得到线程池对象

1.使用ExecutorService的实现类ThreadPoolExecytor自创建一个线程

ExecutorService------->ThreadPoolExecytor

2.使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象

public ThreadPoolExecutor(int corePoolSize,//核心线程数
                          int maximumPoolSize,//最大线程数
                          long keepAliveTime,//线程空闲时间
                          TimeUnit unit,//时间单位
                          BlockingQueue<Runnable> workQueue,//任务队列
                          ThreadFactory threadFactory,//线程工厂
                          RejectedExecutionHandler handler//拒绝策略) 
{
    ...
}
  • corePoolSize 核心线程数,默认为1。
    • 设置规则:
      CPU密集型(CPU密集型也叫计算密集型,指的是运算较多,cpu占用高,读/写I/O(硬盘/内存)较少):corePoolSize = CPU核数 + 1
      IO密集型(与cpu密集型相反,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。):corePoolSize = CPU核数 * 2
  • maximumPoolSize
    • 最大线程数,默认为Integer.MAX_VALUE 一般设置为和核心线程数一样
  • keepAliveTime
    • 线程空闲时间,默认为60s,一般设置为默认60s
  • unit
    • 时间单位,默认为秒
  • workQueue
    • 队列,当线程数目超过核心线程数时用于保存任务的队列。(BlockingQueue workQueue)此队列仅保存实现Runnable接口的任务。(因为线程池的底层BlockingQueue的泛型为Runnable)
      无界队列
      队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列作为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。阅读代码发现,Executors.newFixedThreadPool 采用就是 LinkedBlockingQueue,而博主踩到的就是这个坑,当QPS很高,发送数据很大,大量的任务被添加到这个无界LinkedBlockingQueue 中,导致cpu和内存飙升服务器挂掉。
      当然这种队列,maximumPoolSize 的值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
      有界队列
      当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。
      使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。
      同步移交队列
      如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列
  • threadFactory
    • 线程工厂,用来创建线程。
      为了统一在创建线程时设置一些参数,如是否守护线程,线程一些特性等,如优先级。通过这个TreadFactory创建出来的线程能保证有相同的特性。
      它是一个接口类,而且方法只有一个,就是创建一个线程。
      如果没有另外说明,则在同一个ThreadGroup 中一律使用Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的NORM_PRIORITY 优先级和非守护进程状态。
      通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。
      如果从newThread 返回 null 时ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务
  • handler
    • 拒绝策略,默认是AbortPolicy,会抛出异常。
      当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。
      当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
      AbortPolicy 丢弃任务,抛运行时异常。
      CallerRunsPolicy 由当前调用的任务线程执行任务。
      DiscardPolicy 忽视,什么都不会发生。
      DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。

核心线程--临时线程

临时线程什么时候创建?

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

什么时候会开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务会被拒绝。

线程池处理Runable、Callable任务

方法:

  • void execute(Runnable command) 执行任务、命令,没返回值--一般Runable任务
  • Future submit(Callable task)执行任务、命令,又返回值,--一般Callable任务
  • void shutdown()等任务执行完毕后关闭线程
  • List shutdownNow()立即关闭线程,停止执行的任务,并返回队列中未执行的任务

新任务的拒绝策略:

  • ThreadPoolExecutor.AborPolicy 丢弃任务并抛出RejectedExccutionException默认

  • ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常不推荐

  • ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务,然后把当前任务加入到队列中

  • ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的run()方法从绕过线程池执行

    public class TreadPoll{
    main(){
    //创建线程池对象
    ExcutorService pool =new ThreadPoolExcutor(3,5,6,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AborPolicy() )
    }
    //模拟线程处理
    Runnable target =new MyRunnable();
    pool.execute(target);
    pool.execute(target);
    pool.execute(target);
    //任务队列
    pool.execute(target);
    pool.execute(target);
    pool.execute(target);
    pool.execute(target);
    pool.execute(target);
    //创建临时线程
    pool.execute(target);
    pool.execute(target);
    //拒绝策略触发
    pool.execute(target);
    //关闭线程池(开发中一般不会使用)
    pool.shutdownNow();
    pool.shutdown();
    }

    public class MyRunnable implement Runnable{
    @Override
    public void run(){
    for(int i =0 ;i<5;i++){
    System.out.print(Thread.cuurentThread().getNmae()+"编写了hello world");
    }
    try{
    Thread.sleep(3000);
    }catch(Exception e){
    e.printStackTrace();
    }
    }
    }

处理Callable任务

public class TreadPoll{
    main(){
       //创建线程池对象
       ExcutorService pool =new ThreadPoolExcutor(3,5,6,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AborPolicy() )
    }
    Future<String> f1=pool.submit(new MyCallable(100));
    Future<String> f2=pool.submit(new MyCallable(100));
    Future<String> f3=pool.submit(new MyCallable(100));
    
    String rs = f1.get();
    String rs2 =f2.get();
    String rs3 = f3.get();

}

Executors工具类

  • public static ExecutorsService newCachedThreadPool() 线程池的数量随着任务的增加而增加,如果执行完毕空闲一段时间会被回收
  • public static ExecutorsService newFixedThreadPool(int nThreads)创建固定的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新的线程替代它
  • public static ExecutorsService newSingleThreadExecutor()创建一个线程池对象,如果线程出现异常而结束,那么线程池会补充一个新的线程
  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务

Executors底层也是实现 ThreadPoolExecutor实现的

main(){
    ExecutorService pool=Executors.ExecutorsService newFixedThreadPool(3);
}

但是大型的并发项目会出现系统分险

  • 内存溢出,线程的内存溢出
  • 任务没有限制

定时器

方式一:Timer

方法二:newScheduledThreadPool

Timer

构造器:

  • public Timer() 创建定时器对象

方法:

  • public void schedule(TimerTask task,long delay, long period) 开启一个定时器执行task任务

问题:

  • Timer是单线程,存在延时与定时器的时间有出入的情况

  • 可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续的任务执行

    main(){
    Timer timer =new Timer();
    timer.scedule(new TimerTask(){
    @Oberride
    public void run(){
    //业务
    System.out.print("定时器");
    }
    },3000,2000)
    }//3秒延时调用2秒周期调用

newScheduledThreadPool;

JDK1.5引入的并发包

Executors

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

方法:

  • public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initalDelay ,long period,TimeUnit unit) 周期调度方法

优点:

  • 基于线程池执行,某个任务情况不会影响其他的定时任务的执行

    main(){
    ScheduledExecutorService pool =Executor.newScheduledThreadPool(3);
    pool.scheduleAtFixedRate(new TimeTrask(){
    @Override
    public void run (){
    //业务
    }
    },0,2,TimeUnit.SECONDS);
    }//初始化延迟事件0秒 周期延迟2秒单位秒

并发和并行

多线程:并发和并行同时进行

生命周期

Thread类的枚举类的6中状态

  • new 新建状态
  • Runnable 可运行状态start()
    • Blocked锁阻塞状态
    • waiting无限等待状态wait()
    • Timed Waiting (计时等待)
      • sleep状态的线程好了不用强锁、、、我不要脸
      • wait状态的线程的时间到了,并得到锁,可以跑
      • wait状态时间没到,被唤醒,并得到锁可以跑
      • wait没有得到锁会进入锁阻塞
  • Teminated被终止状态
posted on 2022-06-23 20:57  Steam残酷  阅读(123)  评论(0)    收藏  举报