java多线程

概念:多个线程可以同时在一个程序中运行,并且每个线程完成不同的任务。
java中线程的实现通常有两种方法,继承Thread类和实现Runnable接口。不过好像还有一个Callable接口。

  • Thread类创建线程的方法

继承Thread类并覆盖Thread类的run()方法完成线程类的声明,通过new关键字创建Thread线程类的线程对象。run()方法中的代码实现了线程的行为。

/**
 * @ 描 述 : 多线程
 * @ 作 者 : 一念〃
 * @ 时 间 : 14:41 2018/11/11
 * @ 备 注 :
 */
public class ThreadTest extends Thread {

    public ThreadTest(){}
    public ThreadTest(String name){
        super(name);//调用父类(Thread类)的构造方法
    }

    //重写run方法
    @Override
    public void run() {
        for (int count = 1,row=1;row<10;row++,count++){
            for (int i = 0;i<count;i++){
                System.out.print('*');
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        ThreadTest td = new ThreadTest();//实例化对象
        td.start();//调用start()方法执行一个新的线程
    }
}

//打印结果
*
**
***
****
*****
******
*******
********
*********
  • Thread类创建线程的步骤

1)创建一个新的线程类,集成Thread类并覆盖Thread类的run()方法

public class ThreadTest extends Thread {
    @Override
    public void run() {
        
    }
}

2)创建一个线程类的对象,创建方法与一般对象的创建方法相同,使用new关键字完成

ThreadTest td = new ThreadTest();//实例化对象

3)启动新线程对象,调用start()方法

td.start();

多条线程示例:

/**
 - @ 描 述 : 多线程
 - @ 作 者 : 一念〃
 - @ 时 间 : 14:41 2018/11/11
 - @ 备 注 :
 */
public class ThreadTest extends Thread {
    public ThreadTest(){}
    public ThreadTest(String name){
        super(name);//调用父类(Thread类)的构造方法
    }
    //重载run方法
    @Override
    public void run() {
        for (int count = 1,row=1;row<10;row++,count++){
            for (int i = 0;i<count;i++){
                System.out.print('*');
            }
            System.out.println();
        }
    }
    public static void main(String[] args) {
        ThreadTest td1 = new ThreadTest();//实例化对象1
        ThreadTest td2 = new ThreadTest();//实例化对象2
        ThreadTest td3 = new ThreadTest();//实例化对象3
        td1.start();//对象1.start()方法
        td2.start();//对象2.start()方法
        td3.start();//对象3.start()方法
    }
}
//打印结果
*
***
****
*****
******
*******
********
*********
*
***
****
*****
******
*******
********
*********
*
***
****
*****
******
*******
********
*********
  • Runnable接口创建线程的方法

Runnable接口可用于实现线程类,该接口只有一个run()方法,此方法必须由实现了此接口的类来实现。创建线程的第二种方法就是实现Runnable接口。这种方法可以解决java语言不支持的多重集成问题。
run()方法和start()方法的区别:
Tread类中start()方法是开始线程的方法。start()方法会用特殊的方法自动调用run()方法。run()方法是Tread的具体实现。
继承了Thread类后就通过重写run()方法来说明线程的行为,调用start()方法来开始线程。

/**
 * @ 描 述 : 多线程
 * @ 作 者 : 一念〃
 * @ 时 间 : 14:41 2018/11/11
 * @ 备 注 :
 */
public class ThreadTest implements Runnable {
    //重写run()方法
    @Override
    public void run() {
        for (int count = 1,row=1;row<10;row++,count++){
            for (int i = 0;i<count;i++){
                System.out.print('*');
            }
            System.out.println();
        }
    }
    public static void main(String[] args) {
        Runnable rb = new ThreadTest();//书写规范-接口new实现类
        Thread td = new Thread(rb);//通过Thread类创建线程
        td.start();//启动线程td
    }
}

//打印结果
*
**
***
****
*****
******
*******
********
*********
  • Runnable接口创建线程的步骤

1)创建一个实现Runnable接口的类,并且在这个类中重写run()方法

public class ThreadTest implements Runnable {
    @Override
    public void run() {
        
    }
}

2)使用关键字new新建一个对象实例

Runnable rb = new ThreadTest();

3)通过Runnable接口的实例创建一个线程对象。在创建线程对象时,调用的构造函数是new Thread(ThreadTest),它用ThreadTest中实现的run()方法作为新线程的对象的run()方法。

Thread td = new Thread(rb);

4)通过调用ThreadTest对象的start()方法启动线程

td.start();

多条线程示例:

/**
 * @ 描 述 : 多线程
 * @ 作 者 : 一念〃
 * @ 时 间 : 14:41 2018/11/11
 * @ 备 注 :
 */
public class ThreadTest implements Runnable {
    //重写run()方法
    @Override
    public void run() {
        for (int count = 1,row=1;row<10;row++,count++){
            for (int i = 0;i<count;i++){
                System.out.print('*');
            }
            System.out.println();
        }
    }
    public static void main(String[] args) {
        Runnable rb1 = new ThreadTest();//书写规范-接口new实现类1
        Runnable rb2 = new ThreadTest();//书写规范-接口new实现类2
        Runnable rb3 = new ThreadTest();//书写规范-接口new实现类3
        Thread td1 = new Thread(rb1);//通过Thread类创建线程
        Thread td2 = new Thread(rb2);//通过Thread类创建线程
        Thread td3 = new Thread(rb3);//通过Thread类创建线程
        td1.start();//启动线程td1
        td2.start();//启动线程td2
        td3.start();//启动线程td3
    }
}

//打印结果
*
**
***
****
*****
******
*******
********
*********
*
**
***
****
*****
******
*******
********
*********
*
**
***
****
*****
******
*******
********
*********
  • 线程的生命周期

线程的声明周期由线程创建、可运行状态、不可运行状态和退出等部分组成,这些状态之间的转换是通过线程提供的一些方法来完成的。
线程的状态(五个):创建、就绪、阻塞、运行、死亡。

  • 线程调度

如何解释线程调度?比如,在公交站有很多公交车,就好比多条线程,哪辆车先发车?谁控制发车节奏和时间?有一个叫做公交调度员的岗位,该岗位人员就是负责公交车辆调度工作的。
线程的优先级1-10表示,1表示优先级最低,10表示优先级最高,默认是5。这些优先级对象一个Thread类的公用静态变量。

public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;
public static final int MIN_PRIORITY = 1;

多线程应用程序的每一个线程的重要性和优先级可能不同,例如,多个线程在等待获得CPU的时间片时,优先级高的线程就能抢占CPU并得以执行;并且优先级高的线程占用的时间片应该多。因此,高优先级的线程的执行效率会高一些,执行速度也会快一些。
java中CPU的使用通常采用抢占式调度模式。该模式是指许多线程同时处于可运行状态,但只有一个线程正在运行。当线程一直运行直到结束,或者进入不可运行状态,或者具有更高优先级的线程变为可运行状态的,它将会让出CPU。线程与优先级相关方法如下:

public final void setPriority(int newPriority)

其中,参数newPriority表示线程的优先级,该值必须在MIN_PROIORITY~MAX_PRIORITY范围内,分别取值为1和10.目前Windows系统支持的3个级别的优先级为Thread.MAX_PRIORITY、Thread.MIN_PROIORITY和Thread.NORM_PRIORITY。

/**
 * @ 描 述 : 多线程
 * @ 作 者 : 一念〃
 * @ 时 间 : 14:41 2018/11/11
 * @ 备 注 :
 */
public class ThreadTest {
    public static void main(String[] args) {
        //用Thread类的子类创建线程
        ThreadNowTest tnt = new ThreadNowTest();
        //用Runnable接口类的对象创建线程
        Thread td = new Thread(new RunnableTest());
        tnt.setPriority(5);//设置优先级
        td.setPriority(4);//设置优先级
        tnt.start();
        td.start();
    }
}

class RunnableTest implements Runnable {
    //重写run()方法
    @Override
    public void run() {
        System.out.println("这里是实现Runnable接口的。");
        for (int i = 0;i<5;i++){
            System.out.println("RunnableTest:i="+i);
            try {
                Thread.sleep((int) (Math.random()*2000));//线程休眠时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class ThreadNowTest extends Thread {
    //重写run()方法
    @Override
    public void run() {
        System.out.println("这里是继承Thread类的。");
        for (int i = 0;i<5;i++){
            System.out.println("ThreadNowTest:i="+i);
            try {
                Thread.sleep((int) (Math.random()*2000));//线程休眠时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//打印结果
这里是继承Thread类的。
ThreadNowTest:i=0
这里是实现Runnable接口的。
RunnableTest:i=0
RunnableTest:i=1
ThreadNowTest:i=1
ThreadNowTest:i=2
ThreadNowTest:i=3
RunnableTest:i=2
ThreadNowTest:i=4
RunnableTest:i=3
RunnableTest:i=4
  • 线程的休眠,sleep():

sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。
sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;
在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
sleep()与wait()的比较:
wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。
而sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态”。
但是,wait()会释放对象的同步锁,而sleep()则不会释放锁。
wait()可以指定时间也可以不指定时间。sleep()必须指定时间。
在同步中,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
线程的强制运行,join():
与sleep()方法一样,调用join()方法需要处理InterruptedException异常。
线程的礼让,yield():
yield()方法可暂停当前线程执行,允许其他具有相同优先级的线程获得运行机会,该线程仍处于就绪状态,不转为阻塞状态,此时,系统选择其他相同或更高优先级线程执行,若无其他相同或更高优先级线程,则该线程继续执行。

  • 线程同步

**线程同步简述:**同一时刻有一个线程在修改共享数据,另一个线程在读取共享数据,只有当修改共享数据的线程完成数据处理后,读取数据的线程才会读取共享数据,得到的是修改后的数据。
**线程异步简述:**同一时刻有一个线程在修改共享数据,另一个线程在读取共享数据,当修改共享数据的线程没有处理完毕时,读取共享数据的线程肯定会得到错误的结果。

很多时候都需要同步操作,举例说明:两条线程,同时售卖火车票,可能就会出现一张车票出售两次的错误。

  • 同步的格式

只有把一个语句块声明为synchronized,在同一时间,它的访问线程之一才能执行该语句块。用关键字synchronized可将方法快声明为同步。

public synchronized void test(){

}

对于同步块,synchronized获取的是参数中的对象锁。
有关类锁和对象锁请看文章 → 文章1

synchronized (obj){

}

当线程执行到这里的同步块时,它必须获取obj对象的锁才能执行同步块;否则线程只能等待获得锁。必须注意的是,obj对象的作用范围不同,控制情况也不尽相同。

public void test(){
        Object obj = new Object();//创建局部Object类型的对象obj
        synchronized (obj){ //同步块

        }
    }

以上代码创建了一个局部对象obj。由于每个线程执行到此,都会产生一个obj对象,每个线程都可以获得创建的新的obj对象的锁,不会相互影响,因此这段程序不会起到同步作用。如果同步的是类的属性,情况就不同了。下面的程序是同步类的成员变量的一般格式:

public class ThreadTest {
    Object obj = new Object();//创建局部Object类型的对象obj
    public void test() {
        synchronized (obj) { //同步块

        }
    }
}

当两个并发线程访问同一个对象的synchronized(obj)同步块时,一段时间内只能有一个线程运行,另外的线程必须等到当前线程执行完同步块释放锁之后,获得锁的线程将执行同步块。有时通过下面的格式声明同步块。

public void test() {
	 synchronized (this) { //同步块

	}
}
  • 线程通信

生产者/消费者
生产者在一个循环中不断产生共享数据,而消费者则不断地消费生产者生产的共享数据。二者之间的关系可以很清楚地说明,必须现有生产者生产共享数据,才能有消费者消费共享数据。

生产者与消费者模式的结构图
在这里插入图片描述

举例—寄信:
把信写好------生产者制作数据。
把信放入邮筒------生产者把数据放入缓冲区。
邮递员把信从邮筒里取出------消费者把数据从缓冲区取出。
邮递员把信拿去邮局做相应的处理------消费者处理数据。

生产者和消费者之间的数据关系如下:

  • 生产者生产前,如果共享数据没有被消费,则生产者等待;生产者生产后,通知消费者消费。
  • 消费者消费前,如果共享数据已经被消费完,则消费者等待;消费者消费后,通知生产者生产。

实际上实现多线程的方式还有一种:

实现Callable接口,重写call方法。Callable接口与Runnable接口的功能类似,但提供了比Runnable更强大的功能。有以下三点
(1)Callable可以在任务结束后提供一个返回值,Runnable没有提供这个功能。
(2)Callable中的call方法可以抛出异常,而Runnable的run方法不能抛出异常。
(3)运行Callable可以拿到一个Future对象,表示异步计算的结果,提供了检查计算是否完成的方法。

需要注意的是,无论用那种方式实现了多线程,调用start方法并不意味着立即执行多线程代码,而是使得线程变为可运行状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值