多线程编程学习笔记

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程编程学习笔记相关的知识,希望对你有一定的参考价值。

多线程编程

目录

线程概述

线程的创建

创建线程程序

线程同步

守护线程

线程之间的相互通讯

线程池和java.util.concurrent包

一、概述

1.相关概念

进程(Process):程序(任务)执行的过程,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程,共享内存,共享文件。比如在Windows系统中,一个运行的exe就是一个进程。

线程(Thread):进程中的一个执行流程,一个进程中可以运行多个线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。现在的操作系统是多任务操作系统,多线程是实现多任务的一种方式,在线程之间实际上轮换执行。

2.线程编程相关API

Java多线程相关的核心类和接口主要包括Thread类、Runnable类、Object类等,都存放在java.lang包中,java.lang包提供了利用Java编程语言进行程序设计的基础类。

(1)Thread类。线程管理的主要方法定义在Thread类中,包括:

start()方法:

MyThread2 mythread2 = new MyThread2();

mythread2.start();

在单独的路径中启动线程,使该线程开始执行,然后调用该Thread对象上的 run 方法。

run()方法:

如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;如果我们编写一个类继承Thread,那么就是子类中被重写的run()方法被调用

setName()方法:

MyThread myThread1 = new MyThread();

myThread1.setName("t1");

改变线程名称,使之与参数 name 相同

setPriority()方法:

设置线程对象的优先级,取值范围为1-10,最好使用Thread.NORMPRIORITY,Thread.MINPRIORITY,Thread.MAX_PRIORITY,这三个值(取值分别为1,5,10)

setDaemon()方法:

mythread1.setDaemon(true);//守护线程方法必须在start()启动前前调用

mythread1.start();

将该线程标记为守护线程或用户线程,如果参数为true,则表示该线程是一个守护线程。当正在运行的线程都是守护线程时,Java 虚拟机退出

join()方法:

当前线程在第二个线程上调用本方法,将导致当前线程阻塞,直到第二个线程停止或运行了指定的微秒数。如果参数为0,那么第一个线程将无限期等待。

isAlive()方法:

测试线程是否处于活动状态,如果处于活动状态,则返回true.

上述方法都是在特定的Thread对象上调用的。以下方法都是静态的,调用静态方法之一,将会在当前运行的线程上执行操作:

yield()方法:

导致当前正在运行的线程休眠至少指定的毫秒数时间

sleep()方法:

导致当前正在运行的线程休眠至少指定的毫秒数时间

currentThread()方法:

Thread.currentThread();

返回对当前正在执行的线程对象的引用

(2)Runnable类。Runnable接口中只定义了一个run()方法。

(3)Object类。该类包含了如下与多线程相关的方法:

wait()方法:

导致当前线程在该对向上无限期等待,直到其他线程调用相同对象的notify()或notifyAll()方法通知它恢复执行为止

notify()方法:

唤醒正在该对象上等待的一个线程,当前线程必须拥有对象的锁以调用该方法。

notifyAll()方法:

类似于notify()方法,但是是唤醒所有等待的线程。

二、线程的创建

1.线程的创建

(1)通过继承Thread类来创建线程步骤:

1)创建一个继承Thread的类 2)在该类中重写run()方法,在方法中写入想要线程运行的代码 3)创建该类的示例 4)调用该实例的start()方法,开始运行线程

示例:

//创建一个继承Thread的类
public class MyThread1 extends Thread{
    //重写run()方法,在方法中写入想要线程运行的代码
    public void run(){

        for (int i = 0; i < 20; i++) {

        System.out.println("你在哪儿呢?"+i); 

        }

    }

}

public class Test {

    public static void main(String[] args) {
        //创建该类的示例
        MyThread mythread = new MyThread();
        //调用该实例的start()方法,开始运行线程
        mythread1.start();

    }

}

(2)通过实现Runnable借口来创建线程步骤:

1)创建一个类来实现Runnable接口,用于代表需要线程完成的任务 2)在Runnable指定的run()方法内放入需要线程执行的代码 3)创建一个Runnable类的示例 4)创建一个Thread对象,将Runnable的实例作为构造器参数传入进去 5)通过调用Thread类的实例的start()方法执行线程

示例:

//创建一个类来实现Runnable接口
public class MyTask implements Runnable{

    @Override

    //在Runnable指定的run()方法内放入需要线程执行的代码
    public void run() {

        // TODO Auto-generated method stub

        System.out.println("线程启动了");

    }

}

public class Test {

    public static void main(String[] args) {

        //创建一个Runnable类的示例
        MyTask myTask = new MyTask();
        //创建一个Thread对象,将Runnable的实例作为构造器参数传入进去
        Thread t = new Thread(myTask);
        //通过调用Thread类的实例的start()方法执行线程
        t.start();

        System.out.println("主程序运行结束");

    }

}

总结:Thread类实现了Runnable接口,即Thread类是Runnable接口的一个子类;使用Runnable类可以避免Java单继承的局限性;使用Runnable接口可以将虚拟CPU(Thread类)与县城要完成的任务有效分离,较好的体现了面向对象设计的基本原则。

2.线程中的五种状态

(1)New——新建状态

当程序使用 new 关键字创建了一个线程后,该线程就处于新建状态(初始状态),此时线程还未启动,当线程对象调用 start()方法时,线程启劢,迚入 Runnable状态。

(2)Runnable——可运行(就绪)状态

调用start()方法后,线程从 New 状态迚入 Runnable 状态(就绪状态),start()方法是在 main()方法(Running 状态)中调用的当线程处于 Runnable 状态时,表示线程准备就绪,等待获取 CPU

(3)Running——运行(正在运行)状态

假如该线程获取了 CPU,则迚入 Running 状态,开始执行线程体,即 run()方法中的内

注意:

如果系统叧有 1 个 CPU,那么在仸意时间点则叧有 1 条线程处于 Running 状态;如果是双核系统,那么同一时间点会有 2 条线程处于 Running 状态。但是,当线程数大于处理器数时,依然会是多条线程在同一个 CPU 上轮换执行。

当一条线程开始运行时,如果它不是一瞬间完成,那么它不可能一直处于 Running 状态,线程在执行过程中会被中断,目的是让其它线程获得执行的机会,像这样线程调度的策略取决于底层平台。对于抢占式策略的平台而言,系统系统会给每个可执行的线程一小段时间来处理仸务,当该时间段(时间片)用完系统会剥夺该线程所占资源(CPU),让其他线程获得运行机会。

(4)Block——阻塞(挂起)状态

在如下情冴下,线程会迚入阻塞状态:

1)线程调用了 sleep()方法主劢放弃所占 CPU 资源 2)线程调用了一个阻塞式 IO 方法(比如控制台输入方法),在该方法返回前,该线程被阻塞当正在执行的线程被阻塞时,其它线程就获得执行机会了。需要注意的是,当阻塞结束时,该线程将迚入 Runnable 状态,而非直接迚入 Running 状态

(5)Dead——死亡状态

当线程的 run()方法执行结束,线程迚入 Dead 状态,线程结束后,被对象垃圾回收

需要注意的是,不要试图对一个已经死亡的线程调用 start()方法,线程死亡后将不能再次作为线程执行,系统会抛出 IllegalThreadStateException 异常

三、创建多线程程序

示例1:使用继承实现多线程

经典的卖票:

public class MyThread extends Thread {

    private int ticket=10;

    public void run(){

        for(int i=0;i<20;i++){

            if(this.ticket>0){

                System.out.println(getName()+"出售车票"+this.ticket--);

            }

        }

    }

}


    MyThread myThread1=new MyThread();

    myThread1.setName("窗口1");

    MyThread myThread2=new MyThread();

    myThread2.setName("窗口2");

    MyThread myThread3=new MyThread();

    myThread3.setName("窗口3");

    myThread1.start();

    myThread2.start();

    myThread3.start();

通过运行可以发现:每个窗口都出售了10张票,这很恐怖

示例2:实现多线程第二种方式,实现接口:

在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:

1)避免单继承的局限,一个类可以继承多个接口。 2)适合于资源的共享

public class MyThread implements Runnable {

    private int ticket=10;

    public void run(){

        for(int i=0;i<20;i++){

            if(this.ticket>0){

                System.out.println(Thread.currentThread().getName()+"出售车票"+this.ticket--);

            }

        }

    }

}

    MyThread myThread=new MyThread();

    new Thread(myThread,"窗口1").start();

    new Thread(myThread,"窗口2").start();

    new Thread(myThread,"窗口3").start();

通过运行结果发现在这里实现了了数据共享

1.线程的优先级(资源紧张时候,尽可能优先,取值分别为10,1,5,)

Thread.MAX_PRIORITY 设置为最高优先级

Thread.MIN_PRIORITY 设置为最低级

Thread.NORM_PRIORITY设置为默认级别

默认有10优先级,优先级高的线程获得执行(迚入Running状态)的机会多,机会的多少不能通过代码干预

@Override

public void run() {

    // TODO Auto-generated method stub

    Thread.currentThread().setPriority(1);

    for (int i = 0; i < 100; i++) {

        System.out.println("你是谁"+i);

    }

}

2.使用join()方法

thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。

示例:1

Thread1 t1=new Thread1();

Thread2 t2=new Thread2();

t1.setName("主线程");

t1.start();

t1.join();

t2.start();

在这个实验中,我们发现这次线程运行是先把t1运行完毕之后再运行的t2从某种意义上来说,可以使线程同步起来

示例2:Thread2 t2=new Thread2(); 这个实验我们也发现了同样的事情 try {

        t2.start();

        t2.join();

    } catch (InterruptedException e) {

        // TODO Auto-generated catch block

        e.printStackTrace();

    }

    for(int i=0;i<10;i++){

        System.out.println(Thread.currentThread().getName()+" "+i);

    }

3.使用Thread.yield()让出CPU

当前线程让出处理器(离开 Running 状态),使当前线程迚入 Runnable 状态等待如果线程在运行过程中,自己调用了yield()方法,则主动由Running状态迚入Runnable状态

四.同步线程

1.概念:

异步:并发,各干自己的。如:一群人同时上卡车

同步:步调一致的处理。如:一群人排队上公交车

多个线程并发读写同一个临界资源时候会发生”线程并发安全问题“,如果保证多线程同步访问临界资源,就可以解决

常见的临界资源:

1)多线程共享实例变量 2)静态公共变量

1.同步块:使用同步代码块解决线程并发安全问题

synchronized(同步监视器){

}

同步监视器——是一个任意对象实例,是一个多个线程之间的互斥的锁机制,多个线程要使用同一个"监视器"对象,实现同步互斥

示例:

int count = 20;

@Override
public void run() {

    for (int i = 0; i < 50; i++) {

        synchronized (this) {

            if (count > 0) {

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                }

                System.out.println(Thread.currentThread().getName() + "号窗口卖出" + count-- + "号票");

            }

        }

    }

}

TicketSouce t=new TicketSouce();

new Thread(t,"t1").start();

new Thread(t,"t2").start();

new Thread(t,"t3").start();

2.同步方法——在使用临界资源的位置加锁,如果方法的全部过程需要同步,可以简单使用synchronized修饰方法,相当于整个方法

int count = 20;

@Override
public void run() {

    for (int i = 0; i < 50; i++) {

        sale();

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }

}

public synchronized void sale() {

    if (count > 0) {

        System.out.println(Thread.currentThread().getName() + "号窗口卖出" + count-- + "号票");

    }

}

如果去掉修饰符--这里多测试几次会发现有相同的票出现

五、守护线程(精灵线程、后台线程)——是在后台运行的线程

任何一个守护线程都是整个JVM中所有非守护线程的保姆,只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

演示:

public class MyThread1 extends Thread{

    public void run(){

        Thread.currentThread().setPriority(10);

        for (int i = 0; i < 20; i++) {

            System.out.println("你在哪儿呢?"+i);


        }

    }

}

public class MyThread2 extends Thread{

    public void run(){

        Thread.currentThread().setPriority(1);

        for (int i = 0; i < 20; i++) {

            System.out.println("你猜猜看?"+i);

        }

    }

}

public class Test {

    public static void main(String[] args) {

        MyThread2 mythread2 = new MyThread2();

        mythread2.start();

        MyThread1 mythread1 = new MyThread1();

        mythread1.start();

        mythread1.setDaemon(true);//守护线程方法必须在start()前

    }

}

Java迚程的结束:当前所有前台线程都结束时,Java 迚程结束,当前台线程结束时,不管后台线程是否结束,都要被停掉!

以上是关于多线程编程学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程学习笔记——线程同步

多线程编程学习笔记——线程池

多线程编程学习笔记——使用并发集合

多线程编程学习笔记——async和await

Python 3多线程编程学习笔记-基础篇

多线程编程学习笔记——async和await