从零开始的Java开发1-6-3 多线程:概念Thread类和Runnable接口创建线程线程的状态和生命周期sleep和join方法优先级同步线程间通信
Posted karshey
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零开始的Java开发1-6-3 多线程:概念Thread类和Runnable接口创建线程线程的状态和生命周期sleep和join方法优先级同步线程间通信相关的知识,希望对你有一定的参考价值。
文章目录
进程与线程
进程:是指可执行程序存放在计算机存储器的一个指令序列,它是一个动态执行的过程。
线程:是比线程还要小的运行单位,一个进程包含多个线程。——线程可以看作一个子程序。
多线程:通过对CPU的轮转来达到多个程序同时运行的效果。
Thread类和Runnable接口介绍
线程的创建的两种方法
- 创建一个
Thread
类,或者一个Thread
子类的对象 - 创建一个实现
Runnable
接口的类的对象
Thread类
是一个线程类,位于java.lang包下。
构造方法 | 说明 |
---|---|
Thread() | 创建一个线程对象 |
Thread(String name) | 创建一个具有指定名称的线程对象 |
Thread(Runnable target) | 创建一个基于Runnable接口实现类的线程对象 |
Thread(Runnable target,String name) | 创建一个基于Runnable接口实现类,具有指定名称的线程对象 |
Thread类的常用方法
方法 | 说明 |
---|---|
public void run() | 线程相关的代码写在该方法中,一般需要重写 |
public void start() | 启动线程的方法 |
public static void sleep(long m) | 线程休眠m毫秒的方法 |
public void join() | 优先执行调用join() 方法的线程 |
Runnable接口
- 只有一个方法
run()
Runnable
是Java
中用以实现线程的接口- 任何实现线程功能的类都必须实现该接口
文档
Runnable相关:
Thread相关:
如何使用Thread类去创建一个自定义的线程:
如何创建线程对象并启动线程:
如何用实现Runnable接口的方式去创建线程:
如何创建并启动线程:
创建线程:通过Thread类
通过继承Thread
类的方式创建线程类,重写run()
方法
所有和线程相关的代码都写到run方法里。
代码1:定义一个线程和一个主线程
class MyThread extends Thread
public void run()
System.out.println(getName()+"该线程正在执行!");
public class ThreadTest
public static void main(String[] args)
System.out.println("主线程1");
MyThread mt = new MyThread();
mt.start();// 启动线程
System.out.println("主线程2");
输出:
主线程1
主线程2
Thread-0该线程正在执行!
由此可见,我们的MyThread
的名字默认为Thread-0
。
这里线程的执行顺序是随机的。
注意:线程不能多次启动,只能启动一次,否则会输出:
Exception in thread "main" Thread-0该线程正在执行!
java.lang.IllegalThreadStateException
at java.base/java.lang.Thread.start(Thread.java:793)
at com.thread.ThreadTest.main(ThreadTest.java:16)
代码2:定义了两个线程
class MyThread2 extends Thread
public MyThread2(String name)
super(name);
public void run()
for (int i = 0; i < 20; i++)
System.out.println(getName() + "正在运行" + i);
public class ThreadTest2
public static void main(String[] args)
MyThread2 mt1 = new MyThread2("线程1");
MyThread2 mt2 = new MyThread2("线程2");
mt1.start();
mt2.start();
输出:由此可见,线程获得CPU的使用权是随机的。
线程1正在运行0
线程2正在运行0
线程1正在运行1
线程2正在运行1
线程1正在运行2
线程2正在运行2
线程1正在运行3
线程2正在运行3
线程1正在运行4
线程2正在运行4
线程1正在运行5
线程2正在运行5
线程1正在运行6
线程1正在运行7
线程2正在运行6
线程1正在运行8
线程2正在运行7
线程1正在运行9
线程2正在运行8
线程1正在运行10
线程2正在运行9
线程1正在运行11
线程2正在运行10
线程1正在运行12
线程2正在运行11
线程1正在运行13
线程2正在运行12
线程1正在运行14
线程2正在运行13
线程1正在运行15
线程2正在运行14
线程1正在运行16
线程2正在运行15
线程1正在运行17
线程2正在运行16
线程1正在运行18
线程2正在运行17
线程1正在运行19
线程2正在运行18
线程2正在运行19
创建线程:通过实现Runnable接口
为什么要实现Runnable接口?
Java
不支持多继承,但是可以继承多个接口- 不打算重写
Thread
类的其他方法,只重写run
方法
代码:
//实现Runnable接口
class PrintRunnable implements Runnable
@Override
public void run()
System.out.println(Thread.currentThread().getName() + "正在运行!");
public class Test
public static void main(String[] args)
// 线程1
PrintRunnable pr = new PrintRunnable();// 定义实现类对象
Thread t1 = new Thread(pr);// 接口是参数 创建一个线程
t1.start();// 启动线程
// 线程2
PrintRunnable pr2 = new PrintRunnable();
Thread t2 = new Thread(pr2);
t2.start();
输出:
Thread-0正在运行!
Thread-1正在运行!
再多按几次运行,输出:
Thread-1正在运行!
Thread-0正在运行!
由此可见,线程的运行有随机性。
加上循环结构,接口这样实现:
class PrintRunnable implements Runnable
@Override
public void run()
for(int i=0;i<10;i++)
System.out.println(Thread.currentThread().getName() + "正在运行!"+i);
运行结果1:
Thread-0正在运行!0
Thread-1正在运行!0
Thread-0正在运行!1
Thread-1正在运行!1
Thread-0正在运行!2
Thread-0正在运行!3
Thread-1正在运行!2
Thread-0正在运行!4
Thread-1正在运行!3
Thread-1正在运行!4
运行结果2:
Thread-0正在运行!0
Thread-1正在运行!0
Thread-0正在运行!1
Thread-1正在运行!1
Thread-0正在运行!2
Thread-1正在运行!2
Thread-0正在运行!3
Thread-1正在运行!3
Thread-0正在运行!4
Thread-1正在运行!4
…
可见随机性。
再改一下代码:
//实现Runnable接口
class PrintRunnable implements Runnable
int i = 0;
@Override
public void run()
for (; i < 5; i++)
System.out.println(Thread.currentThread().getName() + "正在运行!" + i);
public class Test
public static void main(String[] args)
// 线程1
PrintRunnable pr = new PrintRunnable();// 定义实现类对象
Thread t1 = new Thread(pr);// 接口是参数 创建一个线程
t1.start();// 启动线程
// 线程2
Thread t2 = new Thread(pr);
t2.start();
输出:显然变少了。——Runnable中的代码可以被多个线程共享,这适用于多个线程处理同一资源的情况,在这里多个线程即Thread-0和Thread-1,同一资源即i。
Thread-0正在运行!0
Thread-1正在运行!0
Thread-0正在运行!1
Thread-1正在运行!2
Thread-0正在运行!3
Thread-1正在运行!4
线程的状态和生命周期
线程的状态:
- 新建
New
:创建Thread
或Thread
子类对象时 - 可运行
Runnable
:已经创建好对象,去调用start
方法时——也成为就绪状态(线程:”我已经准备好了!“) - 正在运行
Running
:一个处于可运行状态的线程获取了CPU的使用权时 - 阻塞
Blocked
:线程遇到干扰 - 终止
Dead
线程的生命周期:其实是线程的五个状态的相互转换过程。
sleep方法的使用
是Thread
类的方法。格式如下:
public static void sleep(long millis)
作用:在指定的毫秒数内让正在执行的线程休眠(暂停、阻塞)。
参数为休眠时间,单位是毫秒。
join方法的使用
是Thread
类的方法。格式如下:(无参数)
public final void join()
作用:等调用该方法的线程结束后才能执行。——抢占资源。
代码如下:
class MyThread extends Thread
public void run()
for (int i = 0; i < 10; i++)
System.out.println(getName() + "该线程正在执行第" + i + "次");
public class ThreadTest
public static void main(String[] args)
MyThread mt = new MyThread();
mt.start();// 启动线程
try
mt.join();
catch (InterruptedException e)
// TODO Auto-generated catch block
e.printStackTrace();
for (int i = 0; i < 10; i++)
System.out.println("主线程运行第" + i + "次");
System.out.println("主线程运行结束");
输出:
Thread-0该线程正在执行第0次
Thread-0该线程正在执行第1次
Thread-0该线程正在执行第2次
Thread-0该线程正在执行第3次
Thread-0该线程正在执行第4次
Thread-0该线程正在执行第5次
Thread-0该线程正在执行第6次
Thread-0该线程正在执行第7次
Thread-0该线程正在执行第8次
Thread-0该线程正在执行第9次
主线程运行第0次
主线程运行第1次
主线程运行第2次
主线程运行第3次
主线程运行第4次
主线程运行第5次
主线程运行第6次
主线程运行第7次
主线程运行第8次
主线程运行第9次
主线程运行结束
从这个例子可以很清晰的看出来join
的作用——抢占资源。
有参数格式如下:
public final void join(long millis)
作用:等待该线程终止的最长时间为millis毫秒。
将代码改成:
class MyThread extends Thread
public void run()
for (int i = 0; i < 1000; i++)
System.out.println(getName() + "该线程正在执行第" + i + "次");
public class ThreadTest
public static void main(String[] args)
MyThread mt = new MyThread();
mt.start();// 启动线程
try
mt.join(1);
catch (InterruptedException e)
// TODO Auto-generated catch block
e.printStackTrace();
for (int i = 0; i < 10; i++)
System.out.println("主线程运行第" + i + "次");
System.out.println("主线程运行结束");
输出:这说明Thread-0
在1毫秒内执行了52次,然后把CPU让出了。
线程优先级
- Java为线程类提供了10个优先级
- 优先级可以用整数1-10表示,超过范围会抛出异常
- 主线程默认优先级为5
优先级常量
- MAX_PRIORITY:线程的最高优先级10
- MIN_PRIORITY:线程的最低优先级1
- NORM_PRIORITY:线程的默认优先级5
优先级相关的方法
方法 | 说明 |
---|---|
public int getPriority() | 获取线程优先级的方法 |
public void setPriority(int newPriority) | 设置线程优先级的方法 |
优先级的设置与操作系统的环境和CPU的工作方式有很大的关系——即使有优先级,程序的运行结果也会有随机性。
如代码:
class MyThread extends Thread
private String name;
public MyThread(String name)
this.name = name;
public void run()
for (int i = 0; i < 100; i++)
System.out.println("线程" + name + "正在执行第" + i + "次");
public class ThreadTest
public static void main(String[] args)
MyThread mt1 = new MyThread("线程1");
MyThread mt2 = new MyThread("线程2");
mt1.setPriority(Thread.MIN_PRIORITY);
mt2.setPriority(Thread.MAX_PRIORITY);
mt1.start();
mt2.start();
输出:
线程同步
- 各线程是通过竞争CPU时间而获得运行机会的
- 各线程什么时候得到CPU时间,占用多久,是不可预测的
- 一个正在运行着的线程在什么地方被暂停是不确定的
举个例子来说明同步的重要性:银行存取款。(存100取200)
银行类Bank
:
public class Bank
private String account;// 帐号
private int balance;// 账户余额
public Bank(String account, int balance)
this.account = account;
this.balance = balance;
public String getAccount()
return account;
public void setAccount(String account)
this.account = account;
public int getBalance()
return balance;
public void setBalance(int balance)
this.balance = balance;
@Override
public String toString()
return "Bank [account=" + account + ", balance=" + balance + "]";
// 存款
public void saveAccount()
int balance = getBalance();
balance += 100;
setBalance(balance);
System.out.println("存款后当前账户余额为" + balance);
// 取款
public void drawAccount()
int balance = getBalance();
balance -= 200;
setBalance(balance);
System.out.println("取款后当前账户余额为" + balance);
存钱SaveAccount
类:
public class SaveAccount implements Runnable
Bank bank;
public SaveAccount(Bank bank)
this.bank = bank;
@Override
public void run()
bank.saveAccount();
取钱DrawAccount
类:
public class DrawAccount implements Runnable
Bank bank;
public DrawAccount(以上是关于从零开始的Java开发1-6-3 多线程:概念Thread类和Runnable接口创建线程线程的状态和生命周期sleep和join方法优先级同步线程间通信的主要内容,如果未能解决你的问题,请参考以下文章