Java学习心得之线程(一)

时间:2021-08-14 14:24:31

一、线程简介

       线程是一个程序里面不同的执行路径。

    每一个分支都叫做一个线程,main()叫做主分支,也叫主线程。

  进程只是一个静态的概念,机器上的一个.class文件,机器上的一个.exe文件,这个叫做一个进程。程序的执行过程都是这样的:首先把程序的代码放到内存的代码区里面,代码放到代码区后并没有马上开始执行,但这时候说明了一个进程准备开始,进程已经产生了,但还没有开始执行,这就是进程,所以进程其实是一个静态的概念,它本身就不能动。平常所说的进程的执行指的是进程里面主线程开始执行了,也就是main()方法开始执行了。进程是一个静态的概念,在我们机器里面实际上运行的都是线程。

  Windows操作系统是支持多线程的,它可以同时执行很多个线程,也支持多进程,因此Windows操作系统是支持多线程多进程的操作系统。LinuxUinux也是支持多线程和多进程的操作系统。DOS就不是支持多线程和多进程了,它只支持单进程,在同一个时间点只能有一个进程在执行,这就叫单线程

  CPU难道真的很神通广大,能够同时执行那么多程序吗?不是的,CPU的执行是这样的:CPU的速度很快,一秒钟可以算好几亿次,因此CPU把自己的时间分成一个个小时间片,我这个时间片执行你一会,下一个时间片执行他一会,再下一个时间片又执行其他人一会,虽然有几十个线程,但一样可以在很短的时间内把他们通通都执行一遍,但对我们人来说,CPU的执行速度太快了,因此看起来就像是在同时执行一样,但实际上在一个时间点上,CPU只有一个线程在运行。

学习线程首先要理清楚三个概念:

  1. 进程:进程是一个静态的概念
  2. 线程:一个进程里面有一个主线程叫main()方法,是一个程序里面的,一个进程里面不同的执行路径。
  3. 在同一个时间点上,一个CPU只能支持一个线程在执行。因为CPU运行的速度很快,因此我们看起来的感觉就像是多线程一样。

  什么才是真正的多线程?如果你的机器是双CPU,或者是双核,这确确实实是多线程。

    Java的线程是通过java.lang.Thread类来实现的,JVM启动时会有一个main方法所定义的主线程,可以通过创建Thread实例来创建线程,每个线程都是由某个特定Thread对象对应的run方法来操作完成的,通过调用Thread类的start方法来启动一个线程。


二、线程的创建和启动

Java中创建线程的方法有两种如下:

1.继承Thread类

public class ThreadTest {
/*
* 通过继承Thread类来启动线程
*/
public static void main(String[] args) {
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.start();
m2.start();
}
}
/*
* MyThread继承自Thread线程类,通过实例化MyThread对象,调用start()方法
* 来启动新线程
* @see java.lang.Thread#run()
*/
class MyThread extends Thread{
/*
*重写run方法,运行新线程逻辑
* @see java.lang.Thread#run()
*/
@Override
public void run() {
String threadName = currentThread().getName();//获取当前线程名称
for(int i=0;i<10000;i++){
System.out.println(threadName+":"+i);
}
}

}

部分输出结果:

Thread-0:5987
Thread-0:5988
Thread-1:8733
Thread-0:5989
Thread-0:5990
Thread-0:5991
Thread-1:8734
Thread-0:5992
Thread-0:5993

我们看到,代码中出现线程0和线程1交替输出,说明两个线程同时在运行(如果效果不明显,可以加大循环次数)

2.实现Runnable接口

public class RunnableTest {
public static void main(String[] args) {
MyThread1 m1 = new MyThread1("线程0");
MyThread1 m2 = new MyThread1("线程1");
/*
* 要启动一个新的线程就必须new一个Thread对象出来,不能直接调用run()方法
*/
new Thread(m1).start();
new Thread(m2).start();
}
}
/*
* 通过实现Runnable接口来创建新线程
*/
class MyThread1 implements Runnable{
private String threadName;//线程名称
public MyThread1(String threadName) {
this.threadName = threadName;
}
/*
* 重写Run方法
* @see java.lang.Runnable#run()
*/
@Override
public void run() {
for(int i=0;i<10000;i++){
System.out.println(threadName+":"+i);
}
}

}

输出结果:
线程0:5742线程0:5743线程0:5744线程1:5043线程1:5044线程1:5045线程1:5046线程1:5047线程1:5048线程1:5049线程1:5050线程0:5745线程0:5746线程0:5747

       使用实现Runnable接口和继承Thread类这两种开辟新线程的方法的选择应该优先选择实现Runnable接口这种方式去开辟一个新的线程。因为接口的实现可以实现多个,而类的继承只能是单继承。因此在开辟新线程时能够使用Runnable接口就尽量不要使用从Thread类继承的方式来开辟新的线程。


三、线程状态转换

Java学习心得之线程(一)


1.线程控制的基本方法

Java学习心得之线程(一)

2.sleep/join/yield方法简介

sleep方法

public class SleepTest {
public static void main(String[] args) {
MyThread2 m1 = new MyThread2();
m1.start();
try {
/*
* Thread.sleep(10000);
* sleep()方法是在Thread类里面声明的一个静态方法,因此可以使用Thread.sleep()的格式进行调用,
* 在哪个线程里面调用了sleep()方法就让哪个线程睡眠, 所以这里是让主线程睡眠10秒种.
*/
Thread.sleep(10000);
System.out.println("main线程睡眠了10秒种后再次启动了");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
m1.flag = false;//改变循环条件,结束死循环
}
}
}

class MyThread2 extends Thread{
public boolean flag=true;//控制循环条件
@Override
public void run() {
//死循环,每秒打印系统时间
while(flag){
System.out.println(new Date().toString());
try {
/*
* 静态方法的调用格式一般为“类名.方法名”的格式去调用 在本类中声明的静态方法时调用时直接写静态方法名即可。 当然使用“类名.方法名”的格式去调用也是没有错的,
* 使用“类名.方法名”的格式去调用属于本类的静态方法
*/
sleep(1000);
/*
* 睡眠的时如果被打断就会抛出InterruptedException异常
*/
} catch (InterruptedException e) {
/*
* // 线程被中断后就返回,相当于是结束线程
*/
return;
}
}
}

}
输出结果:

Tue Oct 24 11:57:49 CST 2017
Tue Oct 24 11:57:50 CST 2017
Tue Oct 24 11:57:51 CST 2017
Tue Oct 24 11:57:52 CST 2017
Tue Oct 24 11:57:53 CST 2017
Tue Oct 24 11:57:54 CST 2017
Tue Oct 24 11:57:55 CST 2017
Tue Oct 24 11:57:56 CST 2017
Tue Oct 24 11:57:57 CST 2017
Tue Oct 24 11:57:58 CST 2017
main线程睡眠了10秒种后再次启动了


join方法
public class TestThread4 {    public static void main(String args[]) {
MyThread2 thread2 = new MyThread2("mythread");
// 在创建一个新的线程对象的同时给这个线程对象命名为mythread
thread2.start();// 启动线程
try {
thread2.join();// 调用join()方法合并线程,将子线程mythread合并到主线程里面
// 合并线程后,程序的执行的过程就相当于是方法的调用的执行过程
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i <= 5; i++) {
System.out.println("I am main Thread");
}
}
}

class MyThread2 extends Thread {
MyThread2(String s) {
super(s);
/*
* 使用super关键字调用父类的构造方法
* 父类Thread的其中一个构造方法:“public Thread(String name)”
* 通过这样的构造方法可以给新开辟的线程命名,便于管理线程
*/
}

public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("I am a\t" + getName());
// 使用父类Thread里面定义的
//public final String getName(),Returns this thread's name.
try {
sleep(1000);// 让子线程每执行一次就睡眠1秒钟
} catch (InterruptedException e) {
return;
}
}
}
}


yield方法

public class TestThread5 {
public static void main(String args[]) {
MyThread3 t1 = new MyThread3("t1");
/* 同时开辟了两条子线程t1和t2,t1和t2执行的都是run()方法 */
/* 这个程序的执行过程中总共有3个线程在并行执行,分别为子线程t1和t2以及主线程 */
MyThread3 t2 = new MyThread3("t2");
t1.start();// 启动子线程t1
t2.start();// 启动子线程t2
for (int i = 0; i <= 5; i++) {
System.out.println("I am main Thread");
}
}
}

class MyThread3 extends Thread {
MyThread3(String s) {
super(s);
}

public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(getName() + ":" + i);
if (i % 2 == 0) {
yield();// 当执行到i能被2整除时当前执行的线程就让出来让另一个在执行run()方法的线程来优先执行
/*
* 在程序的运行的过程中可以看到,
* 线程t1执行到(i%2==0)次时就会让出线程让t2线程来优先执行
* 而线程t2执行到(i%2==0)次时也会让出线程给t1线程优先执行
*/
}
}
}
}