栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java多线程笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java多线程笔记

一. 线程和进程
  1. 什么是进程?

    • 正在运行的程序,是系统进行资源分配的基本单位。
    • 目前的操作系统都是支持多进程的,可以同时执行多个进程,通过进程ID(PID)区分。
  2. 什么是线程?

    • 线程又称轻量级进程。
    • 线程是进程中的一条执行路径,也是CPU的基本调度单位。
    • 一个进程由一个或多个线程组成,彼此间完成不同的工作。
    • 同时执行,又称为多线程。
    • Java虚拟机是一个进程,当中默认包含主线程(main),可以通过代码创建多个独立线程,与main并发执行。
  3. 进程和线程的区别

    • 进程是操作系统资源分配的基本单位,而线程是CPU的基本调度单位。
    • 一个程序运行后至少有一个进程。
    • 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的。
    • 进程间不能共享数据段地址,但同进程间的线程之间可以。
  4. 线程的组成

  • 任何一个线程都具有基本的组成部分:
    • CPU时间片:操作系统(OS)操作系统会为每个线程分配执行时间。
    • 运行数据:
      • 堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象。
      • 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈。
    • 线程的逻辑代码。
  1. 线程的特点
    • 线程抢占式执行
      • 效率高
      • 可防止单一线程长时间独占CPU
    • 在单核CPU中,宏观上同时执行,微观上顺序执行
二.创建线程的三种方式 2.1 继承Thread类,重写run方法

  • main方法是主线程,用来执行main方法;main方法里面创建的线程叫做子线程

  • package com.stu.szh.duoxiancheng;
    
    public class MyThread01 {
        public static void main(String[] args) {
            // 1.创建线程对象
            SonThread sonThread = new SonThread();
            // 2.启子动线程
            // 注意:不能用run()
            sonThread.start();
    
            // main方法,主线程
            for (int i=0; i<=50; i++){
                System.out.println("这是主线程:-----------------"+i);
            }
    
        }
    
        static class SonThread extends Thread {
            @Override
            public void run() {
                super.run();
                for (int i=0; i<=50; i++){
                    System.out.println("这是子线程:.........."+i);
                }
            }
        }
    }
    
    
  • 获取线程ID和线程名称

    1. 在 Thread的子类中调用this.getId()或this.getName(),得到线程id和名称。
      • 缺点:只能在继承Thread类的子类中使用,因为他们是Thread类中的方法。
    2. 使用Thread.currentThread().get()和Thread.currentThread().getName。
  • 修改线程名称

    1. 调用线程对象的setName方法。

      • sonThread.setName("子线程");
        
    2. 使用线程子类的构造方法赋值。

      • public SonThread(String name){
            super(name);
        }
        
        SonThread sonThread = new SonThread("子线程");
        
    3. 只能修改线程的名称,不能修改线程的ID(ID由系统生成)。

    4. 修改线程名称必须在,启动线程之前修改(sonThread.start()之前修改)。

2.2 实现Runnable接口

2.3 实现 Callable接口 2.4 为什么常用实现Runnable接口的方法创建线程
  • 相比继承Thread,实现Runnable有一下优势
    • 适合多个相同的程序代码的线程去共享同一个资源,如果一个类继承Thread,则不适合资源共享;但如果实现了Runnable接口的话,则很容易的实现资源的共享。
    • 可以避免java中单继承的局限性,java不支持多继承,但允许调用多个接口。
    • 增强程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立,而继承方式中线程和任务是耦合的。
    • 线程池只能放入实现Runnable接口或Callable接口的类线程,不能直接放入Thread类。
三.线程的基本状态

3.1 线程休眠
  • 休眠:

    • public static void sleep(long millis) // 当前线程主动休眠 millis 毫秒,1000 millis = 1 s。

    • Thread.sleep(1000);  // 休眠1秒。注意要try-catch异常,不能抛出异常。因为子类不能抛出比父类有更宽的异常
      
3.2 放弃
  • public static void yield () // 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片(有可能抢到也有可能抢不到)。

  • Thread.yield();  // 主动放弃CPU
    
3.3 加入
  • pubilc final void join(); // 允许其他线程加入到当前线程中,会把当前线程阻塞,知道加入的线程执行完毕,当前线程才会继续 执行

  • join()要在启动(start())之后加入。

    j1.join(); // (入过join是在main块中写的话)加入到当前线程(main()),并阻塞当前线程,直到加入线程执行完毕。
    		   //  jion()不是静态方法(static),不能直接用类名调用。
    
3.4 设置线程优先级
  • 优先级:

    • 线程对象setPriority();
    • 线程优先级为1-10,默认为5,优先级越高(值越大,优先级越高,10的优先级最高),表示获取CPU机会越高。
  • p1.setPriority(1);
    
  • 守护线程:

    • 线程对象setDaemon(true); 设置为守护线程
    • 线程有两类:用户线程(前台线程),守护线程(后台线程)
    • 如果线程中所有前台线程都执行完毕了,后台线程会自动结束
    • 垃圾回收器线程属于守护线程
  • d1.stDaemon(true);  // 将d1设置为守护线程,注意要在start()之前设置。
    
3.5 线程的状态(等待)

四. 线程的安全问题
  • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
  • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可以保证其正确性。
  • 原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可以打乱或缺省。
4.1 同步代码块
synchronized(临界资源对象){ // 对临界资源对象加锁
    // 代码(原子操作)
}
  • 当一个线程执行该代码块时,其他线程不能执行该代码块(加锁了)。

  • 每个对象都有一个互斥锁标记,用来分配给线程。

  • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。

  • package com.stu.szh.duoxiancheng;
    
    
    public class TestBankCard02 {
        public static void main(String[] args) {
    
            // 1.创建银行卡
            BankCard02 card = new BankCard02();
    
            // 使用匿名内部类的方法来实现存钱
            Runnable add = new Runnable() {
                @Override
                public void run() {
                    // 加锁
                    synchronized (card) {
                        for (int i = 0; i < 10; i++) {
                            card.setMoney(card.getMoney() + 1000);
                            System.out.println(Thread.currentThread().getName() + "存了1000,余额是:" + card.getMoney());
                        }
                    }
                }
            };
    
            // 使用匿名内部类的方式来取钱
            Runnable sub = new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        synchronized (card) {
                            if (card.getMoney() >= 1000) {
                                card.setMoney(card.getMoney() - 1000);
                                System.out.println(Thread.currentThread().getName() + "取了1000,余额是:" + card.getMoney());
                            } else {
                                System.out.println("余额不足,请及时充钱!");
                                i--;
                            }
                        }
                    }
                }
            };
            // 创建两个线程对象
            Thread xiaoli = new Thread(add,"小李");
            Thread xiaoming = new Thread(sub,"小明");
    
            // 启动线程
            xiaoli.start();
            xiaoming.start();
        }
    }
    
    
    
    
    
    package com.stu.szh.duoxiancheng;
    
    
    public class BankCard02 {
        private double money;
    
        public double getMoney() {
            return money;
        }
    
        public void setMoney(double money) {
            this.money = money;
        }
    }
    
    
4.2 线程的状态(阻塞)

4.3 同步方法
  • synchronized 返回值类型 方法名称 (形参列表){ // 对当前对象(this)加锁
        // 代码(原子操作)
    }
    
    // 同步方法
    public synchronized boolean sale(){}
    
  • 只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。

  • 线程退出同步方法时,会释放相应的互斥锁标记。

4.4 同步规则
  • 注意:
    • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的标记锁。
    • 如果调用不包含同步代码块的方法,或普通方法时,则不需要所标记,可直接调用。
  • 已知JDK中线程安全的类:
    • StringBuffer
    • Vector
    • Hashtable
    • 以上类中的公开方法,均为synchronize修饰的同步方法。
五. 死锁
  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象所标记时,就产生了死锁。
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已拥有的锁标记,由此可能会造成死锁。
六. 线程通信
  • 等待:

    • public final void wait()
      
    • public final void wait(long timeout)
      
    • 必须在obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在o的等待队列中。释放锁进入等待队列。

  • 通知: 即唤醒等待中的线程。(等待线程进入等待队列后,如果没有别的线程来通知它,就不会醒,所以需要以下方法)

    • public final void notify()
      
    • public final void notifyAll()
      
七. 线程池
  • 问题:
    • 线程是宝贵的内存资源,单个线程约占1MB空间,过多分配,易造成内存溢出。
    • 频繁的创建及销毁线程会增加虚拟机的回收频率,资源开销,造成程序性能下降。
  • 线程池:
    • 线程容量,可设定线程分配的数量上限。
    • 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
    • 避免频繁的创建和销毁线程。
7.1 线程池的原理
  • 将任务提交给线程池,由线程池分配线程,运行任务,并在当前任务结束后复用线程
7.2 创建线程池
  • 常用的线程池接口和类(所在包java.util.concurrent):

    • Executor:线程池的顶级接口,所有的线程池都要实现该接口。

    • public interface Executor {
          void executor(Runnable command);
      }
      
    • ExecutorService: 线程池接口,包含管理线程池的一些方法(submit,shutdown),可通过submit(Runnable task)提交任务代码。

      • ThreadPoolExecutor:是ExecutorService的实现类,线程池执行器。
      • SchedulePoolExecutor:是ThreadPoolExecutor的子类,
    • Executors工厂类:通过此类可以获得一个线程池。

      • 创建固定大小的线程池

      • 创建缓存线程池,由任务的多少来决定

      • 创建单线程池(里面只有一个线程)

        Executors.newSingleThreadExeutor();
        
      • 创建调度线程池(调度:周期执行,或定时执行)

        Executors.newScheduledThreadPool(线程个数);
        
    • shutdown : 等待所有任务执行完毕,然后关闭线程池

    • shutdownNow:直接停止所有任务,关闭线程池

    • public class Demo {
          public static void main(String[] args){
              // 1.1创建固定大小的线程池(例:4个线程)
              ExecutorService ex = Executors.newFixedThreadPool(4);
              // 1.2创建缓存线程池,由任务的多少来决定
              ExecutorService ex = Executors.newCachedThreadPool();
              // 2.创建线程任务
              Runnable runnable = new Runnable(){
                  // 采用匿名内部类
                  @override
                  public void run() {...};
              };
              // 3.提交任务
              for (int i=0; i<4; i++) {
                  es.submit(runnable);
              }
              // 4.关闭线程池
              es.shutdown;
          }
      }
      
7.3 Callable接口
  • public interface Callable {
        public V call() throws Exception;
    }
    
  • JDK5加入,与Runable接口类似,实现之后代表一个线程任务

  • Callable具有泛型返回值,可以声明异常(Runnable接口无返回值不具有这两个特点,这也是Callable接口与Runnable的区别)

  • 异步编程用Callable接口

  • package com.stu.szh.duoxiancheng;
    
    import com.sun.corba.se.impl.orbutil.closure.Future;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    
    public class Callable03 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // 1.创建Callable对象
            Callable callable = new Callable() {
                @Override
                public Integer call() throws Exception {
                    System.out.println(Thread.currentThread().getName()+"开始计算");
                    int sum = 0;
                    for (int i=1; i<=100; i++){
    
                        sum += i;
                        Thread.sleep(100);
                    }
                    return sum;
                }
            };
            // 2.将Callable对象转换成可执行对象
            FutureTask task = new FutureTask<>(callable);
            // 3.将任务交给线程对象
            Thread thread = new Thread(task);
            // 4.启动线程
            thread.start();
            // 5.因为有返回值,所以可以获取结果
            // get方法要等到call方法执行完毕,才会返回值
            Integer sum = task.get();
            System.out.println("结果是: "+sum);
        }
    }
    
    
  • 在线程池中使用Callable

  • package com.stu.szh.duoxiancheng;
    
    import java.util.concurrent.*;
    
    
    public class CallableChi03 {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            // 1.创建线程池
            ExecutorService ex = Executors.newFixedThreadPool(1);
            // 2.提交任务,Future:表示将要执行完任务的结果
            Future future = ex.submit(new Callable() {
                @Override
                public Integer call() throws Exception{
                    System.out.println(Thread.currentThread().getName()+"开始计算");
                    int sum = 0;
                    for (int i=0; i<=100; i++){
                        sum+=i;
                    }
                    return sum;
                }
            });
            // 3.获取任务结果,等待任务执行完毕才返回结果
            System.out.println(future.get());
            // 4.关闭线程
            ex.shutdown();
        }
    }
    
    
7.4 Future接口
  • future表示将要完成任务的结果
7.5 线程的同步和异步
  • 同步:

    • 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。
    • 注解:单条执行路径。
  • 异步:

    • 形容一次方法调用,异步一旦开始,像是一次消息传递,调用告知者后立刻返回,二者竞争时间片,并发执行。
    • 注解:多条执行路径
八. 锁 8.1 Lock接口
  • JDK5加入,与synchronized(synchronized效率较低)相比,显示定义,结构更加灵活。

  • 提供更多实用性方法,功能更强大,性能更优越。

  • 常用方法:

    • void lock()		// 获取锁,如果锁被占用,则等待。
      
    • booleab tryLock()		// 尝试获取锁(成功则返回true,失败则返回false,不阻塞)
      
    • void unlock()		// 释放锁
      
8.2 重入锁
  • ReentrantLock: Lock接口的实现类,与synchronized一样具有互斥锁的功能。

  •  class X {
       private final ReentrantLock lock = new ReentrantLock();
       // ...
    
       public void m() { 
         lock.lock();  // block until condition holds
         try {
           // ... method body
         } finally {
           lock.unlock()
         }
       }
     }
    
    
8.3 读写锁
  • ReentrantReadWriteLock:

    • 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁。
    • 支持多次分配读锁,使多个读操作可以并发执行。
  • 互斥规则:

    • 写-写:互斥,阻塞。
    • 读-写: 互斥,阻塞。
    • 读-读: 不互斥,不阻塞。
    • 在读操作远远高于写操作的环境中,可在保证线程安全的情况下,提高运行效率。
  • class CachedData {
       Object data;
       volatile boolean cachevalid;
       ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
       void processCachedData() {
         rwl.readLock().lock();
         if (!cachevalid) {
            // Must release read lock before acquiring write lock
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            // Recheck state because another thread might have acquired
            //   write lock and changed state before we did.
            if (!cachevalid) {
              data = ...
              cachevalid = true;
            }
            // Downgrade by acquiring read lock before releasing write lock
            rwl.readLock().lock();
            rwl.writeLock().unlock(); // Unlock write, still hold read
         }
    
         use(data);
         rwl.readLock().unlock();
       }
     }
     
    


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/276801.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号