zoukankan      html  css  js  c++  java
  • 多线程专题

     JVM内存解析

    线程:虚拟机栈 和 程序计数器 每个线程各自有一套(独立)

    进程:堆 和 方法区 是一个进程一份( 共享方法区和堆 )

           一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以

    访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资 源可能就会带来安全的隐患。

     一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()

    垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

    并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

    并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

    使用多线程的优点

    背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,(多花费线程切换时间)

    为何仍需多线程呢?

    多线程程序的优点:

    1. 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

    2. 提高计算机系统CPU的利用率

    3. 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和 修改

    何时需要多线程

    程序需要同时执行两个或多个任务。

    程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。

    需要一些后台运行的程序时

    /*
    * 多线程的创建,

    方式一:继承于Thread类

    * 1. 创建一个继承于Thread类的子类
    * 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
    * 3. 创建Thread类的子类的对象
    * 4. 通过此对象调用start()
    //问题一:我们不能通过直接调用run()的方式启动线程。
    //问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateExcep
    //创建Thread类的匿名子类的方式
    new Thread(){
    @Override
    public void run() {
    for (int i = 0; i < 100; i++) {
    if(i % 2 == 0){
    System.out.println(Thread.currentThread().getName() + ":" + i);

    }
    }
    }
    }.start();

    * 测试Thread中的常用方法:
    * 1. start():启动当前线程;调用当前线程的run()
    * 2. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    * 3. currentThread():静态方法,返回执行当前代码的线程
    * 4. getName():获取当前线程的名字
    * 5. setName():设置当前线程的名字 还可以通过构造器设置线程的名字
    * 6. yield():释放当前cpu的执行权 加逻辑加操作(可能仍然被释放的抢到)
    * 7. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才
    * 结束阻塞状态。
    * 8. stop():已过时。当执行此方法时,强制结束当前线程。
    * 9. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前
    * 线程是阻塞状态。sleep出现异常,只能用try解决 不能通过抛异常 因为父类run没有抛异常
    * 10. isAlive():判断当前线程是否存活 run方法执行完,就死亡
    *
    * 线程的优先级:
    * 1.
    * MAX_PRIORITY:10
    * MIN _PRIORITY:1
    * NORM_PRIORITY:5 -->默认优先级
    * 2.如何获取和设置当前线程的优先级:
    * getPriority():获取线程的优先级
    * setPriority(int p):设置线程的优先级

    start() 之前设置
    *   说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下
    * 被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。
    *
    
    
    /**
    * 创建多线程的方式二:实现Runnable接口
    * 1. 创建一个实现了Runnable接口的类
    * 2. 实现类去实现Runnable中的抽象方法:run()
    * 3. 创建实现类的对象
    * 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    * 5. 通过Thread类的对象调用start()
    *
    *
    * 比较创建线程的两种方式。
    * 开发中:优先选择:实现Runnable接口的方式
    * 原因:1. 实现的方式没有类的单继承性的局限性
    * 2. 实现的方式更适合来处理多个线程有共享数据的情况。
    *
    * 联系:public class Thread implements Runnable
    * 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
    *
     
    //1. 创建一个实现了Runnable接口的类
    //2. 实现类去实现Runnable中的抽象方法:run()
    //3. 创建实现类的对象
    //4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    //5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()


    Thread.currentThread().getName()(继承)与getName()(实现)

    实现时不同流程只需要传入同一个对象(对象内的数据共享)线程共享数据
    
    

    Java中的线程分为两类:一种是守护线程,一种是用户线程。

     它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。

     守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true)可以把一个用户线程变成一个守护线程。

     Java垃圾回收就是一个典型的守护线程。

     若JVM中都是守护线程,当前JVM将退出。

     形象理解:兔死狗烹,鸟尽弓藏

    /**
    * 例子:创建三个窗口卖票,总票数为100张.使用实现Runnable接口的方式
    *
    * 1.问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
    * 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
    * 3.如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他
    * 线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
    *
    *
    * 4.在Java中,我们通过同步机制,来解决线程的安全问题。
    *
    * 方式一:同步代码块
    *
    * synchronized(同步监视器){
    * //需要被同步的代码
    *
    * }
    * 说明:1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
    * 2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
    * 3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
    * 要求:多个线程必须要共用同一把锁。
    *
    * 补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。
    * 方式二:同步方法。
    * 如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
    *
    *
    * 5.同步的方式,解决了线程的安全问题。---好处
    * 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 ---局限性
    *

    /**
    * 使用同步代码块解决继承Thread类的方式的线程安全问题
    *
    * 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式
       加同步代码块,同步监视器保证唯一 加static

    *
    * 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。(类只会加载一次)


    有同步问题:有共享数据 共享数据是谁 操作共享数据的代码 同步监视器

    保证唯一 加static

    实现Runnable

    解决:

    方式一:同步代码块
    *
    *   synchronized(同步监视器){
    *     //需要被同步的代码
    *
    *   }

    在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

    方式二:同步方法。
    *
    *     如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。

    private synchronized void show(){//同步监视器:this
          //synchronized (this){}
          }

    继承Thread类的方式

    使用同步代码块解决
    synchronized (W.class){//Class clazz = Win.class,Win.class只会加载一次
    //错误的方式:this代表着t1,t2,t3三个对象
    // synchronized (this){}
    }

    使用同步方法处理
    private static synchronized void show(){//同步监视器:Window4.class
    //private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的}
    }

     /**

     * 使用同步机制将单例模式中的懒汉式改写为线程安全的
    *

       线程的死锁问题

    不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃 自己需要的同步资源,就形成了线程的死锁

    出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于 阻塞状态,无法继续

    解决方法 

    专门的算法、原则

    尽量减少同步资源的定义

    尽量避免嵌套同步

    /**
    * 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增
    *
    * 1. 面试题:synchronized 与 Lock的异同?
    * 相同:二者都可以解决线程安全问题
    * 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
    * Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
    *
    * 2.优先使用顺序:
    * Lock  同步代码块(已经进入了方法体,分配了相应资源)  同步方法(在方法体之外)
    *
    *
    * 面试题:如何解决线程安全问题?有几种方式


    保证锁lock唯一
    private ReentrantLock lock = new ReentrantLock(); implement
    private  static ReentrantLock lock = new ReentrantLock(); extrends


    /**
    * 线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印
    *
    * 涉及到的三个方法:
    * wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
    * notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
    * notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
    *
    * 说明:
    * 1.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
    * 2.wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
    * 否则,会出现IllegalMonitorStateException异常
    * 3.wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
    *
    * 面试题:sleep() 和 wait()的异同?
    * 1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
    * 2.不同点:1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
    * 2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
    * 3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
    *
    */

    经典例题:生产者/消费者问题

    生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。

    这里可能出现两个问题: 生产者比消费者快时,消费者会漏掉一些数据没有取到。 消费者比生产者快时,消费者会取相同的数据。

    /**
    * 线程通信的应用:经典例题:生产者/消费者问题
    *
    * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
    * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员
    * 会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品
    * 了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
    *
    * 分析:
    * 1. 是否是多线程问题?是,生产者线程,消费者线程
    * 2. 是否有共享数据?是,店员(或产品)
    * 3. 如何解决线程的安全问题?同步机制,有三种方法
    * 4. 是否涉及线程的通信?是
    *
    */
    /**
    * 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增
    *
    *
    * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
    * 1. call()可以有返回值的。
    * 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
    * 3. Callable是支持泛型的
    */

    新增方式二:使用线程池

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

     思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。

     好处:

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

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

     corePoolSize:核心池的大小

     maximumPoolSize:最大线程数

     keepAliveTime:线程没有任务时最多保持多长时间后会终止 ...

    /**
    * 创建线程的方式四:使用线程池
    *
    * 好处:
    * 1.提高响应速度(减少了创建新线程的时间)
    * 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    * 3.便于线程管理
    * corePoolSize:核心池的大小
    * maximumPoolSize:最大线程数
    * keepAliveTime:线程没有任务时最多保持多长时间后会终止
    *
    *
    * 面试题:创建多线程有几种方式?四种!
    */
    public class ThreadPool {

    public static void main(String[] args) {
    //1. 提供指定线程数量的线程池
    ExecutorService service = Executors.newFixedThreadPool(10);//此时service为接口对象 设置不了属性值 属性值如果声明在接口为常量
    ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//此时service为可设置线程池的属性的对象
    //设置线程池的属性
    // System.out.println(service.getClass());
    // service1.setCorePoolSize(15);
    // service1.setKeepAliveTime();


    //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
    service.execute(new NumberThread());//适合适用于Runnable
    service.execute(new NumberThread1());//适合适用于Runnable

    // service.submit(Callable callable);//适合使用于Callable
    //3.关闭连接池
    service.shutdown();
    }

    }





  • 相关阅读:
    Python3之random模块常用方法
    Go语言学习笔记(九)之数组
    Go语言学习笔记之简单的几个排序
    Go语言学习笔记(八)
    Python3之logging模块
    Go语言学习笔记(六)
    123. Best Time to Buy and Sell Stock III(js)
    122. Best Time to Buy and Sell Stock II(js)
    121. Best Time to Buy and Sell Stock(js)
    120. Triangle(js)
  • 原文地址:https://www.cnblogs.com/terrycode/p/12330871.html
Copyright © 2011-2022 走看看