Java 多线程学习

简介: 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种方式结束,结束后就处于死亡状态:


run()或call()方法执行完成,线程正常结束。

线程抛出一个未捕获的Exception或Error。

直接调用该线程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() 内部,不然非常容易抛出异常,或者是死锁。


相关文章
|
1天前
|
缓存 Java
【Java基础】简说多线程(上)
【Java基础】简说多线程(上)
5 0
|
1天前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
|
1天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
1天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
ava从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
|
1天前
|
设计模式 存储 前端开发
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
Java从入门到精通:2.2.1学习Java Web开发,了解Servlet和JSP技术,掌握MVC设计模式
|
1天前
|
Java API
Java从入门到精通:2.1.5深入学习Java核心技术之文件操作
Java从入门到精通:2.1.5深入学习Java核心技术之文件操作
|
1天前
|
并行计算 算法 安全
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
|
1天前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
6 0
|
1天前
|
监控 安全 Java
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
在Java中如何优雅的停止一个线程?可别再用Thread.stop()了!
7 2
|
1天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
8 1