1.进程|线程
进程是系统分配和管理的资源的基本单位。
进程就是在系统中运行的程序或者任务,比如在任务管理器中,你看到的每一个正在运行的exe程序就可以理解成
一个进程。
--------------------------------
在IDE中运行*.class文件相当于在操作系统中启动一个JVM虚拟机进程,在该虚拟机进程加载*.class文件并运
行。
--------------------------------
线程是在进程中独立运行的子任务。
进程和线程总结:
1.进程虽然是相互独立的,但它们可以互相通信,通用的方式是使用Socket或HTTP协议。
2.进程拥有共享的系统资源,比如内存、网络端口、供其内部线程使用。
3.进程较重、因为创建进程需要操作系统分配资源,会占用内存。
4.线程存在于进程中,是进程的一个子集,先有进程、后有线程。
5.虽然线程更轻,但线程上下文切换的时间成本非常高。
2.使用多线程的优点
充分利用CPU的空闲时间来处理其他任务,由于任务的切换速度很快,给使用者的感受就是这些任务同时运行,所
以使用多线程技术可以在同一时间内做更多不同种类的任务。
---------------------------------
补充:
1.单任务的特点就是排队执行,也就是同步。在同一时间只能执行一个任务,CPU利用率大幅降低,这就是单任务环境的缺点。
2.多任务的特点是在同一时间内可以执行多个任务,使用多线程就是在使用异步。
3.在什么场景使用多线程
1.阻塞:系统中出现了阻塞现象,则可以根据实际情况来使用多线程提高运行效率。
2.依赖:业务A阻塞时,业务B不依赖业务A的执行结果时,可以用多线程提高运行效率。
4.继承Thread类
Java的JDK开发包自带了对多线程技术的支持,可以方便地进行多线程编程。实现多线程编程的方式主要有两种:
一种是继承Thread类,另外一种是实现Runnable接口。这两种方式创建线程的功能都是一样的,没有本质的区
别。只不过继承的局限就是单继承,实现可以多实现。
public class ThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
System.out.println("运行结束!");
}
}
------------------------------------
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("MyThread");
}
}
------------------------------------
输出:
运行结束!
MyThread
Process finished with exit code 0
代码使用start()方法来启动一个线程,线程启动后会自动调用线程对象中的run()方法,run()方法里面的代码
就是线程对象要执行的任务,是线程执行任务的入口。
注意:
使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序无关。因为线程是一个子任务,CPU是以不确定的
方式,或者随机的时间来调用线程中的run()方法,所以先输出"运行结束!"和先输出"MyThread"具有不确定性。
5.常见的3个命令分析线程的信息
如果想要查看线程的状态与信息,则可采用3种常见的命令,分别是jsp+jstack.exe、jms.exe和jvisualvm.exe
它们在jdk\bin文件夹中。
1.jps+jstack.exe命令,在cmd中输入jps命令查看Java进程,然后使用jstack命令查看该进程下线程的状态:
C:\Program Files\Java\jdk1.8.0_201\bin>jps --直接回车 便可以查询进程对应的PID
7088 Launcher
7712 ThreadTest2
4276 Jps
7096
11564 RemoteMavenServer36
C:\Program Files\Java\jdk1.8.0_201\bin>jstack -l 7712 --回车就可以看到线程的状态
2.使用jmc.exe命令
在jdk/bin中双击jmc.exe命令
注意:如果在jmc.exe中看不到JVM进程,说明jmc.exe和IDE中使用的JDK版本不一致。
3.使用jvisualvm.exe命令,双击jvisualvm.exe命令。
6.执行start()的顺序不代表执行run()的顺序
执行start()方法的顺序不代表线程启动的顺序,不代表run()方法执行顺序,执行run()是随机的。
public class ThreadTest3 extends Thread {
private int i = 0;
public ThreadTest3(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---->i" + i);
}
}
public class ThreadTest {
public static void main(String[] args) {
ThreadTest3 t1 = new ThreadTest3(1);
ThreadTest3 t2 = new ThreadTest3(2);
ThreadTest3 t3 = new ThreadTest3(3);
ThreadTest3 t4 = new ThreadTest3(4);
ThreadTest3 t5 = new ThreadTest3(5);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
-------------------------------------------
Thread-2---->i3
Thread-3---->i4
Thread-1---->i2
Thread-4---->i5
Thread-0---->i1
Process finished with exit code 0
7.public Thread(Runable target)中的target参数
JVM直接调用的是Thread.java类中的run()方法。该方法源代码如下:
@Override
public void run() {
if (target != null) {
target.run();
}
}
/* What will be run. */
private Runnable target;
变量target是声明的Runnable对象,结合if判断就可以执行Runnable 对象的run()方法了。变量target是在
init()方法中进行赋值初始化的。init方法又在构造器中被调用了。
-------------构造----------------------
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
------------init-------------------------
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
8.线程安全问题
出现线程安全情况:多个线程操作同一个对象的同一个实例变量。
public class MyThread extends Thread{
private Integer count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("线程名称:"+MyThread.currentThread().getName()+",count值="+count);
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
//多个线程操作同一个对象的同一个实例属性,会出现线程安全问题
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
Thread thread3 = new Thread(myThread);
Thread thread4 = new Thread(myThread);
Thread thread5 = new Thread(myThread);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
输出结果:
线程名称:Thread-3,count值=3
线程名称:Thread-1,count值=2
线程名称:Thread-4,count值=2
线程名称:Thread-2,count值=1
线程名称:Thread-5,count值=0
Process finished with exit code 0
count--的操作要分解成3步,执行这3步骤的过程会被其他线程所打断,因为不是"原子性"的操作。
(1):取得原有count值
(2):计算count-1
(3):对count进行重新赋值
在这3个步骤中,如果有多个线程同时访问的话,大概率会出现线程安全问题。
9.synchronized关键字
public class MyThread extends Thread{
private Integer count = 5;
@Override
public synchronized void run() {
super.run();
count--;
System.out.println("线程名称:"+MyThread.currentThread().getName()+",count值="+count);
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
//多个线程操作同一个对象的同一个实例属性,会出现线程安全问题
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
Thread thread3 = new Thread(myThread);
Thread thread4 = new Thread(myThread);
Thread thread5 = new Thread(myThread);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
输出结果:
线程名称:Thread-1,count值=4
线程名称:Thread-5,count值=3
线程名称:Thread-4,count值=2
线程名称:Thread-3,count值=1
线程名称:Thread-2,count值=0
Process finished with exit code 0
加上synchronized关键字,来解决线程安全问题。一个线程在调用run方法时,会判断run方法有没有上锁,
如果上锁,说明其他线程正在调用run方法,必须等其他线程调用结束后,才可以执行run方法。虽然count--
操作,仍被划分3个步骤,但是在执行这3个步骤时并没有被打断,呈“原子性”,所以运行结果是正确的。
使用synchronized关键字修饰的方法称为“同步方法”,可用来对方法内部的全部代码进行加锁,而加锁的这
段代码称为“互斥区”或“临界区”。
当一个线程想要执行同步方法里面的代码时,它会首先尝试去拿这把锁,如果能拿到,那么该线程就会执行
synchronized里面的代码。如果不能拿到,那么这个线程就会不断尝试去拿这把锁,直到拿到为止。
10.isAlive()
isAlive()的作用是测试线程是否处于活动状态。即线程已经启动尚未终止。如果线程处于正在运行或者准备开
始运行的状态,就认为线程是“存活”的。
public class MyThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("线程名称:"+this.currentThread().getName()+",是否存活:"+this.isAlive());
}
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
System.out.println("线程名称:"+myThread.getName()+",是否存活:"+myThread.isAlive());
myThread.start();
Thread.sleep(1000L);
System.out.println("线程名称:"+myThread.getName()+",是否存活:"+myThread.isAlive());
}
}
线程名称:Thread-0,是否存活:false
线程名称:Thread-0,是否存活:true
线程名称:Thread-0,是否存活:false
Process finished with exit code 0