1、多线程代码运行的特点
-
每一条线程,如果抢不到CPU的执行权,就执行不了代码。
-
只有抢到了CPU的执行权,才能执行run方法里面的代码。
-
在执行代码的过程中,随时都有可能被另外的线程抢走CPU的执行权。
-
如果我们同时开启了多条线程,在执行的时候,结果有可能每次都不一样,是随机的。
2、创建线程方式一(继承方式)
Java使用java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:
定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。创建Thread子类的实例,即创建了线程对象调用线程对象的start()方法来启动该线程。
测试类:
public class Test {
public static void main(String[] args) {
//1.创建线程的对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//2.开启线程,让线程跑起来
t1.start();
t2.start();
//并没有开启线程
//普通的方法调用
//是运行在虚拟机中的main线程中的。
//t1.run();
//特点:
//在Java中,线程启动之后。
//在每一个时刻,到底是那条线程执行,我们是无法控制的。
//随机性。
//当线程启动之后,多条线程,都在抢夺CPU的执行权。
//谁抢到了CPU,那么就执行哪条线程。
}
}
自定义线程类:
public class MyThread extends Thread{
//因为run方法里面的代码,就是线程开启之后,要运行的代码
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
3、创建线程的方式二(实现方式)
采用java.lang.Runnable
也是非常常见的一种,只需要重写run方法即可。
步骤如下:
- 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
- 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
- 调用线程对象的start()方法来启动线程。
代码如下:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(i);
}
}
}
public class Test {
public static void main(String[] args) {
//1.创建的是线程要执行的参数对象
//创建了一个参数对象
MyRunnable mr1 = new MyRunnable();
//又创建了一个参数对象
MyRunnable mr2 = new MyRunnable();
//2.创建线程对象
//两条线程,各自跑各自的
//线程一,跑参数一
Thread t1 = new Thread(mr1);
//线程二,跑参数二
Thread t2 = new Thread(mr2);
t1.start();
t2.start();
}
}
通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
tips:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
总结:
Thread和Runnable的区别
实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去共享同一个资源。
- 可以避免java中的单继承的局限性。
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
4、线程的姓名
第一种创建多线程的方式中。
利用set方法设置姓名
利用构造方法设置姓名
利用get方法获取姓名
代码示例:
public class Test {
public static void main(String[] args) {
//子类不能继承父类的构造方法
//但是可以通过super进行调用
//1.创建线程的对象
MyThread t1 = new MyThread("火车");
MyThread t2 = new MyThread("飞机");
//细节
//起名字,要在创建对象之后,开启线程之前
//t1.setName("火车");
//t2.setName("飞机");
//2.开启线程,让线程跑起来
t1.start();
t2.start();
}
}
public class MyThread extends Thread{
//此时我们没有写构造,jvm会加一个无参构造
public MyThread() {
}
public MyThread(String name) {
super(name);
}
//因为run方法里面的代码,就是线程开启之后,要运行的代码
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//此时getName是获取当前线程的姓名
System.out.println(getName() + "@" + i);
}
}
}
第二种创建多线程的方式中。
代码示例:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//Thread.currentThread(); 哪条线程执行到这行代码,此时获取的就是当前线程的对象
System.out.println(Thread.currentThread().getName() + "@" +i);
}
}
}
public class Test {
public static void main(String[] args) {
//1.创建的是线程要执行的参数对象
//创建了一个参数对象
MyRunnable mr1 = new MyRunnable();
//又创建了一个参数对象
MyRunnable mr2 = new MyRunnable();
//2.创建线程对象
//两条线程,各自跑各自的
//线程一,跑参数一
Thread t1 = new Thread(mr1,"线程A");
//线程二,跑参数二
Thread t2 = new Thread(mr2,"线程B");
t1.start();
t2.start();
}
}