进程
独立性:每个进程拥有自己私有的地址空间,拥有独立的资源。动态性:程序是静态的指令集合,进程是动态的。进程具有自己的生命周期和不同的状态。并发性:多个进程可以在单个处理器上并发执行,互不影响。线程
线程是进程的执行单元,一个进程可以有多个线程。一个线程可以拥有自己的堆栈,程序计数器和局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源线程是独立运行的,它并不知道是否还有其他线程存在操作系统无需对多线程实现调度和管理以及资源分配,这些由进程本身完成。操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。
Runnable对象仅仅作为Thread对象的target,实际的线程对象依然是Thread,只是Thread负责执行target的run方法。
Callable接口提供了一个call方法可以作为线程执行体。call方法比run方法更强大。但是Callable接口没有实现Runnable接口,因此不能作为Thread的target。
call方法可以有返回值call方法可以声明抛出异常Future接口代表call方法的返回值,并且Future接口有一个实现类FutureTask实现类,该实现类实现了Future接口和Runnable接口,因此可以作为Thread对象的target。一般可搭配Executors线程池一起使用,或者作为Thread的target。
步骤:
1.创建Callable实现类的实例。 2.使用FutureTask类包装Callable对象。该FutureTask对象封装类call方法的返回值。 3.使用FutureTask对象作为Thread对象的target,并启动线程 4.调用FutureTask对象的get方法获取子线程执行结束后的返回值。
下面给出Thread包装线程示例:
public static void main(String[] args) throws Exception { //1.创建Callable实现类的实例 Callable<Integer> call = new Callable<Integer>() { public Integer call() throws Exception { //线程执行体 System.out.println("计算线程正在计算结果..."); Thread.sleep(3000); return 1; } }; //2.使用FutureTask类包装Callable对象 FutureTask<Integer> task = new FutureTask<>(call); //3.使用FutureTask对象作为Thread对象的target,并启动线程 Thread thread = new Thread(task); thread.start(); System.out.println("main线程干点别的..."); //4.获取子线程执行结束后的返回值 Integer result = task.get(); System.out.println("从计算线程拿到的结果为:" + result); }实现Runnable接口和实现Callable接口方式:
还可以继承其他类多个线程可以共享同一个target,非常适合多个相同线程来处理同一份资源的情况。继承Thread类方法:
线程已经继承类Thread类,不能再继承其他父类多个线程之间无法共享线程类的实例变量。新建状态:new了一个线程后,该线程就处于新建状态。
就绪状态:调用start方法后,处于就绪状态。
运行状态:处于就绪状态的线程获得了CPU,开始执行,就处于运行状态。
阻塞状态:处于运行状态的线程被中断,使得其他线程获得执行机会。被阻塞的线程在合适的时候会重新进入就绪状态。当发生如下情况时,线程将进入阻塞状态:
线程调用sleep主动放弃所占用的CPU资源线程调用了阻塞式IO方法,在该方法返回前,线程被阻塞线程试图获得一个同步监视器,但该监视器正被其他线程持有线程调用了wait方法,在等待某个notify程序调用线程的suspend方法将该线程挂起。(这个方法容易导致死锁,所以应该尽量避免)死亡状态:线程结束后就处于死亡状态。
run或call方法执行完成,线程正常结束线程抛出一个未捕获的Exception调用线程的stop方法结束该线程(容易导致死锁)如果直接调用了线程对象的run方法启动,则线程对象会被当成一个普通对象,也不用再调用线程对象的start方法了。
只能对处于新建状态的线程调用start方法,也不要对已死亡的线程调用start方法,否则将引发IllegalThreadStateException。
如果希望调用子线程的start方法后子线程立即开始执行run方法,可以使用Thread.sleep(1)来让当前运行的线程(主线程)睡眠1毫秒。
当主线程结束时,其他线程并不受影响,不会随之结束。一旦子线程启动起来后,就拥有和主线程相同的地位。
为了测试某个线程是否死亡,可以调用线程对象的isAlive方法,当线程处于就绪/运行/阻塞状态时,返回true;新建/死亡状态时,返回false。
当某个程序执行时调用其他线程的join方法时,调用线程将被阻塞,直到被join方法加入的join线程执行完为止。
join()join(long millis): 设置最长等待时间所有前台线程都死亡,后台线程会自定死亡。调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程(注意要在start方法之前)。Thread还有一个isDaemon方法,判断线程是否是后台线程。
MyThread thread = new MyThread(); // 设置为后台线程,要在start之前 thread.setDaemon(true); thread.start();前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。
调用Thread的静态sleep方法,可以让当前正在执行的线程暂停一段时间,并进入阻塞状态。
即使程序中没有其他可执行的线程,处于sleep()中的线程也不会执行。
sleep 方法声明抛出了InterruptedException异常,所以调用sleep方法时要处理该异常(捕获或继续抛出)
//让当前线程暂停1秒 Thread.sleep(1000);调用Thread的静态yield方法,可以让当前正在执行的线程暂停,但它不阻塞该线程,只是将该线程转入就绪状态。yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次。完全可能的情况是:调用yield线程又被重新执行。
Thread.yield();每个线程默认的优先级都与创建它的父线程优先级相同。Thread提供了setPriority(int)和 getPriority来设置和获取指定线程的优先级。
setPriority的参数范围是1~10之间的整数。也可以使用Thread类的三个静态常量:
MAX_PRIORITY: 10MIN_PRIORITY: 1NORMAL_PRIORITY:5不同操作系统上的优先级并不相同,最好使用Thread的静态常量设置优先级,这样使程序具有更好的可移植性。
obj就是同步监视器。任何时刻只有一个线程可以获得对同步监视器的锁定。当同步代码块执行完时,该线程会释放对该同步监视器的锁定。通常推荐使用可能被并发访问的共享资源充当同步监视器。
使用sychronized修饰方法。(非静态方法)无须显示指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。
synchronized可以修饰方法,代码块,但不能修饰构造器,变量等。
public sychronized void draw(){ }程序无法显示释放对同步监视器的锁定。
释放同步监视器的锁定的情况:
同步方法/同步代码块执行完同步方法/同步代码块出现了未处理的Error或Exception同步方法/同步代码块中执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器线程不会释放同步监视器的情况:
同步方法/同步代码块中调用了Thread.sleep()/Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器线程在执行同步方法/同步代码块时,其他线程调用了该线程的suspend方法将该线程挂起,该线程不会释放同步监视器。Java5开始提供同步锁–更强大的线程同步机制,显示定义同步锁对象来实现同步。
常用的是ReentrantLock(可重入锁),Lock的实现类,可以显示的加锁,释放锁。
ReentrantLock lock = new ReentrantLock(); lock.lock(); ... lock.unlock();ReentrantLock具有可重入性,一个线程可以对被已加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显示调用unlock来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。
当两个线程相互等待对方释放同步监视器时就会发生死锁。
一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是等待同步监视器的线程处于阻塞状态,无法继续。
Object类提供了三个方法实现线程通信:wait(),notify(),notifyAll()。
wait : 导致当前线程等待,直到其他线程调用该同步监视器的notify()或者notifyAll()方法来唤醒该线程。调用wait方法的当前线程会释放对该同步监视器的锁定。notify : 唤醒在此同步监视器上等待的单个线程。会选择唤醒等待的其中一个线程,选择是任意的。notifyAll : 唤醒在此同步监视器上等待的所有线程。这三个方法必须由同步监视器对象来调用。
对于同步方法,可直接调用这三个方法。因为该类的默认实例this就是同步监视器。对于同步代码块,同步监视器是synchronized后括号的对象,所以必须使用该对象调用这三个方法。使用Lock对象来进行同步时,就不能使用wait(),notify(),notifyAll()方法进行线程通信,因此需要使用Condition对象来进行线程通信。Condition实例被绑定在一个Lock对象上。Condition提供来三个类似与wait(),notify()和notifyAll()的方法。
await(): 类似wait(),导致 当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll方法方法来唤醒该线程。signal(): 类似notify(),唤醒在此Lock对象上等待的单个线程。signalAll(): 类似notifyAll(),唤醒在此Lock上等待的所有线程。 Lock lock = new ReentrantLock(); Condition cond = lock.newCondition(); lock.lock(); .... cond.await(); .... cond.notifyAll(); .... lock.unlock();当生产者线程试图从BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞; 当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
BlockingQueue中两个支持阻塞的方法:
put(E e): 放入元素到尾部take(): 从头部取出元素ThreadGroup:线程组,对一批线程分类管理。对线程组进行控制,相当于同时控制这批线程。
如果没有显示指定线程属于哪个线程组,则该线程属于默认线程组。在默认情况下,子线程和创建它的父线程处于同一个线程组内。一旦某个线程加入了指定线程组之后,线程运行中不能改变它所属的线程组,直到该线程死亡。
Java 5新增了Executors工厂类来产生线程池,该工厂类包含如下几个静态工厂方法来创建线程:
newCachedThreadPool():线程数量不定,只有非核心线程。最大线程数是Integer.MAX_VALUE相当于任意大。超时时间为60秒。任务队列相当于一个空集合,导致任何任务都会立即被执行。适合大量的耗时少的任务。newFixedThreadPool():线程数量固定,只有核心线程。没有超时机制(线程不会被回收),任务队列没有大小限制。能更快响应外界的请求。newSingleThreadExecutor():只有一个核心线程。确保所有任务都在同一个线程中按顺序执行,不需要处理线程同步的问题。newScheduledThreadPool():核心线程数量固定,最大线程数是Integer.MAX_VALUE,非核心线程闲置时会被立即回收。主要用于执行定时任务和具有固定周期的重复任务。newSingleThreadScheduledExecutor():newWorkStealingPool():Java8新增的,创建持有足够的线程的线程池来支持给定的并行级别。充分利用多CPU并行的能力 //1. 创建线程池 ExecutorService pool = Executors.newFixedThreadPool(6); //2. 创建Runnable实现类或Callable实现类的实例,作为线程执行任务 Runnable target = () -> { ..... } //3.向线程池中提交任务 pool.submit(target); //4. 关闭线程池 pool.shutdown();注意: 调用shutdown()后,线程池不再接受新任务,但会将以前所有已提交任务执行完成。 shutdownNow()会试图停止所有正在执行的活动任务,暂停处理正在等待的任务。