多线程(待补充)
一、多线程
1、程序:为了完成特定的任务用魔种语言编写的一种指令的集合,即指一段静态的静态对象。
2、进程:程序执行的一次过程,或者正在运行的一个程序是一个动态的过程:产生 -> 存在 -> 消亡 进程是资源分配的单位,徐通在运行是为每个进程分配不同的内存区域
3、进程:
(1)进程进一步细化为线程,是程序内部执行的一条路径
(2)若一个进程同时执行多个线程,就是执行多线程
(3)线程作为一个执行和调度的单位,每个线程拥有独立的运行栈和计数器,线程的切换开销比较小,方法区和堆内存被该进程的所有线程共享。
4、并行:多个CPU同时执行多个任务
5、并发:一个CPU(时间片)同时执行多个任务,多个任务之间切换。实际应用场景:秒杀
6、使用多线程的优点:提高应用程序的响应效率,对于图形化界面的意义重大,变相的提高了用户的体验,提高CPU的利用率,优化程序的结构,可以把复杂冗长的进程分为多个线程,提高CPU的利用率,优化程序的结构,可以把复杂冗长的进程分为多个线程,每个线程独立运行。降低耦合度,便于修改
7、使用多线程的时机:需要同时完成多个任务,文件的读写操作,网络的操作,搜索,需要后台运行的时候。
8、一个java程序运行至少需要3个线程,一个是main()主线程,gc()线程,异常处理线程,线程的创建和使用:4种方法(jdk1.5之前只有前两种)
二、实现多线程的方法
1、方法一:
(1)继承Thread类
(2)重写run方法
(3)创建子类对象
(4)调用对象的start()方法
2、方法二:
(1)实现Runnable接口
(2)实现run方法
(3)创建实现类对象放入Thread对象种
(4)调用Thread对象的start()方法,静态代理中,实现Runable:
3、继承Thread类:线程代码是存放在Thread子类run方法;实现Runable:线程代码存在实现类的run方法中。
4、推荐使用实现的方式:
(1)可以避免单继承的局限性。
(2)多个线程共享同一接口实现类的对象,适合多个相同线程处理共享资源。
5、Thread类中的常用方法
(1)void start() 启动线程,并执行对象的run方法
(2)void run() 线程在被调度的时执行的方法。
(3)String getName() 返回线程的名字
(4)setName() 设置线程的名字
(5)static Thread currentThread() 返回当前线程对象
(6)static void yield() 线程让步(把执行权交给优先级更高的线程),会暂停当前正在执行的线程,把执行权交给相优先级、或者高优先级的线程。
交出CPU的执行权后,CPU依旧可能会分配给它执行权。
(7)join() 当前线程调用了join方法,那么当前线程会被阻塞,转而执行join所加进来的线程,直到加入进来的线程执行完毕才会继续指向当前线程
(8)static void sleep() 当前线程休眠指定的毫秒数。
(9)setPriority() 设置线程优先级,最小1、最大10、默认5。
(10)getPriority() 获取优先级,低优先级的线程获得调度的概率不为0,只是有点小
通过继承的方式常见线程,优先级等于父类
(11)在java中线程分为:用户线程和守护线程(通过jvm何时离开进行划分),守护线程是为了用户线程服务的。可以在strat之前调用线程的deamon(true)设置用户线程为守护线程
三、线程的生命周期
1、时间片;抢占式:高优先级的线程抢占CPU
2、生命周期:
(1)新建:当Tread类或者其子类被声明创建的时候,新生的线程对象处于新建状态
(2)就绪:新建线程调用了start方法,进入到线程队列,等待CPU的时间片,此时已经具备了运行条件,只是没有获得CPU执行权。
(3)运行:就绪的线程获得CPU的资源,并被调度,进入运行状态
(4)阻塞:将线程被动挂起,将CPU临时终止了线程执行,进入阻塞状态
(5)消亡:被人为的停止、执行完成或出现异常没有处理之后,线程消亡。
四、线程的同步
1、解决线程安全的问题
(1)使用synchronized同步代块
synchronized(Object 同步监听器){
操作共享的代码
}
(2)同步监听器:数可以使用任意对象充当,可以是Object类型
2、解决线程安全的方法:
(1)将共享
(3)可以通过反射获取到线程类的对象:线程类.class
3、同步代码块包裹的必须是操作供选个数据的代码,范围不能太大,否则体现不出多线程的功能;范围太小可能锁不住共享数据
4、解决线程安全问题的第二种方式:
使用同步方法:public synchronized void salaTicket(){};默认获取的同步监听器为this
5、不管使用哪种方法,都要保证同步监听器的唯一
6、一个线程类种的所有静态方法共用同一把锁(类名.class),非静态方法公共的同一把锁(this)。
7、释放锁操作:
(1)同步方法或者同步代码块中return 和 break释放锁操作;
(2)yeild()、sleep()不会释放锁;
(3)执行完同步方法或者同步代码也会释放锁
(4)当前进程同步代码块或者同步方法调用了wait()方法,当前线程阻塞,并且释放锁
(5)当前线程同步方法调用了supend()方法,将线程挂起,不会释放锁。(尽量不要使用supend()方法,与之相对应的还有一个resume()方法,用来放下挂起的状态)
8、单例模式
懒汉式的线程安全:懒汉式需要构造方法私有化
9、死锁:不同的线程分别占用了对方所需要的同步资源不放弃。都在等待对方先放弃同步资源,最终形成了死锁;出现死锁之后不会异常、不会出现提示,所有的线程都处于阻塞状态。
10、解决死锁:尽量避免同步嵌套;尽量减少同步资源的定义;
11、Lock显示锁:接口,和synchronized拥有箱体哦那个的并发性和内存语义。
Lock lock = ...//通常是ReentrantLock对象(是Lock的一个子类)
lock.lock();
try{
共享资源
}finally{
lock.unlock();
}
12、synchronized和Lock对比:
(1)Lock是显示锁:可以手动的开启和关闭锁;synchronized是隐式锁:出了作用域自动释放。
(2)Lock是代码块锁;synchronized有代码块锁和方法所。
(3)JVM中:使用Lock花费的调度线程时间较少,性能更好,扩展性较高。优先使用:Lock-->同步代码块-->同步方法
五、线程的通信
(1)基本思路:进程一种进入等待后,唤醒正在等待的进程二(通过wait()和notify()),同理,进程二进入等待后唤醒正在等待的进程一。
(2)wait():让当前进程挂起,并放弃CPU,同步资源并等待,使别的线程可以访问并修改共享资源,只有其他线程在获取锁之后,调用notify()或者notifyAll()该线程才能被唤醒,并重新排队。
(3)notify():唤醒正在等待同步资源的线程中优先级最高者。
(4)notifyAll():唤醒正在排队等待同步资源的所有线程。
(5)notify和wait只能用在同步方法中
六、线程池
1、非阻塞队列:当队满,入队,数据丢失;对空,出队,得到null
2、阻塞队列:当队满,入队,进行队列,直到出队操作执行之后再入队;出队,等待,当队中有元素之后再出队
LinkedBlockingDeque
3、线程池用的是阻塞队列
4、线程池:节省了线程创建和销毁的时间,线程池中的线程无需创建和销毁,可以直接拿来使用
5、ThreadPoolExecutor(核心线程数,最大线程数,时间,时间单位TimeUnit,阻塞队列)
6、线程池的分类
(1)可缓存:ExutorService es = Excutors.newCachedThreadPool();
(2)定长:ExutorService = Excutors.newFixedThreadPool(3) //核心线程数固定
(4)定时:ExutorService = Excutors.newScheduleThreadPool(3)
(5)单例:ExutorService = Excutors.newSingleThreadPool
七、Callable
1、步骤:
(1)实现Cakkable接口
(2)实现call方法
(3)借助FutureTask类
(4)启动线程(FutureTask是Runnable的实现类)
(5)FutureTask类对象的get()方法,方法回调取得call()的返回值
2、对比:相对于run方法 call方法是可以有返回值的,是可以抛异常的、支持泛型 但是需要借助FutureTask类,比如说获取方法返回值