多线程和反射

1.什么是线程

        在了解线程之前,先了解一下进程。

进程:一段正在运行的程序。

线程:线程是进程中最小的调度的单元(单位),cpu控制的最小的执行单元。轻量级的进程。任何一个程序都至少有一个线程在用,多个线程共享内存。多线程切换消耗的资源少。

2.并发与并行

并发:在同一时间间隔内,同时有多个线程运行。(一个CPU调度多个线程)

并行:在同一时刻,同时有多个线程运行。(多个CPU调度多个线程)

3.线程和进程的关系?

线程是进程中最小的调度单元,线程必须存在于进程中。

进程是有自己的独立的内存空间。

多线程提高开发效率,多线程共享进程中的内存空间。

线程是由CPU进行控制调度,线程执行需要抢到CPU的时间片。

线程可以并发执行可以并行执行。

4.如何创建一个线程?

1.继承Thread的类,并且重写run方法,创建线程对象,调用.start()启动线程

public class MyThread1 extends Thread {
    
public static void main(String[] args) {
    MyThread1 myThread1 = new MyThread1();
    myThread1.start();
    System.out.println("主线程正在进行");
}
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {

            System.out.println(Thread.currentThread().getName() + "要唱歌" + i);
        }
    }
}

run()和start()有什么区别?

run()在启动线程是会被自动调用,如果使用对象.run()则只是单单的调用run方法,start()则是启动线程的入口。

2.实现Runable接口,重写run()方法

public class MyRun implements Runnable {

    public static void main(String[] args) {
     
        MyRun myRun = new MyRun();
        new Thread(myRun).start();

        System.out.println("主线程正在进行");
        myThread2.start();
    }
    @Override
    public void run() {

            System.out.println(Thread.currentThread().getName() + "正在思考" + i);
        }

    }
}

继承Thread和实现Runable接口有什么区别?

1、继承Thread:简单,受限于java单继承;

2、实现Runnable接口:继承类,实现多个接口,实现资源的共享,

3.实现Callable接口

这种方式可以获取线程的返回值。

public class MyCall1 implements Callable<String> {

    public static void main(String[] args) throws ExecutionException, InterruptedException{
        MyCall1 myCall1 = new MyCall1();
        FutureTask<String> futureTask = new FutureTask<>(myCall1);
        Thread thread = new Thread(futureTask);
        thread.start();
        String s = futureTask.get();
        System.out.println(s);
    }
    @Override
    public String call() throws Exception {
        return "今天星期三";
    }
}

总结

1、创建线程的三种方式:

  1. 继承Thread类

  2. 实现Runnable接口

  3. 实现Callable接口,配套使用FutureTask进行转换

2、什么时候用哪种方式?

  • 如果有返回结果:Callable

  • 如果没有返回结果:继承Thread类|实现Runnable接口

  • 继承只能单继承,多个线程共享数据使用Runnable接口

一些关于Thread类的API

方法描述
public static Thread currentThread()返回当前的线程
public final String getName()返回线程名称
public final void setPriority(int priority)设置线程优先级
public void start()开始执行线程
public static void sleep(long m)使目前的线程休眠**m毫秒**
public final void yield()暂停目前的线程,运行其他 线程
public void run()线程要执行的任务

sleep()

线程休眠,会让当前线程处于阻塞状态,指定时间过后,线程就绪状态

yield()

线程礼让,暂停当前正在执行的线程对象,并执行其他线程

yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。

yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。

注意:在回到就绪之后,有可能还会再次抢到。

join()

join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个线程运行结束,当前线程再由阻塞转为就绪状态

interrupt:中断线程,仅仅发送了一个中断的信号,当碰到wait(),sleep方法时,清除中断标记,抛出异常。(了解)

setDaemon:设置线程为后台(守护)线程。

3、线程状态

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)

(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

数据安全问题

  • 条件1:多线程并发

  • 条件2:有共享数据

  • 条件3:共享数据有修改的行为

变量对线程安全的影响

实例变量:在堆中。

静态变量:在方法区。

局部变量:在栈中。

结论:

局部变量永远都不会存在线程安全问题。

实例变量在堆中,堆只有1个。 静态变量在方法区中,方法区只有1个。 堆和方法区都是多线程共享的,所以可能存在线程安全问题。

局部变量+常量:不会有线程安全问题。 成员变量:可能会有线程安全问题。

什么是线程同步?

线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作

那相对的线程异步处理就不用阻塞当前线程,而是允许后续操作,直至其他线程将处理完成,并回调此线程。

线程同步的利弊

  • 好处:解决了线程同步的数据安全问题

  • 弊端:当线程很多的时候,每个线程都会去判断同步上面的这个锁,很耗费资源,降低效率

基于Lock实现

ReentrantLock类实现了Lock,它拥有和synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁。

synchronized与Lock的对比

Lock是显示锁,需要自己手动开启和关闭,synchronized是隐式锁,出了作用于自动释放,无需自己释放。

Lock只能锁代码块,synchronized可以锁代码块和方法

使用Lock锁,JVM将花费更少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

如何解决线程安全问题

第一种方案:尽量使用局部变量代替“实例变量和静态变量”。

第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样 实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象, 对象不共享,就没有数据安全问题了。)

第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候 就只能选择synchronized了。线程同步机制。

死锁:

死锁:当多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,从而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况。

什么是“生产者和消费者模式”?

生产者消费者问题是多线程同步的一个经典问题。生产者消费者同时使用一块缓冲区,生产者生产商品放入缓冲区,消费者从缓冲区取出商品。我们需要保证的是,当缓冲区满时,生产者不可生产商品;当缓冲区为空时,消费者不可取出商品。

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

仓库类:

public class Store {
    //    库存数量 ,计划最多放入10个
    private  int count = 0;

    public synchronized    void push() {
            if (count == 10) {
                System.out.println(Thread.currentThread().getName()+"库存已满:" + count);
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
//            if(count<10){
                count++;
                System.out.println(Thread.currentThread().getName()+"正在生产:,现有库存" + count);
//            }
            this.notify();
   }

    public synchronized   void pop() {

            if (count == 0) {
                System.out.println(Thread.currentThread().getName()+"库存已空:" + count);
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return;
            }

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
//            if(count>0){
                count--;
                System.out.println(Thread.currentThread().getName()+"正在消费:,现有库存" + count);
//            }

            this.notify();
        }

}

生产者:Producer

public class Producer implements Runnable {
    private Store store;

    public Producer(Store store) {
        this.store = store;
    }

    @Override
    public void run() {
            for(int i=1;i<=50;i++){
                store.push();
            }
    }
}

消费者:Consumer

public class Consumer implements Runnable {
    private Store store;

    public Consumer(Store store) {
        this.store = store;
    }

    @Override
    public void run() {
            for(int i=1;i<=50;i++){
                store.pop();
            }
    }
}

生产者消费者模式的优点:

1、解耦: 由于有缓冲区的存在,生产者和消费者之间不直接依赖,耦合度降低。

2、支持并发: 由于生产者与消费者是两个独立的并发体,他们之间是用缓冲区作为桥梁连接,生产者只需要往缓冲区里丢数据,就可以继续生产下一个数据,而消费者只需要从缓冲区了拿数据即可,这样就不会因为彼此的处理速度而发生阻塞。

3、支持忙闲不均: 缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来 了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。 等生产者的制造速度慢下来,消费者再慢慢处理掉。

sleep方法和wait方法的区别?

1、sleep是Thread类种的方法,wait是Object中的方法。

2、wait必须用在同步代码中,释放锁,让当前线程处于等待状态;sleep不会解锁;

线程池

线程池就是首先创建一些线程,他们的集合称之为线程池。线程池在系统启动时会创建大量空闲线程,程序将一个任务传递给线程池,线程池就会启动一条线程来执行这个任务,执行结束后线程不会销毁(死亡),而是再次返回到线程池中成为空闲状态,等待执行下一个任务。

创建线程池

 public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           RejectedExecutionHandler handler) 

  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。

    • 设置规则: CPU密集型(CPU密集型也叫计算密集型,指的是运算较多,cpu占用高,读/写I/O(硬盘/内存)较少):corePoolSize = CPU核数 + 1 IO密集型(与cpu密集型相反,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。):corePoolSize = CPU核数 * 2

  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。默认为Integer.MAX_VALUE,一般设置为和核心线程数一样

  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。

    线程空闲时间,默认为60s,一般设置为默认60s

  • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

  • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。

  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。

  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略

    默认是AbortPolicy,丢弃任务并抛出 RejectedExecutionException 异常。

任务队列(workQueue)

任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在 Java 中需要实现 BlockingQueue 接口。但 Java 已经为我们提供了 7 种阻塞队列的实现:

关闭线程方法:shutdown和shutdownNow的区别

  • shutdown():仅仅是不再接受新的任务,以前的任务还会继续执行

  • shutdownNow():立刻关闭线程池,如果线程池中还有缓存的任务没有执行,则取消执行,并返回这些任务

通过Executor工厂类中的静态方法获取线程池对象

1、通过newCachedThreadPool获取线程池对象

该方式特点是:创建一个默认的线程池对象,里面的线程可重用,且在第一次使用时才创建

2、通过newFixedThreadPool获取线程池对象

该方式特点是:可指定创建线程数,并且可以重复用

3、通过newSingleThreadExecutor获取线程池对象

该方式特点是:只会创建一个线程

三种创建线程池的区别

第一种:newCachedThreadPool:线程的数据是不做限制的,每次有任务来的时候都会以任务优先,性能最大化(也就是服务器压力比较大)

第二种:newFixedThreadPool:可以让压力不那么大,并且可以规定线程的数量,当线程的数量达到指定数量的时候,这个时候就不会再有新的线程了

第三种:newSingleThreadExecutor:绝对的安全,不考虑性能,因为是单线程,永远只有一个线程来执行任务。

Executors 的 功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值