1.1 进程和多线程的概念以及线程的优点
进程:是受操作系统管理的基本运行单元。(通过查看Windows任务管理器中的列表,可以把运行中的.exe理解为进程。)
线程:可以理解成是在进程中独立运行的子任务。(常见例子:QQ的传文件、听音乐、发送图片表情等功能都有相应的线程在后台默默地运行。)
优点:使用多线程也就是在使用异步,同一时间内运行更多不同种类的任务。
不要把代码的顺序当成线程执行的顺序,线程被调用的时机是随机的。
1.2 使用多线程
一个进程正在运行时至少会有1个线程在运行,这种轻快在Java中也是存在的。
比如调用public static void main()方法的线程就是这样的,而且它是由JVM创建的。
1.2.1 继承Thread类
public class Thread implements Runnable
Thread类实现了Runnable接口,它们之间具有多态关系。
线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。
如果多次调用start()方法,会出现异常IllegalThreadStateException。
线程调用的随机性
Thread.java类中的start()方法通知”线程规划器“此时线程已准备就绪,等待调用run()方法。
这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程,具有异步执行的结果。
执行start()方法的顺序不代表线程启动的顺序。
1.2.2 实现Runnable接口
为了支持多继承,可以用实现Runnable接口的方式。
Thread.java类也实现了Runnable接口。
也就意味着构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,这样做可以将Thread对象的run()方法交由其他的线程调用。
1.2.3 实例变量与线程安全
1 不共享数据
创建多个线程,每个线程都有各自的变量,自己修改自己变量的值。
2 共享数据
在某些JVM中,i–操作分成3步:
1 取得原有i值
2 计算i-1
3 对i进行赋值
在这3个步骤中,如果多个线程同时访问,那么一定会出现非线程安全问题。
非线程安全问题主要是指多个线程对同一个对象的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。
这就需要使多个线程之间进行同步,按照顺序排队的方式进行操作。
在run方法前加入synchronize关键字,使多个线程在执行run方法时,以排队的方式进行处理。
synchronize可以在任意对象以及方法上加锁,被上锁的这段代码称为“互斥区”或“临界区”。
package controller;
/**
* Author yrucrew
* Time 2018/7/9 0:34
*
* 非线程安全问题
*/
public class LoginServlet {
private static String usernameRef;
private static String passwordRef;
//加入synchronize关键字
//synchronize public static void doPost(String username, String password){
public static void doPost(String username, String password){
try {
usernameRef = username;
if (username.equals("a")){
Thread.sleep(5000);
}
passwordRef = password;
System.out.println("username="+usernameRef+" password="+password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ALogin extends Thread {
@Override
public void run() {
LoginServlet.doPost("a","aa");
}
}
public class BLogin extends Thread {
@Override
public void run() {
LoginServlet.doPost("b","bb");
}
}
public class Run {
public static void main(String[] args) {
ALogin a = new ALogin();
a.start();
BLogin b = new BLogin();
b.start();
}
}
- 非线程安全
- 排队进入方法
1.2.4 留意i–与System.out.println()的异常
println()方法内部是同步的
/**
* Prints a String and then terminate the line. This method behaves as
* though it invokes <code>{@link #print(String)}</code> and then
* <code>{@link #println()}</code>.
*
* @param x The <code>String</code> to be printed.
*/
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
i–操作发生在println()之前,所以有非线程安全问题发生。
package extthread;
/**
* Author yrucrew
* Time 2018/7/9 1:23
*
* 自定义线程
* 虽然println方法在内部是同步的,但i--的操作却是在进入println之前发生的,所以有发生非线程安全问题的概率
*/
public class MyThread extends Thread{
private int i = 5;
@Override
public void run() {
System.out.println("i="+(i--)+" threadName="+Thread.currentThread().getName());
}
}
1.3 currentThread()方法
此方法可返回代码段正在被哪个线程调用的信息。
package run;
/**
* Author yrucrew
* Time 2018/7/9 1:41
*/
public class MyThread extends Thread {
public MyThread(){
System.out.println("构造方法的打印:"+Thread.currentThread().getName());
}
@Override
public void run() {
System.out.println("run方法的打印:"+Thread.currentThread().getName());
}
}
构造函数是被main线程调用的。
当Thread构造方法传入的是个Thread对象时,只是调用了对象的方法。
public class CountOperate extends Thread {
public CountOperate() {
System.out.println("CountOperate---begin");
System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());
System.out.println("this.getName()=" + this.getName());
System.out.println("CountOperate---end");
System.out.println();
}
@Override
public void run() {
System.out.println("run---begin");
System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());
System.out.println("this.getName()=" + this.getName());
System.out.println("run---end");
}
}
public class Run {
public static void main(String[] args) {
CountOperate c = new CountOperate();
// c.start();
Thread t1 = new Thread(c);
t1.setName("A");
t1.start();
}
}
运行结果:
1.4 isAlive()方法
此方法功能是判断当前的线程是否处于活动状态。
另外,使用isAlive()方法时,如果将线程对象以构造参数的方式传递给Thread对象进行start()启动会出现与之前示例相同的差异。
package mythread;
/**
* Author yrucrew
* Time 2018/7/9 11:11
*/
public class CountOperate extends Thread {
public CountOperate() {
System.out.println("CountOperate---begin");
System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());
System.out.println("Thread.currentThread().isAlive()=" + Thread.currentThread().isAlive());
System.out.println("this.getName()=" + this.getName());
System.out.println("this.isAlive()=" + this.isAlive());
System.out.println("CountOperate---end");
System.out.println();
}
@Override
public void run() {
System.out.println();
System.out.println("run---begin");
System.out.println("Thread.currentThread().getName()=" + Thread.currentThread().getName());
System.out.println("Thread.currentThread().isAlive()=" + Thread.currentThread().isAlive());
System.out.println("this.getName()=" + this.getName());
System.out.println("this.isAlive()=" + this.isAlive());
System.out.println("run---end");
}
}
package test;
import mythread.CountOperate;
/**
* Author yrucrew
* Time 2018/7/9 11:14
*/
public class Run {
public static void main(String[] args) {
CountOperate c = new CountOperate();
Thread t1 = new Thread(c);
System.out.println("main begin t1 isAlive=" + t1.isAlive());
t1.setName("A");
t1.start();
System.out.println("main end t1 isAlive=" + t1.isAlive());
}
}
运行结果:
造成这种差异的原因来自于Thread.currentThread()和this的差异:
- c作为构造参数传入t1
- 使得t1使用c的run方法
- 所以正在在运行的线程是t1而不是c
- c并没有被启动
- 在c的run方法中调用this,其实还是在对c对象操作
其实就是传参和调用的问题,只不过有点绕…..
1.5 sleep()方法
此方法的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。
正在执行的线程是指this.currentThread()返回的线程
1.6 getId()方法
此方法的作用是取得线程的唯一标识。
小结
略为复杂的问题在与线程A构造时,若是传入了别的Thread对象(线程B),那么线程A会调用线程B的run方法。
所以在调用Thread.currentThread 和 this时,会出现不同的结果。
真正在运行的线程和被调用的run方法,分清楚,也就不复杂了。