进程和线程的概念(面试)
1、程序和进程是什么?
- 程序是从上往下执行的,单线程运行
- 程序:是对数据描述和操作的代码集合。比如我们说的暴风影音、Office中的word、QQ、微信。程序是一个静态的概念。程序运行后就是进程
- 进程:是运行中的程序,或者是程序的一次动态执行,它对应从代码的加载、执行、执行完毕。它是一个动态的概念,它有生命周期。
一句话概括:系统运行的一个或者多个程序,叫做进程。
特点:
1)进程是系统运行程序的基本单元。
2)每个进程有自己的独立的一块内存空间、一组系统资源。
3)每一个进程的内部数据和状态是完全独立的。
2、程序在CPU中运行的状态
- cpu同一时刻只能被一个进程使用
- 由于cpu在各个程序之间切换的时间片非常短,用户就会觉得cpu同时被多个程序在使用
- 时间片:它是操作系统调度cpu的单位。比如: 0.00001毫秒时间片被迅雷下载使用
3、线程是什么?它和进程之间有什么关系?
每个正在系统上运行的程序就是一个进程。每个进程可以包含多个线程。线程是一组指令的集合,它可以在程序中独立的运行。
我们可以把线程理解程代码运行的上下文。线程是轻量级的进程,它负责在单个程序中热行任务。通常它的调度是由操作系统负责和执行。
总结:线程是运行在进程中的,一个进程中可以有很多线程,这些线程可以相互影响,也可以独立运行,叫做多线程。
4、为什么叫做多线程? - 进程之间没有关联(不共享内存),线程之间有关务(共享内存)在同一个程序中
- 线程的使用效率比进程要高
- JAVA提供了对多线程的支持,编程的效率高
5、线程的分类以及特点:
-
主线程:main()方法
main方法会启动一个主线程。其他的线程都是这个线程的子线程 -
守护线程:也称“服务线程”,在没有线程可服务时会自动离开,优先级:守护线程的优先级比较低,永无为系统中的其他对象和线程提供服务。
例如:
垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
如果把用户线程用setDaemon(true)方法进行设置,那么就变成守护线程。当进程不存在或者主线程停止,守护线程会被停止。 -
用户线程:是用户自己定义的线程,主线程停止的时候,用户线程不会停止。
6、多线程的优势
就是可以提高程序执行的效率。实际生活中迅雷软件去下载电影。它实际上是采用多线程的,多线程不能提高程字下载的速度,只是提高程序的执行效率。
7、程序中的同步和异步
线程的创建
线程是程序中执行的线程。 Java虚拟机允许应用程序同时执行多个执行线程。
每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守护程序。 当在某个线程中运行的代码创建一个新的Thread对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护线程。
Thread()
分配一个新的 Thread对象。
Thread(Runnable target)
分配一个新的 Thread对象。
Thread(Runnable target, String name)
分配一个新的 Thread对象。
Thread(String name)
分配一个新的 Thread对象。
2)主要由如下几种创建方式
继承方式、实现接口方式、匿名内部类、线程池方式
- 继承方式
package com.m.demo;
public class Test extends Thread {
private int count=0;
// 重写run方法
// run()方法:表示线程处于运行状态
// 一般我们把需要运行的核心方法写在run()方法中,执行该方法的内容
public void run() {
while (count<=100) {
//显示线程的名字(先获取线程对象 然后获取线程的名字)
try {
Thread.sleep(200);//单位是毫秒,表示让线程休眠,休眠后,处于堵塞状态,时间到了 自动唤醒,处于就绪状态
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+count);
count++;
}
}
}
class demo{
public static void main(String[] args) {
Test t=new Test();//创建线程对象
Test t2=new Test();
t.start();//让线程处于就绪状态
t2.start();
}
}
System.out.println("开始创建");
Test t=new Test();//创建线程对象
System.out.println("结束创建");
t.start();//让线程处于就绪状态
System.out.println("结束");
//多线程的执行,就是争夺cpu使用权,谁争夺到了,谁执行
主线程和t线程争夺cpu使用权
原因:当t.start()的时候,线程启动了,但没来得及执行run(),执行权就被主线程争夺走了,
主线程执行后面的打印语句,主线程执行完毕后,t继续执行
1)交替执行,没有规则,具体执行哪个线程是由cpu调度的,人为不能干涉cpu的调度。
2)线程会跟其他的线程抢夺CPU的资源,谁抢到了,谁调用,结果都是随机的。
- 实现接口方式
package com.m.demo;
public class Mythread implements Runnable {
private int count=0;
@Override
public void run() {
while (count<=100) {
//显示线程的名字(先获取线程对象 然后获取线程的名字)
try {
Thread.sleep(200);//单位是毫秒,表示让线程休眠,休眠后,处于堵塞状态,时间到了 自动唤醒,处于就绪状态
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+count);
count++;
}
}
}
class demo1{
public static void main(String[] args) {
System.out.println("开始创建");
Mythread t=new Mythread();
Thread thread=new Thread(t,"接口创建方式");//可以指定线程的名字
System.out.println("结束创建");
thread.start();
System.out.println("结束");
}
}
- 匿名内部类
class demo1{
public static void main(String[] args) {
System.out.println("开始创建");
//Mythread t=new Mythread();
Thread thread=new Thread(new Runnable() {
private int count=0;
@Override
public void run() {
while (count<=100) {
//显示线程的名字(先获取线程对象 然后获取线程的名字)
try {
Thread.sleep(200);//单位是毫秒,表示让线程休眠,休眠后,处于堵塞状态,时间到了 自动唤醒,处于就绪状态
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+count);
count++;
}
}
});//使用匿名内部类
System.out.println("结束创建");
thread.start();
System.out.println("结束");
}
}
- 线程池方式
多线程状态 (面试)
线程是有生命周期的,它有5个状态:
-
新建状态
当采用new关键字创建一个线程对象的的时候,此时线程还没有运行。比如:Thread t=new Thread(newMyThread20): -
就绪状态
当调用了start(),系统为该线程分配除了cpu之外所有的资源。此时该线程具备运行的机会,此时处于就绪状态。此时线程是没有运行的。 -
运行状态
处于就绪状态的线程,获得了cpu的使用资格,才开始运行。调用了run() -
阻塞状态
一个正在运行的线程由于某种原因丧失了cpu的使用权,不能继续运行,就由运行状态进入阻塞状态。处于阻塞状态的线程在得到某个特点的事件后会转入可运行状态。
A、线程调用了sleep0方法进入睡眠状态。
B、线程执行到一个I/O操作,如果IO还没有完成,线程就被阻塞。
C、线程视图得到一个锁,而该锁正被其他的线程所持有。
D、执行了suspend()方法,线程被挂起。但是容易导致死锁。被jdk列为过时,基本不用。 -
死亡状态
肯定就是线程的run()方法执行完毕,自然死亡。可能是一个未捕获的异常终止了线程的run()的执行。
线程调度(面试)
void join()
等待这个线程死亡。
void run()
如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
void setName(String name)
将此线程的名称更改为等于参数 name 。
void setPriority(int newPriority)
更改此线程的优先级。
static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
void start()
导致此线程开始执行; Java虚拟机调用此线程的run方法。
static void yield()
对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。
- 线程调度机制
同一时刻可能有多个线程处于就绪状态。线程调度器负责线程的排队和CPU在线程之间的分配。线程调度器按照一定的调度的算法进行词度,当线程调度器选中了某个线程后该线程就可以进入运行状态。
线程的调度它是抢占式的,如果当前的线程在执行的过程中来了一个优先级更高的线程进入了就绪状态,则该优先级高的线程会被调度执行。 - join()方法
在哪个线程内调用该方法就会让当前的线程暂停执行,就是获得了CPU也会让出控制权,等待调用该方法的线程执行完毕后,再执行本线程。
需求:创建一个线程,子线程执行完毕后,主线程才能执行。在子线程中输出0-9的数字:主线程输出0-19的数字。分析:
1、在主线程输出0-19的数字。main()方法中
2、白定义线程类,在了线程中输出0-9的数字
3、在main(方法中,调用join(方法。
package com.m.demo;
public class ThreadRun implements Runnable {
@Override
public void run() {
for(int i=0;i<10;i++) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
class demo3{
public static void main(String[] args) {
Thread t=new Thread(new ThreadRun(),"自定义");
t.start();
// 在主线程调用join()让主线程让cpu的控制权,让子线程先执行
try {
t.join();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
for(int j=0;j<20;j++){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" "+j);
}
}
}
- yield()方法
暂停当前的线程,运行其他线程执行,但是该线程还是处于就绪状态,不转为阻塞状态可能下次的时候仍然获得CPU的控制权。并不能保证线程让步的目的。
详细说明: - Java线程中的Thread.yield( )为法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。
- yield()的作用是让步。它能让当前线程由运行状态"进入到就绪状态",从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到"运行状态""继续运行!
- 举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。
- 但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。
- sleep()方法
Thread.sleep()是Thread类的一个静态方法,使当前线程休眠,进入阻塞状态(暂停执行),如果线程在睡眠状态被中断,将会抛出lterruptedException中断异常。。
static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
在哪个线程调用sleep()哪个方法就阻塞
注意问题
- sleep()方法是Thread类的静态方法,如果调用线程对象.sleep()方珐并不是该线程就休眠,而是在哪一个线程里面执行了sleep()方法哪一个线程就休眠。
⒉线程睡眠到期自动苏醒,并返回到可运行状态(就绪),不是运行状态。然后等待线程的调度。
问题: sleep()和yield()方法的区别?
sleep()会转入阻塞状态,进行休眠,即使没有其他等待运行的线程也会等待指定的时间。而yield()方法暂停线程的执行,不会进行阻塞。如果没有其他等待的线程会马上恢复执行。它会和其他就绪的线程一起竞争CPU资源的使用。它可能被选中。 - setPriority()方法更改线程的优先级。
注意:所谓的优先级,并不是说线程一定会抢夺到CPu的控制权,而是有这个优势而已,只能通过大量的数据才能显示出来优势。
1.优先级表示重要程度或者紧急程度.但是能不能抢到资源也是不一定.
2.分配优先级:反映线程的重要或紧急程度
线程的优先级用1~10表示,1的优先级最低,10的优先级最高,默认值是5
static int MAX_PRIORITY
线程可以拥有的最大优先级。 10
static int MIN_PRIORITY
线程可以拥有的最小优先级。 1
static int NORM_PRIORITY
分配给线程的默认优先级。 5
线程安全
线程安全问题就是多个线程同时对公共变量(对象)进行操作,所产生的问题
线程不安全 | 线程安全 |
---|---|
ArrayList | Vector |
HashMap | HashTable |
如果多个线程同时运行,而这些线程可能会运行相同的代码,在这段代码中要对公共的变量(对象)进行非读操作(写操作),会出现意想不到的情况,和预期的值不一样,这就是所谓的线程不安全。反之就是线程安全的。
- 多个线程对同一全局变量进行了修改操作,这样的问题,就是线程安全的问题
- 在run()方法运行过程中,可能随时会被其他线程抢夺执行权,这就会造成run()方法执行不完全,所以会出现线程安全的问题.
线程同步
1)什么是线程同步?
当线程出现安全问题的时候,java提供了线程的同步机制来解决上面的安全问题。
2)什么情况下,需要使用同步解决线程安全
当多线程操作同一个共享变量,并且对变量值进行修改。
3)如何解决线程安全问题?(如何实现线程的同步)
同步方法、同步代码块
思想:对可能出现线程安全的代码,用锁把它锁定,代码执行完毕后释放锁,让其他线程进来执行。这样可以解决安全问题。不能让多个线程同时去操作共享的变量,在一个线程执行完代码前,不让其他线程进入执行代码。
3)同步代码块
synchronized(锁对象){
//可能出现线程安全的代码
}
也可以在方法前直接加synchronized
同步代码块中的锁对象是任意的对象,但是多个线程,要使用同一把锁才能保证线程安全。
package com.m.demo2;
public class Ticket implements Runnable {
int count=0;
@Override
public void run() {
while(count<=100) {
synchronized (this) {
sale();
}
}
}
public void sale() {
if(count<100) {
count++;
System.out.println(Thread.currentThread().getName()+"卖了第"+count+"票");
}
}
}
package com.m.demo2;
public class Test {
public static void main(String[] args) {
Ticket t=new Ticket();
///三个线程执行
Thread t1=new Thread(t,"线1");
Thread t2=new Thread(t,"线2");
Thread t3=new Thread(t,"线3");
t1.start();
t2.start();
t3.start();
}
}