一、多线程(进程和线程)
1、①什么是进程:程序是静止的,只有真正运行时的程序才被称为进程。列:QQ音乐,QQ,迅雷,浏览器等。
②单核CPU在任何时间点上,只能运行一个进程;宏观并行、微观串行。
③有一个操作系统概念:时间片轮回
2、什么是线程:
线程进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务。线程的执行时抢占式,多个线程在同一个进程中可以并发执行,其实就是CPU快速的在不同的线程之间切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行。
3、线程与进程的关系
①一个程序运行后至少有一个进程
②一个进程可以包含多个线程,但是至少需要有一个进程,否则这个进程是没有意义的。
③进程间不能共享资源,单线程之间可以。
④系统创建进程需要为该进程重新分配系统资源,而创建线程则容易的多,因此使用线程实现多任务 并发比多进程的效率高
4、线程的组成:
①CPU时间片段:操作系统会为每一个线程分配执行时间
②运行数据:
堆空间:存储线程需要使用的对象,多个线程可以分享堆中的对象
栈空间:存储程序需要使用的局部变量,每个线程都拥有独立的栈
5、创建新执行线程有两种的方式:
①将类声明为Thread的子类。该子类应重写Thread类的run方法。继承Thread类
②声明实现Runnable 接口的类。该类然后实现run方法。然后可以分配该类的实例
6、start()方法
①在start()方法之后才开始真正开始交替执行
②交替执行,在各个计算机中是不一样的,甚至在同一台机器中不同时也是有不同的执行交替书序的,但是交替是必须的。
③start开启了一个子线程,他会与主线程main抢占CPU资源。
④开启了一个子线程的方法叫做start(),如果直接调用run(),那么该run()方法会被当成一个普通方法调用,最终结果不会出现交替执行
7、两种实现多线程的方式的对比:
① java是单继承类,一旦一个类继承了thread类之后,就不能再去继承第二个类,所以第一种方式显得不够灵活
② 实现Runnable接口的方式,用Runnable 对象作为参数来创建新的线程对象才能够让该线程对象启动后起到多线程的作用,可以实现很好的资源共享,但是代码稍微繁琐。
③第二种实现Runnable接口的方式,特别强调,Runnable对象自身不具备多线程的能力,只有该对象进入Thread的构造器中才可以。没有start()方法。
8、run()方法与start()方法对比 :
①run()方法可以被调用,但是起不到多线程作用。run方法中的代码与main线程中的代码不会出现交替执行
②只有调用start()方法才会真正开启一个子线程
9、合并线程:类名(“线程名称”)。用类对象
在执行原来线程的过程中,如果遇到了合并线程,则优先执行合并进来的线程,执行完合并进来的线程后,再回到原来的任务中,继续执行原来的线程
特点:
①线程合并,当前线程一定释放CPU时间片;CPU会将时间片分给要Join的线程
②哪个线程需要合并就在当前线程中添加要合并的线程。
③join之前,一定要将线程处于准备状态start。
二、线程的常用方法
1、设置线程名称:
①调用setName方法:
thread.setName("线程1");
②构造方法中设置:
Thread thread = new Thread(eat,"吃货1");
2、线程休眠:
①使得当前正在执行的线程休眠一段时间,释放时间片,导致线程进入阻塞阶段
sleep(long second)
try{
Thread.sleep(200);//单位毫秒
}catch{
}
3、设置线程优先级
①可以通过设置优先级来改变线程抢到时间片的概率,优先级高的线程获得较多的执行机会
priority
英 /praɪˈɒrəti/:n. 优先;优先权;
thread1.setPriority(1);最低 或者是这样写:thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(5);默认 或者是这样写:thread1.setPriority(Thread.NORM_PRIORITY)
thread3.setPriority(10);最高 或者是这样写:thread1.setPriority(Thread.MAX_PRIORITY);
②这个方法的设置一定要在start()方法之前,线程的优先级低并不意味着抢不到时间片,只是抢到的时间片的概率比较低而已。
4、sleep()和start()
5、后台线程:
隐藏起来一直默默运行的线程,直到进程结束,又被称为守护线程或者精灵线程,JVM的垃圾回收线程就是典型的后台线程。
:Daemon:英 /ˈdiːmən/ n. 守护进程;后台程序。
:使用setDaemon(true)将该线程设置为守护线程。
:线程守护的意思:主线程结束后,守护线程可以不必执行结束,可以提前终止。
:isDaemon(){
return daemon;
} 方法来判断给定线程是否为守护线程。
:thread.setDaemon(true);true不可少。
特点:
如果所有的前台线程都死亡,后台线程会自动死亡,必须要在start直线执行。
6、线程让步 yield:
可以让当前正在执行的线程暂停,但他不会阻塞该线程,他只是将该线程转入就绪状态,完全可能出现的情况是:当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
yield 英 /jiːld/:停止,给。。。。让路
Thread.yield();
特点:
实际上,当某个线程调用了yield方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会。
7、线程生命周期:
①:新建状态、可运行状态、阻塞状态、锁定状态、死亡状态。
②:New(新生)新建状态:线程被实例化,但是还没有开始运行;Therad t = new Therad()
:Runnable(就绪)可运行状态:并没有获取CPU资源,所以只能是可运行,不是运行。t.start();
:Running (运行状态):抢到时间片,CPU开始处理这个线程中的任务;
:Blocked(阻塞):线程在执行过程中遇到特殊的情况,使得其他线程就可以获得执行的机会,被
阻塞的线程会等待合适的时机。
:重新进入就绪状态
:Dead(死亡):线程终止。
:run方法执行完成,线程正常结束【正常的死亡】
:直接调用该线程的stop()方法强制终止这个线程。
三、临界资源
单列模式:程序运行中只有一个对象。
1、锁的实现方式共三种:
synchronized:英 /'sɪŋkrənaɪzd/ adj. 同步的;同步化的
①同步代码块:
synchronized(临界资源对象//任意的非空对象){//对临界资源对象加锁。
//代码(原子操作)
}
注:
每个对象都有一个互斥锁标记,用来分配给线程的。
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块。
线程退出同步代码块时,会释放相应的互斥锁标记。
②同步方法:
synchronized 返回值类型 方法名称(形参列表0){//当前对象(this)加锁
//代码(原子操作)
}//同步方法的锁是this,当前对象。
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
线程退出同步方法时,会释放相应的互斥锁标记。
③静态同步方法:
static synchronized 返回值类型 方法名称(形参列表0){//当前对象(this)加锁
//代码(原子操作)
}//静态同步方法的锁是当前的类对象。自身类的对象。
2、锁(同步)规则:
注意:
- 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。
- 如调用布包含同步代码块的方法,或普通方法时,则不需要锁标记,可以直接调用。
已知JDK中线程安全的类:
- StringBuffer
- Vector
- Hashtable
- 以上类中的公开方法,均为synchornized修饰的同步方法。
二、临界资源产生的原因:
有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走
了,临界资源问题就产生了。
三、解决临界资源问题:
一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要
访问这个资源,就得在“锁”外面等待。
四、ReentrantLock类实现同步: - 通过显示定义同步锁对象来实现同步,同步锁提供了比synchornized代码块更为广泛的锁
定操作。含Lock及Unlock方法。
注意:
- 最好将unlock的操作放到finally块中。
- 通过使用ReentrantLock这类来进行锁的操作,他实现了Lock接口,使用
ReentrantLock可以显示地加锁,释放锁。 - lock()和unlock() 都是成对出现的。unlock()一般写在finally中。
五、死锁:
1、死锁产生的原因: - 每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得
所有需要的资源之前都不会放弃已经拥有的资源。 - 当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
六、锁机制;
1、synchronized关键字
①同步代码块,锁为任意的非空对象,注意不能放基本类型的数据
②同步方法,锁为this
③静态同步方法,锁为类.class对象
2、ReentrantLock RPI
①、lock
②、unlock