概念:多个线程可以同时在一个程序中运行,并且每个线程完成不同的任务。
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方法并不意味着立即执行多线程代码,而是使得线程变为可运行状态。