进程 是指一个内存中运行的应用程序,每个进行都有一块独立的内存空间。
线程 是一个进程中一条执行路径,它们共享进程的内存空间,线程之间可以自由切换,并发执行,一个进程可以有多个线程。
分时调度 :所有线程轮流使用CPU,平均分配每个线程占用CPU的时间。
抢占式调度:让优先级高的线程优先使用CPU资源,CPU使用该方式在多个线程之间进行高速的切换,在某一时刻来说,对于CPU的一个核心来说,只有一个线程在执行,只是执行速度很快,我们无法感知出来,这中体验就像是多个线程在同时运行。多线程不能提高程序的运行速度,但是能提高程序的运行效率,使CPU的使用率更高。
同步:排队执行,效率低(效率低是一般情况下),线程安全 异步:同时执行,效率高,线程不安全
并发:多个事件在同一时间段发生
并行:多个事件在 同一时刻发生(真正的同时发生)
实现Runnable接口和继承 Thread 相比有如下优势:
1.通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况 2.可以避免由于Java只能单继承而带来的局限性 3.任务与线程是分离的,提高了程序的健壮性 4.线程池技术,只接受Runnable类型的任务,不接受Thread类型的线程线程阻塞 :也成为耗时操作,一般出现需要相对长时间的操作,比如接收用户输入。
线程中断:一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
在早期版本中,JDK提供了stop()方法用于中断 线程,但是后来发现该方法不合理,例如突然终止线程,可能会出现与此相关联的一些资源得不到及时释放;因此一个线程的中断应该由自身决定才符合逻辑,于是该方法被弃用。
在目前版本中,如果要想线程中断,应该由开发者对线程进行中断标记,然后通过捕获中断异常 InterruptedException 在 catch 块中进行中断线程。
例如下面的例子中,演示了如果主线程 main 执行完之后,则分支线程也中断的方法:
public class Demo2 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new MyRunnable()); //创建一个分支线程并分配一个任务 t.start(); for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + ":" + (i + 1)); Thread.sleep(1000); } /*分支线程中断的标记,如果t线程还在执行中,并且有能触发捕获该异常的操作(例如sleep()) 则会进入捕获中断异常的catch块内*/ t.interrupt(); } static class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ":" + (i + 1)); try { //每隔1秒输出一次,sleep()方法会触发检查中断异常,如果线程有中断标记则会进入catch块 Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("捕获到中断异常"); return; //捕获到中断异常,则终止运行并返回 } } } } } /* 输出结果: Thread-0:1 main:1 main:2 Thread-0:2 Thread-0:3 main:3 Thread-0:4 捕获到中断异常 从以上的输出结果可以看出,分支线程在主线程执行完成打印任务后就触发了分支线程的中断; 如果在分支线程的catch块中不进行return返回,则分支线程不会中断,直到线程正常结束。 */线程可分为 用户线程 和 守护线程 ,直接创建的线程都是用户线程;
对于用户线程来说,当一个进程不包含任何存活的用户线程时才结束;
对于守护线程来说,当最后一个用户线程结束时,所有守护线程自动结束。
public static void main(String[] args) throws InterruptedException { Thread t = new Thread(new MyRunnable()); //创建一个线程并分配一个任务 t.setDaemon(true); //设置为守护线程,要在线程启动之前进行设置 t.start(); for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + ":" + (i + 1)); Thread.sleep(1000); } } static class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + ":" + (i + 1)); try { Thread.sleep(1000); //每隔1秒输出一次 } catch (InterruptedException e) { } } } } /* 输出结果: main:1 Thread-0:1 Thread-0:2 main:2 Thread-0:3 main:3 Thread-0 从结果可以看出,设置为守护线程的t,在主线程结束后也跟着结束了。 */使用示例:
public static void main(String[] args) { SaleTicket s = new SaleTicket(); Thread t1 = new Thread(s, "窗口1"); Thread t2 = new Thread(s, "窗口2"); Thread t3 = new Thread(s, "窗口3"); t1.start(); t2.start(); t3.start(); } static class SaleTicket implements Runnable { private int ticket = 10; //票数 Object o = new Object(); //用作锁的对象 @Override public void run() { //如果锁对象写在方法体中,那么每个线程所拥有的锁就不是同一把锁,无法保证线程安全 // Object o = new Object(); while (true) { /*同步代码块,把需要上锁的部分括上就可以达到线程安全,o是锁对象*/ synchronized (o) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "准备出票中..." ); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println(Thread.currentThread().getName() + "出票完成,余票" + ticket); } else { break; } } } } }同步代码块可以精确控制一行或多行代码,我们也可以把需要上锁的部分单独写成一个同步方法供run() 调用,然后在方法声明中加入 synchronized 关键字即可。
对同步代码块进行修改可得:
public synchronized boolean sale() { /*在非静态的同步方法中,上锁对象为 this ; 如果该方法是静态方法,则上锁对象是类名.class,在本方法中就是SaleTicket.class*/ if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "准备出票中..." ); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println(Thread.currentThread().getName() + "出票完成,余票" + ticket); return true; } return false; }前两种同步代码块和同步方法都属于隐式锁 。
显示锁使用方法:
//首先创建一个锁对象(默认非公平锁) Lock l = new ReentrantLock(); //其次在需要上锁的代码前一行进行手动上锁 l.lock(); { //需要上锁的部分 } //最后在上锁的代码后一行进行解锁 l.unlock();公平锁就相当于先来先到,根据等待时间进行排队;非公平锁就相反,在每次锁解锁后,线程之间又进行争夺,谁抢到谁就执行。
上述介绍的三种上锁方式默认都是非公平锁,如果需要使用公平锁,则需要在创建显示锁的时候传入加上参数 true。
Lock l = new ReentrantLock(true); //创建公平锁线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。
死锁产生的条件
互斥条件:一个资源,或者说一个锁只能被一个线程所占用,当一个线程首先获取到这个锁之后,在该线程释放这个锁之前,其它线程均是无法获取到这个锁的。占有且等待:一个线程已经获取到一个锁,再获取另一个锁的过程中,即使获取不到也不会释放已经获得的锁。不可剥夺条件:任何一个线程都无法强制获取别的线程已经占有的锁循环等待条件:线程A拿着线程B的锁,线程B拿着线程A的锁。。wait() :线程休眠
notify() :唤醒等待该线程的单个线程
new
尚未启动状态
Runnable
正在执行状态
Blocked
阻塞状态,在排队时就是处于阻塞状态,阻塞取消后回到执行状态
Waiting
无限等待状态,直到某个线程唤醒它,唤醒后变成执行状态
TimeWaiting
指定时间等待状态,被唤醒或者计时结束就回到执行状态
Terminated
线程终止
当并发的线程很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会使系统效率大打折扣,因为创建线程和结束线程都需要时间。线程池的出现就是为了解决并发线程效率不高的问题,线程池中的线程可以反复使用,这样就省去了频繁创建线程的操作,节省很多时间。
线程数量没有限制,在执行时,首先判断线程池是否存在空闲线程,如果存在,则使用空闲线程执行任务;如果不存在,则重新创建线程并放到线程池里供其使用。
public static void main(String[] args) throws InterruptedException { //创建一个缓存线程池 ExecutorService service = Executors.newCachedThreadPool(); //为线程池分配任务 service.execute(new Runnable() { @Override public void run() { System.out.println("任务1:" + Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("任务2:" + Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("任务3:" + Thread.currentThread().getName()); } }); //主线程休眠一秒钟 Thread.sleep(1000); service.execute(new Runnable() { @Override public void run() { System.out.println("任务4:" + Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("任务5:" + Thread.currentThread().getName()); } }); } /*输出: 任务3:pool-1-thread-3 任务1:pool-1-thread-1 任务2:pool-1-thread-2 任务4:pool-1-thread-2 任务5:pool-1-thread-1 从结果可以看出,线程确实得到复用了 */线程数量是指定的固定值,在执行时,首先判断池中是否存在空闲线程,如果存在则使用;如果不存在空闲线程,且在线程池未满的情况下,会重新创建线程并放到线程池供其使用;如果线程不存在空闲且线程池已满,则需要等待线程池存在空闲线程才进行执行新的任务。
//创建一个长度为2的线程池 ExecutorService e = Executors.newFixedThreadPool(2);有点类似于定长线程池中设置线程数为1.
//创建一个单线程线程池 ExecutorService e = Executors.newSingleThreadExecutor();