- 进程,当一个程序进入内存时,即变成一个进程。进程是运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。
- 线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以共享父进程中的共享变量及部分环境。
Java使用Thread类代表线程,所有线程对象都必须是Thread类或其子类的实例,用线程执行体来代表这段程序流。创建线程的几大方法:
继承Thread类创建线程类
1. 继承Thread类
2. 重写run()方法,里头的代码代表了线程要完成的任务
3. 调用线程对象的star()方法来启动public class Threah extends Thread { private int i; @Override public void run() { for( ; i < 100; i++) { System.out.println(getName() + " " + i); } } public static void main(String[] args) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); Threah st = new Threah(); if (i == 20) { new Threah().start(); new Threah().start(); } } } }
实现Runnable接口创建线程类
1. 定义Runnable的实现类
2. 重写run()方法
3. 创建该实现类的实例,并将该实例作为Thread的target来创建对象
4. 调用线程对象的start()来启动该线程public class Threah implements Runnable { private int i; @Override public void run() { for( ; i < 100; i++) { // 获取当前线程只能使用Thread.currentThread() System.out.println(Thread.currentThread().getName() + " " + i); } } public static void main(String[] args) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + " " + i); Threah st = new Threah(); if (i == 20) { new Thread(st , "新线程一").start(); new Thread(st , "新线程二").start(); } } } }
使用Callable和Future创建线程(Lamada表达式相关)
1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,再创建Callable实现类的实例
2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了改Callable对象的call()方法的返回值
3. 使用FutureTask对象作为Thread对象的target创建并启动新线程
4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
程序至少会创建一个主线程,主线程的线程执行体不是由run()方法确定的,而是由main()方法确定的。
- 线程的五个状态: 新建、就绪、运行、阻塞、死亡
- 当使用关键字new创建了一个线程后,该线程就处于新建状态。当线程对象调用start()方法后,该线程就处于就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行,只是表示可以运行了,运行时间取决于JVM的调度算法。
- 如果就绪状态的线程获得了CPU,开始执行run方法的线程执行体,该线程就处于运行状态。线程阻塞意味着线程不能继续执行下去了,被阻塞的进程会在合适的时间重新进入就绪状态,必须等待调度器再次调度它。
- 线程死亡代表线程结束。
- 线程抛出一个为捕获的异常或ERROR。
- 直接调用线程的stop()方法结束进程(容易死锁)。
控制线程:
- join(),让一个线程等待另一个线程完成的方法,当在某个程序执行流中调用其它线程的join方法时,调用线程将被阻塞,直到被join方法加入的Join线程完成为止。
- 后台进程,为其他线程提供服务,这种线程被称为“后台进程”。如果前台进程都死亡,后台进程会自动死亡。
- sleep(),线程睡眠,让当前正在执行的线程暂停一段时间,并进入阻塞状态。
- yield(),线程让步,让当前线程暂停一下,让系统的线程调度器重新调度一次。线程转入就绪状态。
- 改变线程优先级,setPriority(),使用该方法改变线程的优先级。
线程同步问题,程序中有连个并发线程在修改一个值(或对象或其他什么东西),就会引发问题。Java引入同步监视器来解决这个问题。
synchronized(obj) { // 括号里参数为同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定 // 推荐使用可能被并发访问的共享资源当作同步监视器 }
- 同步代码块的同步监视器在任何线程修改指定资源之前,首先对该监视器加锁,在加锁期间其他线程无法修改唉资源,当加锁线程修改完毕之后,该线程释放对该资源的锁定。通过这种方式保证并发线程在任一时刻只有一个线程可以进入修改共享资源的代码区。
- 同步方法,是用synchronized关键字来修饰的某个方法。若修饰的是实例方法,不需要显示的指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。
- 程序无法显式释放对同步监视器的锁定,线程释放对同步监视器的锁定的几种情况:
- 当前线程的同步方法、通过不代码块执行结束。
- 当前线程在同步代码块、同步方法中遇见了break,return终止了继续执行。
- 当前线程在同步代码块、同步方法中出现了未处理的异常或错误。
- 当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。
- 执行时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,但不会释放同步监视器
- 线程执行时,调用了该线程的suspend()方法将该线程挂起,不糊释放同步监视器。
- Java5开始,可以通过显式定义同步锁对象来实现同步,在这种机制下,同步锁由Lock对象充当。
- 每次只能有一个线程对Lock对象加锁,线程访问共享资源之前应先获得Lock对象。
- 死锁,当两个进程互相等待对方释放同步监视器时就会发生死锁,死锁时所有处于阻塞状态,无法继续运行。但JVM没有检测也没有采取措施来处理死锁。
- Java提供一些机制保障线程协调运行(进程通信),Object类的三个方法(必须使用synchronized关键字进行同步):
- wait(),导致当前线程等待,直到其他线程调用该同步监视器的notify和notifyAll方法来唤醒该线程。该方法会释放对同步监视器的锁定。
- notify(),唤醒在此同步监视器上等待的单个线程,若有多个则任意选择一个唤醒。
- notifyAll(),唤醒在此同步监视器上等待的所有线程。
- 若使用Lock对象来保证同步,Java提供Condition类来保持协调。await()、signal()、signalAll()同上述三个方法。
- 使用阻塞队列控制线程通信,Java5提供了一个BlockingQueue接口,作为线程同步的工具。程序的连个线程通过交替向BlockingQueue中放入元素,取出元素。可以很好的控制线程的通信。
- 当生产者试图向BlockingQueue中放入元素时,如果队列已满,则该线程被阻塞;当消费者试图取出元素时,如果队列为空,则该线程被阻塞。
- 线程组:ThreadGroup,可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。其内部定义了void uncaughttException(Thread t, Throwable e),该方法可以处理该线程组内的任意线程所抛出的异常。
- 线程池:系统启动一个新线程的成本很高,因为它涉及与操作系统交互。用线程池可以很好的提高性能,尤其是程序中药创建大量生存期很短的线程的时候。
- 线程池在系统创建时就会创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个线程来执行他们的run()或call()方法,当方法执行结束后,线程并不会死亡,而是再次返回线程池中成为空闲状态,等待下一个Runnable对象或Callable对象的run()或call()方法。
使用线程池来执行线程任务的步骤如下:
- 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
- 创建Runnable实现类或Callable实现类的实例,作为线程执行认为。
- 调用ExecutorService对象的submit()方法来提交Runnable实例或Callable实例。
- 当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池。
public class A { public static void main(String[] args) throws Exception{ // 创建一个有固定线程数的线程池,6个线程 ExecutorService pool = Executors.newFixedThreadPool(6); // 使用lambda表达式创建Runnable对象 Runnable target = () -> { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "的i值为" + i); } }; // 向线程池提交两个线程 pool.submit(target); pool.submit(target); // 关闭线程池 pool.shutdown(); } }
- ForkJoinPool,是一种特殊的线程池,将任务分到多个不同核心上,最后再集合的类。
- 线程相关类:
- ThredLocal类,代表一个线程局部变量,通过把数据放在ThreadLocal中可以让每个线程创建一个该变量的副本,从而避免并发访问的线程安全问题。
- 包装线程不安全的集合,Collections提供了静态方法用于包装这些集合。
Java疯狂讲义读书笔记第十六章
最新推荐文章于 2024-09-15 09:47:04 发布