zoukankan      html  css  js  c++  java
  • Java进阶——线程与多线程

    线程和多线程

    概念

    • 程序

      程序是一段静态代码。
    • 进程

      进程是程序的一次动态执行过程(从代码加载、执行、执行完毕的完整过程)。进程是资源分配的最小单位。
    • 线程

      线程是CPU调度的最小执行单位。程序执行过程中可以产生多个线程。

    进程和线程的区别

    1. 进程:一个应用程序对应一个进程;进程是资源分配的最小单位;通过多线程占据系统资源;进程之间数据状态完全独立。
    2. 线程:一个进程可以有多个线程;线程是执行程序的最小单元;线程是占用CPU的基本单位;线程之间共享一块内存空间。

    线程的生命周期

    • 新建状态

      线程对象创建,还未调用start()方法。
    • 就绪状态

      调用start()方法,但调度程序还未将其选为可运行线程。
    • 运行状态

      线程调度程序从可运行池中选择一个线程作为当前线程。
    • 阻塞状态

      • 等待状态
      • 阻塞状态
      • 睡眠状态
        线程是活的,但没有条件运行;当某事件发生后,可返回到就绪状态。
    • 死亡状态

      线程run()方法完成。线程一旦死亡,不可复生(调用start()会抛异常)。

    Java的线程

    主线程

    每个Java程序都有一个默认的主线程。

    当JVM加载代码,发现main方法后,会立即启动一个线程(主线程)
    主线程特点

    1. 产生其他子线程的线程
    2. 不一定是最后完成执行的线程

    创建线程

    1. 继承Thread类
      • 重写run()方法
      • new一个线程对象
      • 调用对象的start()方法启动线程
    2. 实现Runnable接口
      • 实现run()方法
      • 创建一个Runnable类的对象
      • 创建Thread类对象,将Runnable对象作为参数
      • 调用Thread对象的start()方法启动线程
    • 一个线程只能被启动一次run()方法执行结束后,线程结束。
    • 一个程序多个线程,线程只能保证开始时间,结束时间和执行顺序无法确定。
    • 线程调度采用队列形式;JVM线程调度程序决定执行就绪状态的某个线程。
    • 运行的线程有名字
      • 可通过JVM默认线程名字
      • 自定义线程名字setName()
    //给子线程命名
    //默认为Thread-
    Thread1 t1=new Thread1();
    Thread t=new Thread(t1);
    t.start();
    t.setName("t1");
    
    //获取主线程,并命名
    Thread.currentThread().setName("mm");
    System.out.println(Thread.currentThread().getName());
    

    创建线程方法对比

    1. 继承Thread类
      1. 编写简单,访问当前线程this
      2. java是单继承机制,不能继承其他父类;没有达到资源共享
    2. 实现Runnable接口
      1. 编程复杂,访问当前线程Thread.currentThread()
      2. java是多接口实现,可继承其他类;可多个线程共享同一个目标对象。
    //Runnable
    //多线程解决同一问题
    Thread1 t1 = new Thread1();
    new Thread(t1).start();
    new Thread(t1).start();
    new Thread(t1).start();
    
    //Thread
    //资源不共享
    Thread t1 = new Thread1();
    Thread t2 = new Thread2();
    Thread t3 = new Thread3();
    t1.start();
    t2.start();
    t3.start();
    

    线程的方法

    方法名 功能
    start() 启动线程,让线程从新建状态进入就绪状态队列
    run() 普通方法,线程对象被调度后执行的操作
    sleep() 暂停线程的执行,休眠线程
    yield() 暂停正在执行的线程,让同等优先级的线程运行
    join() 暂停当前线程的执行,等调用该方法的线程执行完后,线程返回就绪状态
    interrupt() 唤醒休眠的线程
    stop() 终止线程
    isAlive() 测试线程的状态;新建/死亡状态=false
    currentThread() 返回当前正在执行线程对象的引用
    //输出 (新建状态、死亡状态为false)
    //false  false
    //join方法将主线程暂停,先执行Thread1线程
    Thread1 t1=new Thread1();
    Thread t=new Thread(t1);
    System.out.print(t.isAlive());
    t.start();
    t.join();
    System.out.print(t.isAlive());
    

    设置线程优先级

    • 通过Thread的setPriority()方法设置线程优先级
      • Thread.MIN_PRIORITY ——1
      • Thread.NORM_PRIORITY ——5
      • Thread.MAX_PRIORUTY ——10
    • 通过thread的getPriority()方法得到线程优先级
    • 线程默认优先级为创建其的运行状态线程的优先级

    线程让步

    当线程池中的线程具有相同优先级

    1. 选择一个线程运行,知道线程阻塞或运行结束
    2. 时间分片,为线程池中每个线程提供均等运行机会

    多线程运行时,JVM按优先级调度,级别相同的由操作系统按时间片分配。

    阻止线程执行

    线程睡眠 sleep()

    当线程睡眠时,暂停执行;苏醒前不会回到就绪状态;
    当睡眠时间到期,线程回到就绪状态。

    • 线程睡眠可帮助其他线程获得运行机会的最好方法
    • 线程苏醒后,返回到就绪状态
    • sleep()指定时间为最短睡眠时间
    • sleep()为静态方法,只能控制当前运行的线程

    线程等待 yield()

    线程让步,暂停当前正在执行的线程对象,并执行同等优先级的其他线程
    yield()使线程从运行状态——>就绪状态;让步的线程也有可能被线程调度程序选中。

    线程阻塞 join()

    线程A中调用线程B的join()方法,让线程A置于线程B的尾部。
    在线程B执行完毕之前,线程A一直处于阻塞状态,只有当B线程执行完毕时,A线程才能继续执行

    当join(100)带有参数时,如果A线程中掉用B线程的join(100),则表示A线程会等待B线程执行100毫秒,100毫秒过后,A、B线程并行执行;同时join(0)==join()

    join方法必须在线程start方法调用之后调用才有意义

    在主线程中执行程序:创建A、B两个子线程,首先调用线程A的start()方法执行线程A;
    调用线程A的join()方法,使主线程进入阻塞状态,只有当线程A执行完毕后,才能执行主线程
    线程A执行完毕后,主线程才可执行,调用线程B的start()方法执行线程B。

    Thread a = new ThreadA();
    Thread b = new ThreadB();
    //线程A开始执行
    a.start();
    //线程A调用join()
    a.join();
    //线程B开始执行
    b.start();
    

    多线程

    对象互斥锁

    Java每个对象都对应一个互斥锁的标记。
    每个对象只有一个锁(lock)与之相关联.
    synchronized关键字与对象互斥锁联合使用,保证对象在任意时刻只能由一个线程访问。
    避免多个线程进行访问导致数据不同步的问题。

    • 修饰代码块,该代码块在任意时刻只能由一个线程访问
      • 作用范围:代码块{}的内容
      • 作用对象:调用该代码块的对象
    //实现Runnable接口
    public class Thread1 implements Runnable {
    
    	private static int count;
    
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    		// 1. 修饰代码块
    		// 同步语句块
    		synchronized (this) {
    			for (int i = 0; i < 5; i++) {
    				count++;
    				System.out.println(Thread.currentThread().getName() + ":" + count);
    			}
    		}
    
    	}
    
    }
    // 同一个对象
    //实现资源共享、资源同步
    Thread1 thread = new Thread1();
    new Thread(thread).start();
    new Thread(thread).start();
    
    // 不同对象
    //实现资源同步、多线程进行处理
    Thread1 thread = new Thread1();
    Thread1 thread1 = new Thread1();
    new Thread(thread).start();
    new Thread(thread1).start();
    
    • 修饰方法,表示该方法在任意时刻只能由一个线程访问
      • 作用范围:方法内容
      • 作用对象:调用方法的对象
      • 关键字synchronized不可继承
      • 定义接口不可使用关键字synchronized修饰
      • 构造方法不可使用关键字synchronized修饰,但可以用同步代码块
    public synchronized void print(){
          //todo
    }
    
    • 修饰静态方法,
      • 作用范围:静态方法内容
      • 作用对象:这个类的所有对象
    public synchronized static void print(){
          //todo
    }
    
    • 修饰类,表示该类的所有对象公用一把锁
      • 作用范围:{}包括的所有内容
      • 作用对象:这个类的所有对象
    class ClassTest{
          public void method(){
                synchronized(ClassTest.class){
                      //todo
                }
          }
    }
    

    多线程同步

    为了更好的解决多个交互线程之间的运行进度。
    引入wait()方法与notify()方法
    wait()方法:使当前线程进行等待状态
    notify()方法:通知那些等待该对象锁的其他线程,使其重新获取该对象的对象锁。

    • wait()notify()方法必须配合synchronized关键字使用
    • wait()会释放锁,notify()不会释放锁
    • wait()方法执行后,执行interrupt()方法会报异常

    死锁

    死锁:当两个或两个以上的线程在执行过程中时,因争夺资源造成互相等待,若无外力作用,线程都无法推进下去的现象。
    必要条件

    1. 互斥条件
      • 线程对分配到的资源进行排他性使用;其他线程不可使用。
    2. 请求、保持条件
      • 线程保持至少一个资源,但又提出新的资源请求。
    3. 不可剥夺条件
      • 线程获得的资源在使用完之前,不可被剥夺;只能在使用完后自主释放。
    4. 环路等待条件
      • 发生死锁时,必然存在线程请求资源、资源被另一线程占用的环。

    线程池

    引入

    为什么有线程池

    通过在主线程中new一个Thread线程

    1. 新建线程对象性能差
    2. 线程缺乏统一管理,造成多线程之间的死锁、同步、资源协调问题
    3. 缺乏定期执行等功能

    线程池优点

    1. 重用线程、减少线程的创建、死亡开销。
    2. 可控制最大并发线程数,提高系统资源利用率
    3. 提供定期执行等功能

    线程池

    • newCachedThreadPool

    可缓存线程池。
    当请求线程数大于线程池长度

    1. 回收空闲线程
    2. 没有空闲线程可回收时,创建新线程
    //可缓存的线程
    ExecutorService pool = Executors.newCachedThreadPool();
    MyThread thread = new MyThread();
    //线程池执行子线程
    pool.execute(thread);
    pool.execute(thread);
    pool.execute(thread);
    pool.execute(thread);
    //此时线程池不会立刻退出,直到添加到线程池中的任务都已经处理完成,才会退出。
    pool.shutdown();
    
    pool-1-thread-1
    pool-1-thread-2
    pool-1-thread-1
    pool-1-thread-3
    
    • newFixdThreadPool

    定长线程池
    可控制线程最大并发数,超出的线程会在队列等待
    创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

    //定长线程池
    ExecutorService pool = Executors.newFixedThreadPool(2);
    MyThread thread = new MyThread();
    //线程池执行子线程
    pool.execute(thread);
    pool.execute(thread);
    pool.execute(thread);
    pool.execute(thread);
    pool.execute(thread);
    //关闭线程池
    pool.shutdown();
    
    pool-1-thread-1
    pool-1-thread-2
    pool-1-thread-1
    pool-1-thread-2
    pool-1-thread-1
    
    • newScheduledThreadPool

    延迟连接池
    支持定时、周期性任务执行

    //创建延时线程池-定长
    ScheduledThreadPoolExecutor	pool = new ScheduledThreadPoolExecutor(3);
    //创建子线程
    MyThread thread = new MyThread();
    //线程延迟执行
    /* 
     * scheduleAtFixedRate(Runnable,long,long,TimeUnit);
     *  1.Runnable:子线程
     *  2.long:该线程池延迟毫秒
     *  3.long:两次执行最短间隔
     *  4.TimeUnit:计时单位
     *  */
    pool.scheduleAtFixedRate(thread, 5000, 1000, TimeUnit.MILLISECONDS);
    
    • newSingleThreadExecutor

    单线程化线程池
    创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

    ExecutorService pool = Executors.newSingleThreadExecutor();
    MyThread thread = new MyThread();
    pool.execute(thread);
    pool.execute(thread);
    pool.execute(thread);
    pool.execute(thread);
    pool.shutdown();
    
    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-1
    pool-1-thread-1
    

    在此感谢以下博主

    java 线程方法join的简单总结
    Java中Runnable和Thread的区别
    Java中Synchronized的用法
    四种Java线程池用法解析

  • 相关阅读:
    【Winform】Webservice调用服务器端EXE
    LeetCode: Word Ladder II
    LeetCode: Maximal Rectangle
    Algorithm: 拓扑排序
    Algorithm: 匈牙利算法
    LeetCode: Scramble String
    LeetCode: Integer to Roman
    LeetCode: Roman to Integer
    算法导论:基础知识。
    C++主函数main()讲解
  • 原文地址:https://www.cnblogs.com/occlive/p/thread.html
Copyright © 2011-2022 走看看