API文档 => java.lang => Thread,Runnable
线程的概述
创建线程的方式
- 创建一个Thread类,或者一个Thread子类的对象
- 创建一个实现Runnable接口的类的对象
Thread类
是一个线程类,位于java.lang包下
Runnable接口
- 只有一个方法run()
- Runnable是Java中用以实现线程的接口
- 任何实现线程的类都必须实现该接口
线程的创建
通过Thread类创建线程
案例1:简单的案例
- 这里有两个线程,主线程和副线程
- 副线程运行的时间是随机的,因为它什么时候获得CPU使用权不一定
- 线程不能多次启动,也就是说不能多次调用start方法 ```cpp package com.thread;
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 myThread = new MyThread(); myThread.start(); System.out.println(“主线程2”); } }
主线程1<br />主线程2<br />Thread-0该线程正在执行!
案例2:复杂的案例<br />可以明显看出,线程运行的随机性
```cpp
package com.thread;
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
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 myThread1 = new MyThread("线程1");
MyThread myThread2 = new MyThread("线程2");
myThread1.start();
myThread2.start();
}
}
执行结果可能是这样:
主线程1
线程1正在执行0
线程1正在执行1
线程1正在执行2
线程1正在执行3
线程1正在执行4
主线程2
线程2正在执行0
线程2正在执行1
线程2正在执行2
线程2正在执行3
线程2正在执行4
执行结果也可能是这样:
主线程1
线程1正在执行0
线程1正在执行1
线程1正在执行2
线程1正在执行3
线程1正在执行4
线程2正在执行0
线程2正在执行1
线程2正在执行2
线程2正在执行3
线程2正在执行4
主线程2
实现Runnable接口创建线程
已经有通过Thread类创建线程的方式,为什么要还需要此方式呢?
- Java不支持多继承
如果一个类已经继承了某个父类,那么就无法通过继承Thread类的方式创建线程
- 不打算重写Thread类的其他方法
一般来说,重写run方法就够我们用了,不打算重写其他方法的话,还是实现Runnable接口的方式比较方便
案例1:简单的例子
package com.runnable;
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在运行!");
}
}
public class RunnableTest {
public static void main(String[] args){
// 1. 定义Runnable类的对象
MyRunnable myRunnable = new MyRunnable();
// 2. 使用Runnable类的对象,创建线程类的对象
Thread thread = new Thread(myRunnable,"主线程");
// 3. 启动线程
thread.start();
}
}
运行结果:主线程正在运行!
案例2-1:复杂的例子
两个线程都运行5次
package com.runnable;
class MyRunnable implements Runnable{
@Override
public void run() {
int i=0;
while (i<5){
System.out.println(Thread.currentThread().getName() + "正在运行" + i++);
}
}
}
public class RunnableTest {
public static void main(String[] args){
MyRunnable myRunnable1 = new MyRunnable();
MyRunnable myRunnable2 = new MyRunnable();
Thread thread1 = new Thread(myRunnable1);
thread1.start();
Thread thread2 = new Thread(myRunnable2);
thread2.start();
}
}
Thread-0正在运行0
Thread-0正在运行1
Thread-0正在运行2
Thread-0正在运行3
Thread-0正在运行4
Thread-1正在运行0
Thread-1正在运行1
Thread-1正在运行2
Thread-1正在运行3
Thread-1正在运行4
案例2-2:复杂的例子
运行结果与案例2-1是一致的,两个线程都运行5次
package com.runnable;
class MyRunnable implements Runnable{
@Override
public void run() {
int i=0;
while (i<5){
System.out.println(Thread.currentThread().getName() + "正在运行" + i++);
}
}
}
public class RunnableTest {
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
thread1.start();
Thread thread2 = new Thread(myRunnable);
thread2.start();
}
}
Thread-0正在运行0
Thread-0正在运行1
Thread-0正在运行2
Thread-0正在运行3
Thread-0正在运行4
Thread-1正在运行0
Thread-1正在运行1
Thread-1正在运行2
Thread-1正在运行3
Thread-1正在运行4
案例2-3:复杂的例子
运行结果与案例2-1,2-2是一致的,两个线程都运行5次
package com.runnable;
class MyRunnable implements Runnable{
int i = 0;
@Override
public void run() {
while (i<5){
System.out.println(Thread.currentThread().getName() + "正在运行" + i++);
}
}
}
public class RunnableTest {
public static void main(String[] args){
MyRunnable myRunnable1 = new MyRunnable();
MyRunnable myRunnable2 = new MyRunnable();
Thread thread1 = new Thread(myRunnable1);
thread1.start();
Thread thread2 = new Thread(myRunnable2);
thread2.start();
}
}
Thread-0正在运行0
Thread-0正在运行1
Thread-0正在运行2
Thread-0正在运行3
Thread-0正在运行4
Thread-1正在运行0
Thread-1正在运行1
Thread-1正在运行2
Thread-1正在运行3
Thread-1正在运行4
案例2-4:复杂的例子
运行结果与案例2-1,2-2,2-3不一致,两个线程加起来运行5次
线程thread1和线程thread2共享同一个资源myRunnable,所以加起来运行5次之后就结束了。
也就是说,多个线程可以处理同一个资源
package com.runnable;
class MyRunnable implements Runnable{
int i = 0;
@Override
public void run() {
while (i<5){
System.out.println(Thread.currentThread().getName() + "正在运行" + i++);
}
}
}
public class RunnableTest {
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
thread1.start();
Thread thread2 = new Thread(myRunnable);
thread2.start();
}
}
Thread-0正在运行1
Thread-0正在运行2
Thread-0正在运行3
Thread-0正在运行4
Thread-1正在运行0
线程的状态和生命周期
获取cpu使用权的时间是不一定的
#Java已经不建议使用stop方法
sleep方法的使用
- Thread类的方法 pubic static void sleep(long millis)
- 作用是在指定毫秒数内让正在执行的线程休眠(暂停执行)
- 参数为休眠的时间,单位是毫秒
package com.sleep;
class MyThread implements Runnable{
@Override
public void run() {
for (int i=1;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行第"+i+"次");
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
public class SleepDemo {
public static void main(String[] args){
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
大概每隔1秒输出一次
为什么说是大概每隔1秒呢?Thread.sleep(1000);的意思是暂停线程1秒,暂停之后线程状态会切换到可运行(Runnable)状态,而在可运行状态下获取cpu使用权的时机是不一定的,也许是马上,可有可能是30ms之后,所以这里说大概每隔1秒。
join方法的使用
- Thread类的方法 pubic final void join(long millis)
- 作用是等待调用该方法的线程结束后主线程才能执行,也就是说,可以使主他线程由正在运行状态变成阻塞状态
- 参数为抢占时间
如果不写参数,那么调用该方法的线程结束后执行其他线程
如果写了参数,那么该时间结束后执行其他线程
package com.thread;
class MyThread extends Thread{
public void run(){
for (int i=0;i<5;i++){
System.out.println(getName()+"正在执行"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args){
MyThread myThread = new MyThread();
myThread.start();
try {
myThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程运行结束");
}
}
Thread-0正在执行0
Thread-0正在执行1
Thread-0正在执行2
Thread-0正在执行3
Thread-0正在执行4
主线程运行结束
重点:可以看到,一定是Thread-0线程结束之后,才会执行主线程的
线程的优先级
- Java为线程类提供了10个优先级
- 优先级可以用整数1-10表示,超过范围会抛出异常
- 主线程默认优先级为5
- 优先级常量
MAX_PRIORITY:线程的最高优先级10
MIN_PRIORITY:线程的最低优先级1
NORMAL_PRIORITY:线程的默认优先级5
- 优先级相关的方法
package com.thread;
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
public void run(){
for (int i=0;i<5;i++){
System.out.println("线程" + name + "正在执行"+i);
}
}
}
public class ThreadTest {
public static void main(String[] args){
// 获取主线程的优先级
int mainPriority = Thread.currentThread().getPriority();
System.out.println("主线程的优先级为" + mainPriority);
MyThread myThread1 = new MyThread("线程1");
MyThread myThread2 = new MyThread("线程2");
myThread1.setPriority(Thread.MAX_PRIORITY); // 跟设置为10是一样的
myThread2.setPriority(Thread.MIN_PRIORITY); // 跟设置为1是一样的
myThread2.start();
myThread1.start();
System.out.println("线程1的优先级为:" + myThread1.getPriority());
System.out.println("线程2的优先级为:" + myThread2.getPriority());
}
}
主线程的优先级为5
线程线程2正在执行0
线程线程2正在执行1
线程线程2正在执行2
线程线程2正在执行3
线程线程2正在执行4
线程1的优先级为:10
线程2的优先级为:1
线程线程1正在执行0
线程线程1正在执行1
线程线程1正在执行2
线程线程1正在执行3
线程线程1正在执行4
重点:虽然线程1的优先级比较高,但是并不是线程1线运行,这个CPU,操作系统都有关系。不过,虽然执行结果可能是随机的,但是大大提高了优先级高的线程优先执行的概率。
线程同步
普通线程(非同步)的特点
- 各个线程是通过竞争CPU时间而获得运行机会的
- 各线程什么时候得到CPU时间,占用多久,是不可预测的
- 一个正在运行着的线程在什么地方暂停是不确定的
普通线程(非同步)因为具有以上特点,所以当两个线程操作一个共享对象的时候,可能会出现问题。
让我们通过一个银行的存取款例子,来说明这种可能发生的问题。
问题描述
Bank.java
package com.process;
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 "账号:"+account+",余额:"+balance;
}
// 存款
public void saveAccount(){
int balance = getBalance();
balance += 100;
setBalance(balance);
System.out.println("存款后的余额为:"+balance);
}
// 取款
public void drawAccount(){
int balance = getBalance();
balance -= 200;
// 通过sleep方法模拟被其他线程中断的情况
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(balance);
System.out.println("取款后的余额为:"+balance);
}
}
SaveAccount.java
package com.process;
public class SaveAccount implements Runnable{
Bank bank;
public SaveAccount(Bank bank){
this.bank = bank;
}
@Override
public void run() {
bank.saveAccount();
}
}
DrawAccount.java
package com.process;
public class DrawAccount implements Runnable {
Bank bank;
public DrawAccount(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.drawAccount();
}
}
Test.java
package com.process;
public class Test {
public static void main(String[] args){
// 创建账户,给定余额为1000
Bank bank = new Bank("song",1000);
// 创建线程对象
SaveAccount saveAccount = new SaveAccount(bank);
DrawAccount drawAccount = new DrawAccount(bank);
Thread save = new Thread(saveAccount);
Thread draw = new Thread(drawAccount);
draw.start();
save.start();
try {
draw.join();
save.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 以上的join()是为了让这个输出语句最后执行
System.out.println(bank);
}
}
join()方法在主方法中,只能保证调用join()方法的线程执行完才能执行主线程,但是影响不了主方法中的另一个线程。也就是说,draw.join()方法可以保证draw()方法执行完后,再执行主线程,但是不影响save线程的执行。
运行结果如下,这显然是不对的,银行系统出现这种问题,后果将是灾难性的。
存款后的余额为:1100
取款后的余额为:800
账号:song,余额:800
我们来分析下为什么会发生这种情况,主要原因是因为线程中断而没有及时更新银行对象的余额。
- 先取款,取款线程中发生的事情是这样的:
函数的balance值更新为800,被中断
- 再存款,存款线程中发生的事情是这样的
取得银行对象的balance值为1000(因为取款线程被中断了,所以余额还未更新)
函数的balance值更新为1100
银行对象的balance值更新为1100
输出,即“存款后的余额为:1100”
- 回到取款线程
银行对象的balance值更新为800
输出,即“取款后的余额为:800”
- 回到主程序,输出“账号:song,余额:800”
那该怎么防止这种情况的发生呢?这时候就需要用到线程同步了
保证在存款或取款的时候,不允许其他线程对账户余额进行操作
需要将Bank对象进行锁定
使用同步关键字synchronized,实现共享对象在同一时刻只能被一个线程访问
synchronized关键字
成员方法 public synchronized void saveAccount(){}
静态方法 public static synchronized void saveAccount(){}
语句块 synchronized(obj){ … }
解决方案
package com.process;
public class Bank {
private String account;
private int balance;
。。。无修改,省略。。。
// 存款
public synchronized void saveAccount(){
int balance = getBalance();
balance += 100;
setBalance(balance);
System.out.println("存款后的余额为:"+balance);
}
// 取款
public void drawAccount(){
synchronized (this){
int balance = getBalance();
balance -= 200;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
setBalance(balance);
System.out.println("取款后的余额为:"+balance);
}
}
}
运行结果如下:
取款后的余额为:800
存款后的余额为:900
账号:song,余额:900
或者
存款后的余额为:1100
取款后的余额为:900
账号:song,余额:900
线程间通信
wait()方法:中断方法的执行,使线程等待
notify()方法:唤醒处于等待的某一个线程,使其结束等待
notifyAll()方法:唤醒处于等待的所有线程,是它们结束等待
Queue.java
package com.queue;
public class Queue {
private int n;
boolean isActive = false;
public synchronized int getN() {
if(!isActive){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费:"+n);
isActive = false;
notifyAll(); // 避免死锁问题
return n;
}
public synchronized void setN(int n) {
if (isActive){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产:"+n);
this.n = n;
isActive = true;
notifyAll(); // 避免死锁问题
}
}
Consumer.java
package com.queue;
public class Consumer implements Runnable {
Queue queue;
public Consumer(Queue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
queue.getN();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Producer.java
package com.queue;
public class Producer implements Runnable{
Queue queue;
public Producer(Queue queue) {
this.queue = queue;
}
@Override
public void run() {
int i=0;
while (true){
queue.setN(i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Test.java
package com.queue;
public class Test {
public static void main(String[] args){
Queue queue = new Queue();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}