Java多线程 基础知识的总结

1 并发与并行

并行:在同一时刻,有多个指令在多个cpu上同时执行。

并发:在同一时刻,有多个指令在单个cpu上交替执行。

2 进程与线程

进程:正在运行的程序。

独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。

动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。

并发性:任何进程都可以同其他进程一起并发执行。

线程:是进程中单个顺序的控制流,是一条执行路径。

单线程:一个进程只有一条执行路径,则称为单线程程序。

多线程:一个进程如果有多条执行路径,则称为多线程程序。

3 实现多线程的方法

3.1方式一:继承Thread类

(1)介绍thread类

thread extends Object

thread implements Runnable

Thread的构造方法

Tread(Runnable target)

Tread(Runnable target,String name)

(2)具体步骤:创建一个类去继承Thread类,这个子类去重写Thread中的run()方法,然后分配并启动子类的实例。

public class Demo1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

public class Test01 {
    public static void main(String[] args) {
        Demo1 d1 =new Demo1();
        Demo1 d2 =new Demo1();
        d1.start();
        d2.start();
    }
}

☆为什么要重写run()方法?

因为run()是用来封装被线程执行的代码。

run()方法和start()方法的区别?

run():封装线程执行的代码,直接调用,相当于普通方法的调用。

start():启动线程,然后由jvm调用此线程的run()方法。

3.2方式二:实现Runnable接口

(1)具体步骤:声明一个实现Runnable接口的类,那个类然后实现run方法,然后可以分配类的实例,在创建Thread对象时作为参数传递,并启动。

(2)Thread的构造方法

Tread(Runnable target)

Tread(Runnable target,String name)

public class Demo2 implements Runnable{
@Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //不能直接使用getName方法,因为Demo2这个类是实现Runnable这个接口。
            //并没有继承Thread这个方法。
            //可以通过拿到当前执行的线程。
            System.out.println(Thread.currentThread().getName()+":"+i);
        }        
    }
}

public class Test2 {
    public static void main(String[] args) {
        Demo2 d1 =new Demo2();      
        Thread t1 = new Thread(d1,"火车");
        Thread t2 = new Thread(d1,"汽车");        
        t1.start();
        t2.start();
    }
}

3.3方式三:实现Callable接口

(1)具体步骤:首先敲写一个实现Callable接口的类。在main方法内new一个这个类的对象,然后把生成的这个实例对象注册到FutureTask类中,然后将FutureTask类的实例注册进入Thread中运行。最后可以采用FutureTask<V>中的get方法获取自定义线程的返回值。

(2)原理:我们可以查看Callable接口的源码,发现只有一个call()方法的定义.FutureTask<V>实现了RunnableFuture<V>的接口,这个RunnableFuture<V>接口也实现了,Runnable这个接口。FutureTask<V>中还有一个需要传入Callable<V>类型参数的构造方法,且会赋值给FutureTask<V>的私有属性。无论我们以怎样的形式去实现多线程,都需要调用Tread类中的Start()方法去向操作系统请求io和cpu等资源,Tread有参构造中需传入一个Runnable类型的接口参数,而FutureTask<V>的实例正可以充当这个参数传入Tread。

ps:get()方法是FutureTask<V>类中的方法,运用可能需要抛异常。

public class Demo3 implements Callable {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i <10 ; i++) {
            System.out.println("多线程方式3 "+i);
        }
        return "结束了";
    }
}


public class Test03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Demo3 d =new Demo3();
        FutureTask<String> ft =new FutureTask<>(d);
        Thread t1 =new Thread(ft);
        t1.start();
        String s = ft.get();
        System.out.println(s);
    }
}

4设置和获取线程的名称

Tread类中提供了两个方法用来设置和获取线程名称

void setName(String name) 将此线程的名称更改为等于参数的name

String getName() 返回此线程的名称

Thread currentThread() 返回对当前正在执行的线程对象

ps:如果在Demo4类中输出中调用了Tread类中的getName方法而在Test04中并没有命名,则系统会按照自己的源码给一个名字,例如“Demo-0:0或 Demo-1 :0”等等。

public class Demo4 extends Thread{
    public Demo4() {
    }
    public Demo4(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i= 0; i <5; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}

public class Test04 {
    public static void main(String[] args) {
        //命名方法一:调用有参构造
        Demo4 d1 =new Demo4("飞机");
        Demo4 d2 =new Demo4("高铁");
       /*命名方法二:调用无参构造
        Demo4 d1 =new Demo4();
        Demo4 d2 =new Demo4();
        d1.setName("飞机");
        d2.setName("高铁");
       */
        d1.start();   d2.start();
    }
    }

5 线程的常见控制方法

static void sleep(long millis)     使当前正在执行的线程停留(暂停执行)指定的毫秒数。

void join()                                等待这个线程死亡

Void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,

                                                     Java虚拟机将退出

5.1线程休眠

static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数。

public class Demo5 extends Thread{                  运行结果
    public Demo5() {                                刘备+0
    }                                               孙权+0
    public Demo5(String name) {                     曹操+0
        super(name);                                孙权+1
    }                                               刘备+1
    @Override                                       曹操+1
    public void run() {                             孙权+2
        for (int i = 0; i < 5; i++) {               曹操+2
       /*Tread表示的是当前运行的线程停留1000毫秒。*/    刘备+2
            try {                                     *
                Thread.sleep(1000);                   * 
            } catch (InterruptedException e) {        *
                e.printStackTrace();
            }
            System.out.println(getName()+"+"+i);
        }
    }
}


public class Test5 {
    public static void main(String[] args) {
        Demo5 d1 =new Demo5("曹操");
        Demo5 d2 =new Demo5("刘备");
        Demo5 d3 =new Demo5("孙权");
        d1.start();
        d2.start();
        d3.start();
    }
}

总结:这三个人在执行的时候也在抢cpu的使用权。


5.2 线程死亡

void join() 只有该线程执行完之后,才会执行其他线程。

public class Demo5 extends Thread{
    public Demo5() {
    }
    public Demo5(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i= 0; i <5; i++) {
            System.out.println(getName()+":"+i);
        }
    }
}
public class Test5 {
    public static void main(String[] args) throws InterruptedException {
        Demo5 d1 =new Demo5("皇上");
        Demo5 d2 =new Demo5("大阿哥");
        Demo5 d3 =new Demo5("二阿哥");
        d1.start();
        d1.join();
        //只有皇上执行完之后,两位阿哥才有机会去执行。
        d2.start();
        d3.start();
    }
}

5.3守护线程

Void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,

Java虚拟机将退出(不是立即退出,会挣扎一会)

public class Demo5 extends Thread{
    public Demo5() {
    }
    public Demo5(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"+"+i);
        }
    }
}

public class Test5 {
    public static void main(String[] args)  {
        Demo5 d1 =new Demo5("皇上");
        Demo5 d2 =new Demo5("大阿哥");
        Demo5 d3 =new Demo5("二阿哥");
        d3.setDaemon(true);
        d2.setDaemon(true);
        d1.start();
        d2.start();
        d3.start();
    }
}

6线程的调度

6.1线程调度方式:

(1)分时调度模型:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间片

(2)抢占式调度模型:优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选一个,优先级高的线程获取的cpu时间片相对多一些。

Java使用的是抢占式调度模型。

6.2Tread类中获取和设置优先级的方法

ps:线程的优先级高仅仅表示线程获取的cpu时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果。

final int getPriority()                          返回此线程的优先级

final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10

public class Demo6 implements Callable {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
        return "程序完毕";
    }
}

public class Test06 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Demo6 d1 =new Demo6();
        FutureTask<String> ft =new FutureTask<>(d1);
        Thread t1 =new Thread(ft);
        t1.setName("飞机");
        t1.setPriority(9);
        t1.start();
        Demo6 d2 =new Demo6();
        FutureTask<String> ft2 =new FutureTask<>(d2);
        Thread t2 =new Thread(ft2);
        t2.setName("高铁");
        t2.setPriority(1);
        t2.start();
        System.out.println(t1.getPriority());
         String s = ft2.get();
        System.out.println(s);
    }
}

7三种实现多线程方式的总结

(1)实现Runnable,Callable接口的方式

好处:拓展性强,实现该接口的同时还可以继承其他的类,而且Callable接口的call方法还会有返回值

缺点:代码复杂,不能直接使用Thread类中的方法

(2)继承Thread类

好处:代码简单,可以直接使用Thread类中的方法

缺点:拓展性较差,不能再继承其他的类。

上一篇:第八章:(3)CyclicBarrier 循环栅栏


下一篇:java 多线程编程(包括创建线程的三种方式、线程的调度策略、线程安全问题、线程同步机制、同步锁、线程通信、线程池等)