线程与进程:
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
线程调度:
分时调度:
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度:
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为
抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,
只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时
刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使
用率更高。
同步与异步&并发与并行
同步:排队执行,效率低但是安全。
异步:同时执行,效率高但是数据不安全。
并发与并行
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
继承Thread
需要自行实现run()方法。
run方法就是线程要执行的任务方法。
在run方法中,实现run方法的代码就是一条新的执行路径
这个执行路径的处罚方式,不是调用run方法,而是通过thread对象的start()来启动任务
每个线程都拥有自己的栈空间,共用一份堆内存。
由一个线程调用的方法,这个方法也会执行在这个线程里面。
实现Runnable
创建一个类实现Runnable,用来作为实现子线程任务的类。
也是需要自行实现run方法,来实现线程的任务。
使用方式:
1.创建一个任务对象
2.创建一个线程,并为其分配一个任务
3.执行这个线程
实现Runnable 和 继承Thread相比有如下优势:
1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
2.可以避免单继承所带来的局限性。
3.任务与线程本身是分离的,提高了程序的健壮性。
4.后续学习的线程池技术,接受Runnable类型的任务,不接收Thread类型的线程。
也可以采用类似匿名内部类的方式实现线程。
Thread类(API)
getName(),getId()等方法
三个常量(最大优先级,最低优先级,默认优先级)
注意(stop()方法不能停止,已过时,不安全)
最常用的方法sleep()方法
守护线程(daemon线程)(依赖于用户线程)和用户线程
设置和获取线程名称
Thread.currentThread()(.getName()获取名称.setName()设置名称)方法可以得到当前运行的线程。
线程休眠sleep:
Thread.sleep()方法
线程阻塞:
所有消耗时间的操作都可以称为阻塞,会导致线程等待
线程的中断:
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
(线程启动以后,可能会使用很多资源,如果在外部停用的话,就会产生资源没有释放的问题,会产生很多不能回收的垃圾)。
注意:子类不能声明比父类更大的异常抛出。
对需要中断的线程使用interrupt()方法,告知线程应该中断,会在内部产生异常进入catch块,程序员决定是否杀死进程。
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
线程:分为守护线程和用户线程
用户线程:当一个进程不包含任何的存活的用户线程时,进行结束。
设置为守护线程的方法.setDaemon(true)。
线程安全问题:
数据有误
线程不安全的处理方式:
同步代码块:
线程同步:格式:synchronized(锁对象){
需要控制的部分(可以一行代码加锁)
}
注意:每个对象都看同一把锁。
同步方法(在设计的方法前加上synchronized修饰):
以方法为单位进行加锁。
将要加锁的部分设计为一个方法。
如果上锁的对象方法不是静态的,那么调用后使用它锁的对象就是this,
如果是静态的,就是对象名.class
如果同时有同步代码块和同步方法,如果进入到了代码块后,后续的线程也进不去同步方法中。
同步代码块和同步方法都属于隐式锁
显示锁Lock 子类 ReentrantLock
需要创建Lock对象,在需要上锁的部分进行.lock()方法,在需要解锁的部分进行.unlock()方法。
显示锁和隐式锁的区别:
一、层面不同
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写的控制锁的代码,性能高。
公平锁和非公平锁
公平锁:先来先到,有排队的流程。
非公平锁:抢占式,抢的快的先走。
创建公平锁的方法: 创建显示锁时,在ReentrantLock()中将参数设置为true。
线程死锁:
解决方法:在任何有可能导致锁产生的方法里,不要再调用另外有可能产生锁的方法。
多线程通信问题(生产者与消费者问题):
对进行完成的部分进行沉睡,需要时再去唤醒这样的操作。
this.notifyAll()方法(唤醒)和this.wait()方法(沉睡)
线程的六种状态:
new,Runnable,Blocked,Waiting(无限等待),TimeWaiting(有时间限制的等待),Terminated(终止)
带返回值的线程Callable
Runnable 与 Callable
接口定义 //Callable接口
public interface Callable<V> { V
call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T; }
}
2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
3. 通过Thread,启动线程
new Thread(future).start();
Runnable 与 Callable的相同点
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程
Runnable 与 Callable的不同点
Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出
Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执
行,如果不调用不会阻塞
.isDone()方法,.cancel()方法(看API)
线程池:所有的线程池调用都需要ExecutorService类型
缓存线程池(长度无限制):
任务加入后的执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在,则创建线程并放入线程池,然后使用
.execute()方法重写run()方法
定长线程池(长度是指定的数值):
任务加入后的执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
单线程线程池:
执行流程:
1.判断线程池的那个线程是否空闲
2.空闲则使用
3.不空闲,则等待池中的单个线程空闲后使用
周期任务定长线程池:
执行流程
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用
4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
周期性任务执行时:定时执行,当某个时机触发时,自动执行某任务。
创建方式:ScheduledExecutorService service = Executors.newScheduledThreadPool();
定时执行
service.schedule()方法来执行
有三个参数
参数1.定时执行的任务,参数2.时长数字,参数3.时长数字的单位时间,TimeUnit的常量指定
service.scheduleAtFixedRate()
周期性执行任务
参数1.任务,参数2.延迟时长数字(第一次执行在什么时间以后),参数3.周期时长数字(每隔多久执行一次)
参数4.时长数字的单位
Lambda表达式(函数式编程思想)
面向对象:创建对象调用方法解决问题