1.15 java学习之多线程
多线程
1.1线程的基本概念
进程(process):操作系统中运行的程序就是进程,比如各种软件
在每个软件或者进程中,有多个线程,共同执行一个进程。
程序:是指令和数据的有序集合,是静态的概念。
进程是执行程序的一次执行过程,是动态过程,是系统资源分配的单位。
线程(Thread):是CPU调度和执行的单位。
注意:很多的多线程是模拟出来的,真正的多线程是多个CPU,即多核。比如服务器。
如果是模拟出来的,即在一个CPU的情况下,同一个时间,cpu只能执行一个代码 ,因为切换的很快,所以有同时执行的错觉。
线程是独立的执行路径。
在程序中即使没有自己创建线程,后台也会有多个线程,比如主线程,gc线程。
main():是程序的主入口,用于执行整个程序。
同一个进程如果有多个线程,线程的运行是靠CPU调度的,与操作系统相关,不能人为干预。
同一份资源存在资源抢夺的问题,需要并发控制。
线程会带来额外的开销,并发控制开销。
每个线程在自己的内存交互,内存不当会造成数据不一致。
1.2线程的创建
Thread类
- 自定义线程类继承Thread
- 重写run(),在里面执行线程方法体
- 创建***线程类***对象来调用start()方法启用线程
多个线程是同时执行,开启之后不一定立即开启,需要CPU调用
实现Runnable接口
-
实现Runnable接口
-
实现Run()方法,编写执行体
-
创建实现Runnable接口的实现类对象,在创建一个包含(Runnable接口的实现类对象)线程(Thread)类对象来调用start()方法启用线程。
// new Thread (Threadtest).start()
方法: new Thread (实现类对象,名字).start()
推荐使用Runnable,因为java中继承有局限性。方便一个对象被多个线程使用。
多个线程操作一个资源,线程不安全 ,数据紊乱
实现Callable接口
- 实现Callable ,需要返回值类型
- 重写call 方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFieldThreadPool(1);
- 提交执行:futureresult1 = ser.submit(t1);
- 获取结果:boolean r1 = result1.get();
- 关闭服务:ser.shutdownNow();
可以提供返回值
可以用异常
静态代理
可以使用接口声明对象,但是不能直接实例化。可以用其实现类去实现对象,(多态)
静态代理模式:
- 真实对象和代理对象都是实现统一接口
- 代理对象要代理真实对象
好处:代理对象可以做很多真实对象做不了的事情
真实对象专注自己的事情
这个和 new Thread (Threadtest).start() 是一致的
代理对象 真实对象 具体方法:其中都实现了Runnable接口
Lambda表达式
为什么使用Lambda?
- 避免匿名内部类定义过多
- 代码更简洁
- 只留下核心逻辑
函数式接口:任何接口,如果只有唯一一个抽象方法,就是函数是接口。
对于函数式接口,我们可以通过Lambda表达式来创建该接口对象。
推到过程:
-
首先定义一个接口:interface Ilike{
void lambda()
}
-
外部实现类:class Like implements Ilike{
public void lambda(){
System.ou.print(“外部类”)}
}
Ilike like = new Like();
like.lambda();
-
静态内部类,放在类里:static class public Like2 implements Ilike{
public void lambda(){
System.ou.print(“静态内部类”)}
}
Ilike like = new Like2();
like.lambda();
-
局部内部类,放在方法里: class public Like3 implements Ilike{
public void lambda(){
System.ou.print(局部内部类”)}
}
Ilike like = new Like3();
like.lambda();
-
匿名内部类:没有类的名称,必须借助接口或者父类:
like =new Ilike(){
public void lambda(){
System.ou.print(“匿名内部类”)}
}
like.lambda()
-
用lambda简化:
like =()->{
System.ou.print(“lambda表达式”)
}
like.lambda
-
有参数的表达式可以去掉参数类型:int a -》 a
-
可以去掉括号,如果只有一行代码,可以去掉花括号。
like = ()->System.ou.print(“lambda表达式”)
1.3线程状态
线程的几种状态
新建状态(New):
用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
就绪状态(Runnable):
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
运行状态(Running):
处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。
阻塞状态(Blocked):
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。
阻塞状态可分为以下3种:
位于对象等待池中的阻塞状态(Blocked in object’s wait pool):
当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。
位于对象锁池中的阻塞状态(Blocked in object’s lock pool):
当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。
其他阻塞状态(Otherwise Blocked):
当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态。
死亡状态(Dead):
当线程退出run()方法时,就进入死亡状态,该线程结束生命周期。
停止线程:
-
不建议JDK提供的stop()和destroy()方法
-
推荐线程自己停下来,利用次数,不建议死循环。
-
建议使用一个标识位进行终止变量,当flag = false;,停止线程
-
设置一个标识位
private boollean flag= true;
void run (){
while(flag){
system…out.print(“shuhsfhdi”)}
}
-
设置一个公开的方法停止线程,转化标识为。
public void stop(){
this.flag = false}
-
在主线程中,调用stop()方法,让线程停止。
线程休眠
-
用法:Thread.sleep(时间)
sleep ():指当前线程阻塞的毫秒数;
sleep():有异常InterruptedExecption需要抛出
sleep到达时间后进入就绪状态
可以模拟网络延时和倒计时
每一个对象都有一把锁,但是sleep不会释放锁。
模拟网络延时:可以放大发生的概率
模拟倒计时:
int num= 10;
while(true){
Thread.sleep(1000);
num–;
if (num<=0){
break;
} }
获取当前系统时间:
Date st = new Date(System.currentTimeMillis());//获取当前时间
while(true)
{//循环输出当前系统时间
Thread.sleep(1000)
system.out.print (new SimpleDateFormat(“HH:mm:ss”).format(st));打印出来时间
st = new Date(System.currentTimeMillis())////更新时间
}
线程礼让(yield)
用法:Thread.yield()
让当前正在执行的线程暂停,但不阻塞;
将线程从运行状态转化为就绪状态
让cpu重新调度,礼让不一定成功
线程强制执行
Join合并线程,待此线程执行完毕,在执行其他线程,其他线程进入阻塞状态。
线程的优先级
PRIORITY:优先级
java提供一个线程调度来监控程序中进入就绪状态的所有线程,线程调度按照优先级决定哪个线程先执行。
但是不一定,只是优先级大权重大,决定权在CPU中
设置优先级:1~10;
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY =10
Thread.NORM_PRIORITY =5
使用getPriority,setPriority(int a)获取设置优先级
主线程main 一般优先级是5
一定要先设置优先级在start();
守护(daemon)线程
Thread.daemon:默认是false表示用户线程
守护线程会在虚拟机结束后在运行一段时间,在结束。
线程分为用户线程和守护线程
虚拟机必须保证用户线程执行完毕:main 线程是用户线程,gc是守护线程
虚拟机不用等待守护线程执行完毕
1.4线程同步
含义:多个线程操作一个资源
并发:同一个对象被多个线程同时操作
线程同步就是一种等待机制,多个对象需要某一个对象的需要进入该对象等待池,形成队列,等待当前线程完毕。
为保证安全:加入锁机制 synchronized,增加安全性也会带来性能问题。
对于属性可以根据private,对于方法根据关键字:synchronized关键字:
有两种用法:synchronized 方法和synchronized(obj){}块;
每个synchronized必须获得对象锁才能对对象进行访问,否则线程会堵塞,方法一旦执行,就会独占锁,后面堵塞的线程要等待,会影响效率。
对于synchronized锁的是对象本身就是this。
对于synchronized (obj){}块锁的是里面的obj,obj可以是任何对象(需要增删改查)也称之为同步监视器。就是把要锁定的内容放入代码块里面。
死锁
某个同步代码块同时拥有两个以上的对象锁,,会发生死锁的问题。发生相互线程等待对方释放资源,都停止执行。
必要条件:
- 互斥:一个资源只能被一个进程使用
- 请求与保持条件:一个个进程引擎求资源而阻塞时,对已获得的资源保持不放
- 不剥夺:进程已获得资源,在未使用的情况下,不能强行剥夺
- 循环:若干个进程之间形成头尾相接的资源关系
破坏一个条件就是避免死锁;
lock
显示定义同步锁
,使用lock对象充当
与synchronized一样
一般用可重入锁搭配:private final ReentranLock lock =new ReentranLock ();
lock.lock();//加锁:一般放在try语句中的try中
lock.unlock();解锁:一般放在try语句中的finally中
lock只有代码块,synchronized有方法和代码块
lock有开关,synchronized隐士锁,除了作用域自动释放
lock性能好
线程通信
object类提供几个线程之间通信的方法:wait(),:表示线程一直在等待,知道其他线程通知,与sleep不同,会释放锁
notify():唤醒处于等待的线程
notifyALll():唤醒同一对象上所有调用wait()的方法,优先级别高的线程优先调度。
以上均是在同步方法或者同步代码块中使用。
解决线程通信消费者生产者模型的两种方法:
- 缓冲区
- 标识位
线程池
优点:
提高速度,降低资源消耗,便于线程管理:
-
corePoolSize:线程池大小
-
maximumPoolSize:最大线程数
-
keepAliveTime:线程没有任务时,多久会终止
过程:
-
创建线程服务,参数为池子大小:ExectorService se = Exectors.newFieldThreadPool();
-
se.execute(new Thread());
-
se.execute(new Thread());
se.execute(new Thread());
se.execute(new Thread());
-
关闭连接se.shutdown()
其中ExectuoService是真正的线程池接口,创建之后如果有返回值用提交执行:futureresult1 = ser.submit(t1);
没有返回值用se.execute(new Thread());