Java 多线程学习

进程

  • 独立性:进程是系统中独立存在的实体,他可以拥有自己独立的资源,每一个进程都拥有自己的私有地址空间。在没有经过进程本身允许的情况下,一个用户进行不能直接访问其他进程的地址空间。
  • 动态性:进程与程序的区别在于,程序是一个静态的指令集合,而进程是一个正在系统中活动的指令集合。在程序中加入了时间的概念。进程具有自己的生命周期和各种不同的在黄台,这些概念在程序中都是不具备的。
  • 并发性:多个程序可以在单个处理器上并发执行,多个进程之间不会相互影响。

并行和并发:

​ 并行指的是在同一时刻,有多条指令在多个处理器上执行;并发指的是在同一时刻只能有一条指令在执行,当多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果。

归纳:操作系统可以同时指向多个任务,每个任务就是一个进程;进程可以同时执行多个任务,每个任务就是线程

线程

  • 进程之间不可以共享内存,但线程之间共享内存非常容易。
  • 系统创建进程时需要为该进程重新分配资源,但创建线程则代价小的多,因此使用了多线程来实现多任务的并发比多进程的效率高。
  • java 语言内置了多线程功能的支持,而不是单纯的作为底层操作系统的调度方式,从而简化了java的多线程编程。
创建线程的三种方式:
1, 继承Thread类创建线程:

​ 【1】, 定义Thread类的子类,并实现run()方法。run()方法就代表这线程要执行的任务

​ 【2】,创建子类的对象,并调用start()方法,线程就会启动

public class CreateThread extends Thread {
    
    //重写run方法,run方法就是线程的执行体。
    @Override
    public void run() {
        //Thread.currentThread().getName() 获取当前线程的名字。
        System.out.println("当前线程为:"+Thread.currentThread().getName());
    }

    public static void main(String[] args){
        CreateThread  c1 = new CreateThread();
        c1.start();
    }
}
2,实现 Runnable接口创建线程类:

​ 【1】,实现 Runnable 接口,并实现run()方法。

​ 【2】,创建Runnable 实现类的实例,创建Thread类的实例 将 实现类的实例 传入。

​ 【3】,调用Thread的start()方法,线程会启动。

public class CreateThread implements Runnable  {
    
    public static void main(String[] args){
        CreateThread createThread = new CreateThread();
        Thread thread1 = new Thread(createThread);
        Thread thread2 = new Thread(createThread);
        thread1.start();
        thread2.start();
    }

    @Override
    public void run() {
        //Thread.currentThread().getName() 获取当前线程的名字。
        System.out.println("当前线程为:"+Thread.currentThread().getName());
    }
}
3,使用Callable 和 Future 创建线程

​ 【1】,创建Callable接口的实现类,并试下呢call()方法,这个call()方法具有返回值,下面使用的是匿名内部类 的方式。

​ 【2】,使用 Future Task 来包装Callable对象,该对象封装了call()方法的返回值。

​ 【3】,创建 Thread 对象,将 Future Task 对象传入,然后调用start()方法 就可以启动线程。

​ 【4】,调用Future Task 对象的get()方法获取call()方法的返回值

public class CreateThread  {

    public static void main(String[] args){
        
        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                //Thread.currentThread().getName() 获取当前线程的名字。
                System.out.println("当前线程为:"+Thread.currentThread().getName());
             return 0;
            }
        });

        Thread thread = new Thread(task, "有返回值的线程");
        thread.start();
        try {
            System.out.println("线程的返回值为:"+task.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
创建线程的三种方式对比
  • 继承 Thread 类 会导致当前对象无法继承其他类,降低了扩展性。
  • 实现 Runnable 接口 可以采用内部类的方式,也可以直接实现,并不影响继承。
  • 实现 Callable 接口方式 创建的线程具有返回值,并且可以直接实现,不影响继承。
线程的生命周期:

​ 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。尤其是当线程启动以后,它不可能一直"霸占"着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换。

1,新建和就绪状态

​ 当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他的Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。

​ 当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

​ 只能对处于新建状态的线程调用start()方法,否则将引发IllegaIThreadStateExccption异常

2,运行和阻塞状态

​ 如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态,如果计算机只有一个CPU。那么在任何时刻只有一个线程处于运行状态,当然在一个多处理器的机器上,将会有多个线程并行执行;当线程数大于处理器数时,依然会存在多个线程在同一个CPU上轮换的现象。

​ 当一个线程开始运行后,它不可能一直处于运行状态(除非它的线程执行体足够短,瞬间就执行结束了)。线程在运行过程中需要被中断,目的是使其他线程获得执行的机会,线程调度的细节取决于底层平台所采用的策略。对于采用抢占式策略的系统而言,系统会给每个可执行的线程一个小时间段来处理任务;当该时间段用完后,系统就会剥夺该线程所占用的资源,让其他线程获得执行的机会。在选择下一个线程时,系统会考虑线程的优先级。

当发生如下情况时,线程会进入阻塞状态:

  • 线程调用了sleep()方法 ,进行沉睡。
  • 线程调用了一个 阻塞式IO 方法,在该方法返回之前,线程被阻塞。
  • 线程试图获得一个锁。
  • 线程在等待某个通知(notify)
  • 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法

解除阻塞:

  • 调用sleep()方法的线程经过了指定时间。
  • 线程调用的阻塞式IO方法已经返回。
  • 线程成功地获得了锁。
  • 处于挂起状态的线程被调掉了resume()恢复方法。
3,线程死亡

线程会以如下3种方式结束,结束后就处于死亡状态

  1. run()或call()方法执行完成,线程正常结束。
  2. 线程抛出一个未捕获的Exception或Error。
  3. 直接调用该线程stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用。
常用的方法
  • join():等待这个线程死亡后向下执行;

    重载方法:

    public final void join(long millis) : 最多等待 millis秒,超过这个时间则不会等待。

    public final void join(long millis, int nanos):等待millis 秒,再加上 nanos 纳秒。

    public class CreateThread  {
    
        public static void main(String[] args){
    
          Thread t1 =   new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        System.out.println(Thread.currentThread().getName());
                    }
                }
            });
          t1.start();
            try {
                //等t1线程执行玩后在 执行 主线程。
                t1.join();
                System.out.println(Thread.currentThread().getName()+"---------");
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
    }
    
  • sleep():让当前的线程沉睡一段时间,并进入阻塞状态,

    public static void sleep(long millis):沉睡 millis毫秒。

    public static void sleep(long millis, int nanos) : 沉睡millis毫秒加上 nanos 纳秒。

    当前线程调用这个方法进入阻塞后,在睡眠时间段内,不会释放锁。处于sleep()的线程也不会执行。英雌这个方法常用来暂停程序的执行。

    private static Thread t1;
    public static void main(String[] args){
        t1 = new Thread(new Runnable() {
              @Override
              public void run() {
                  try {
                      //使当前线程 暂停一秒
                      t1.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  for (int i = 0; i < 100; i++) {
                      System.out.println(Thread.currentThread().getName());
                  }
              }
          });
      t1.start();
    }
    
  • setPriority() 设置线程的优先级:

    设置线程的优先级,范围时1~10之间。

  • getPriority():返会指定线程的优先级。

后台线程

​ 有一种线程,他是在后台运行的,他的任务是为其他的线程提供服务了,这种线程被称为“后台线程”,又称为“守护线程”。JVM的垃圾回收 线程 就是典型的后台线程。

​ 后台线程有个特征:如果所有前台线程都死亡,则后台线程会自动死亡。

​ 调用 Thread 的 setDaemone(ture) 可以将指定的线程设置为 后台线程。调用 isDaemon()方法,用于判断指定线程是否为后台线程。

public class CreateThread {

    private static Thread t1;
    public static void main(String[] args) {
        t1 = new Thread(new Runnable() {
            @Override
            public void run() {

                for (int i = 0; i < 100; i++) {
                    System.out.println(i);
                }
            }
        });
        //设置t1 为后台线程
        t1.setDaemon(true);
        t1.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("我是主线程" + Thread.currentThread().getName());
        }
        //当主线程执行完后,前台线程将会死亡,后台线程会跟着死亡。
    }
}
线程的安全问题:

线程的同步方法

方法锁、对象锁、类锁的意义和区别

深入理解java并发之 synchronized 的实现原理

使用Condition 控制线程通信

​ 如果程序不使用 synchronized 关键字来保证同步 ,而是直接使用 Lock 对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用 wait()、notify()、notify All()方法进行线程通信了

​ 当使用 Lock 对象来保证同步时 ,java 提供了一个 Condition 类来保持协调,使用 Condition 可以让那些已经得到 Lock 对象却无法继续执行的线程释放 Lock 对象,Condition 对象也可以唤醒替他初一等待的线程。

​ Condition 将同步监视器方法(如 wait()、notify()等) 分解成截然不同的对象,以便通过将这些对象与 Lock 对象组合使用,为每个对象提供等待集(wait-set)。在这种情况下,Lock 代替了同步方法或者同步代码块,Condition 代替了同步监视器的功能。

​ Condition 实例被绑定在一个 Lock 对象上,要想获得特定的 Lock 实例的 Condition 实例,调用 Lock 的 new Condition()方法即可。Condition 类提供了如下三个方法:

  • await() : 类似于隐式同步监视器上的 wait()方法,导致当前线程等待,直到其他线程调用该 Condition 的 signal()方法 或者 signal All() 方法来唤醒该线程。该方法还有更多变体,如 long awaitNanos() 方法等。可以完成更丰富的操作。
  • signal() : 唤醒在此 Lock 对象上等待的单个线程。如果所有线程都在该 Lock() 对象上等待,则会选择唤醒其中一个线程。选择是任意性的。 只有当前线程调用了 await() 方法,才可以被 signal() 方法唤醒。
  • signal All() : 唤醒在此Lock 对象上等待的所有线程。 只有当前线程调用了 await() 方法,才可以被 signal() 方法唤醒。
public class CreateThread {
    public static void main(String[] args) {
       final Lock lock = new ReentrantLock();
       final Condition condition = lock.newCondition();
        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    System.out.println("等待中");
                    condition.await();
                    for (int i = 0; i < 100; i++) {
                        System.out.println(i);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                lock.unlock();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    Thread.sleep(2000);
                    System.out.println("唤醒正在等待的线程.....");
                    condition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }).start();
    }
}

注意 :无论是在 使用的是什么锁 在调用 wait()/await() 、notify()/signal() … 等方法的时候,一定要保证这些方法要在写在锁的 内部,也就是 lock() 和 unlock() 之间 /synchronized() 内部,不然非常容易抛出异常,或者是死锁。

参考自 java疯狂讲义

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Tʀᴜsᴛ³⁴⁵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值