多线程编程学习笔记

多线程编程

目录

线程概述

线程的创建

创建线程程序

线程同步

守护线程

线程之间的相互通讯

线程池和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 迚程结束,当前台线程结束时,不管后台线程是否结束,都要被停掉!

时间: 2024-11-08 22:25:08

多线程编程学习笔记的相关文章

多线程编程学习笔记——线程池(一)

接上文 多线程编程学习笔记——线程同步(一) 接上文 多线程编程学习笔记——线程同步(二) 接上文 多线程编程学习笔记——线程同步(三) 创建多线程操作是非常昂贵的,所以每个运行时间非常短的操作,创建多线程进行操作,可能并不能提高效率,反而降低了效率. 如果你有非常多的执行时间非常短的操作,那么适合作用线程池来提高效率,而不是自行创建多线程. 线程池,就是我们先分配一些资源到池子里,当我们需要使用时,则从池子中获取,用完了,再放回池子里. .NET中的线程池是受CLR管理的,TheadTool类

多线程编程学习笔记——线程同步(三)

接上文 多线程编程学习笔记——线程同步(一) 接上文 多线程编程学习笔记——线程同步(二) 七.使用Barrier类 Barrier类用于组织多个线程及时在某个时刻会面,其提供一个回调函数,每次线程调用了SignalAndWait方法后该回调函数就会被执行. 1.代码如下: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; //

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

接上文 多线程编程学习笔记——async和await(一) 三.   对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步调用 . 1.示例代码如下. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Thread

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

接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多个并行的异步操作使用await时聚合异常. 1.程序示例代码如下. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;

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

接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 四.   使用ConcurrentBag创建一个可扩展的爬虫 本示例在多个独立的即可生产任务又可消费任务的工作者间如何扩展工作量. 1.程序代码如下. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Sy

多线程编程学习笔记——编写一个异步的HTTP服务器和客户端

接上文 多线程编程学习笔记——使用异步IO 二.   编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Ta

Java多线程编程(学习笔记)

一.说明 周末抽空重新学习了下多线程,为了方便以后查阅,写下学习笔记. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的.例如:程序中有两个子系统需要并发执行,这时候需要利用多线程编程. 通过多线程的使用,可以编写出非常高效的程序.但如果创建了太多的线程,程序执行的效率反而会降低. 同时上下文的切换开销也很重要,如果创建太多的线程,CPU花费在上下文的切换时间将对于执行程序的时间. 二.Java多线程编程 概念 在学习多线程时,我们应该首先明白另外一个概念. 进程:是计算机中的程序关于某

VC多线程编程学习笔记(一)

最近两天在学多线程编程,有了一些心得,写下来和大家一起共勉.文中一些部分引用了韩耀旭的文章<多线程编程>http://www.vckbase.com/document/viewdoc/?id=1704和MSDN资料. 一.缘起 工作上要用到串口编程,本来一直是用mscomm控件来进行串口通讯的,后来觉得这个控件功能不灵活,想直接使用api编程,那就不可避免的要使用多线程技术:用一个支线程一直挂在那里监听串口,就不影响主线程的消息循环了. 二.为何要用多线程 有时候需要把程序的运行挂起一段时间,

linux网络编程学习笔记之四 -----多线程并发服务端

相对于使用进程实现并发,用线程的实现更加轻量.每个线程都是独立的逻辑流.线程是CPU上独立调度运行的最小单位,而进程是资源分配的单位.当然这是在微内核的操作系统上说的,简言之这种操作系统的内核是只提供最基本的OS服务,更多参看点击打开链接 每个线程有它自己的线程上下文,包括一个唯一的线程ID(linux上实现为unsigned long),栈,栈指针,程序计数器.通用目的寄存器和条件码,还有自己的信号掩码和优先级.同一个进程里的线程共享这个进程的整个虚拟地址空间,包括可执行的程序文本.程序的全局