线程与进程
进程:
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动后,里面的若干执行路径又可以划分成若干个线程。
线程调度
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。可以理解为排队使用,一个使用结束下一个再接着使用。
抢占式调度
优先让优先级高的线程使用CPU,如果现场层的优先级相同,那么会随机选择一个(线程随机性),Java使用的就是抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉很快,看上去就是像在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但是能够提
高程序运行效率,让CPU的使用率更高。
同步和异步
同步:
排队执行,效率低但是安全
异步:
同时执行,效率高但是数据不安全。
并发与并行
并发:
指两个或多个事件在同一时间段内发生
并行:
指两个或多个事件在同一时刻发生(同时发生)
创建一个线程的两种方法
继承Thread
实现Runnable
都需要实现run方法
实现Runnable与继承Thread相比有如下优势:
1. 通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
2. 可以避免单继承所带来的局限性
3. 任务与线程本身是分离的,提高了程序的健壮性
4. 后续学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
Thread类
线程是程序中执行的线程。Java虚拟机允许应用程序同时运行多个执行线程。
每个线程都有优先级。具有较高优先级的线程优先于具有较低优先级的线程执行。每个线程可能也可能不会被标记为守护进程。当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护进程线程。
设置和获取线程名称
Thread thread = new Thread(Runnable target,String name)
Thread.currentThread().getName()
线程休眠
Thread.sleep()
线程阻塞
就好比需要用户从控制台输入数据,如果用户一直不输入,那么等待用户输入的线程就一直处于阻塞状态
线程的中断
一个线程是一个独立的执行路径,他是否应该结束应该由其自身决定
以前直接调用stop方法让其线程直接死亡,但是这个方式是不合理的,现在已经不能用了,现在采取给线程一个中断标记。来告知线程应该结束了。
给线程t1添加中断标记 t1.interrupt();
当线程在执行的时候就会去发现这个标记,然后由程序员决定是否杀死这个线程,需要结束的话就直接return
守护线程
线程:分为守护线程和用户线程
用户线程:当一个进程不包含任何的存活的用户线程时,进程结束
守护线程:用于守护用户线程,当最后一个用户线程结束时,所有守护线程自动死亡
在开启线程之前使用t1.setDaemon(true)将线程设置为守护线程
线程安全问题
1.同步代码块(简单理解为,同步代码块中是排队进行的)
格式:synchronized(锁对象){
}
看同一把锁,不然也达不到线程安全的问题
2.同步方法
也就是将需要排队的地方,放到一个方法中吗,然后去对这个方法加锁
3.显示锁 Lock 子类 ReentrantLock
同步代码块和同步方法都属于隐式锁
显示锁Lock和隐式锁synchronized的区别
一、层面不同
synchronized:Java中的关键字,是由JVM来维护的,是JVM层面的锁。
synchronized底层是通过monitorenter进行加锁
底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。并且只有在同步块或同步方法中,JVM才会调用monitory对象的,才可以调用wait/notify等方法
通过monitorexit来退出锁
Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API,是API层面的锁。
lock是通过调用对应的API方法来获取锁和释放锁的。
二、使用方式不同
synchronized
程序能够自动获取锁和释放锁。Sync是由系统维护的,如果非逻辑问题的话,不会出现死锁。
Lock
需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。
手动获取锁方法:lock.lock()。释放锁:unlock方法。并且需要配合tyr/finaly语句块来完成
三、等待是否可中断
synchronized
不可中断,除非抛出异常或者正常运行完成。
Lock
可以中断的。
中断方式:
调用设置超时方法tryLock(long timeout ,timeUnit unit)
调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
四、加锁的时候是否可以设置成公平锁
synchronized
只能为非公平锁
Lock
两者都可以的。默认是非公平锁
在其构造方法的时候可以传入Boolean值。true:公平锁、false:非公平锁
五、锁绑定多个条件来condition
synchronized
不能精确唤醒线程。要么随机唤醒一个线程;要么是唤醒所有等待的线程。
Lock
用来实现分组唤醒需要唤醒的线程,可以精确的唤醒。
六、性能区别
synchronized
托管给JVM执行,Java1.5中,由于需要调用操作接口,可能导致加锁消耗时间过长,与Lock性比性能低。1.6以后,语义定义更加清晰,有适应自旋、锁粗化、锁消除、轻量级锁、偏向锁等,可进行许多优化,性能提高了,与Lock差不多。
Lock
java写的控制锁的代码,性能高
公平锁和不公平锁
公平锁:谁先排队,谁先得到
不公平锁:所有线程共同去抢锁,不存在先到先得
线程死锁
比如线程A在等B,线程B也在等A,那么就会出现死锁
多线程通信问题
举例:生产者和消费者
保证生产者在生产的时候,消费者没有在消费,消费者在消费的时候生产者没有在生产
线程的六种状态
带返回值的线程Callable
Runnable与Callable的不同点 Runnable没有返回值;Callable可以返回执行结果 Callable接口的Call()允许抛出异常,Runnable的run()不能抛出 相同点 都是接口 都可以编写多线程程序 都采用了Thread.start()启动线程 Callable获取返回值 Callable接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞
线程池概述( Executors )
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处
降低资源消耗
提高响应速度
提高线程的可管理性
Java中的四种线程池 . ExecutorService
一、缓存线程池
获取一个缓存线程池
ExecutorService service = Executors.newCachedThreadPool();
执行流程: 1.判断线程池是否存在空闲线程 2.存在则使用 3.不存在,则创建线程,并放入线程池,然后使用 加入任务方法: service.execute(任务) 二、定长线程池 创建定长线程池,长度为2,长度可以自由变动ExecutorService service = Executors.newFixedThreadPool(2);
执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满的情况下,则创建线程,并放入线程池,然后使用
4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
加入任务方法:
service.execute(任务) 三、单线程线程池 创建单线程线程池ExecutorService service = Executors.newSingleThreadExecutor();
也可以使用创建定长线程池,创建的时候传入数值1,效果一致
执行流程:
1.判断线程池的那个线程是否空闲
2.空闲则使用
3.不空闲,则等待,池中的单个线程空闲后使用
· 四、周期性任务定长线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2)
周期任务 定长线程池
执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满的情况下,则创建线程,并放入线程池,然后使用
4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
定时执行
周期执行