多线程概念
简单理解就是同时做多个事情,而不是一件一件的排队做,比如上厕所的时候玩手机。。。
多线程与平时的方法有啥区别呢?
可以看出,多线程在执行的效率上是高于单线程的
线程与进程
进程简单理解是一个IDEA运行、一个微信运行等,在操作系统中运行的程序就称为进程,但是一个微信中可以包括多个事务的运行,如聊天的同时接收到他人消息、订阅号通知等。可见,线程与进程的关系为:一个进程包括多个线程。
进程 :执行程序的一次执行过程,动态概念,是系统资源分配的单位。
线程 :CPU调度和执行的单位,一个进程中至少包含一个线程。
注意:
- 很多多线程是模拟出来,真正的多线程是指有多个
_CPU_(服务器),单个CPU情况下,同一时间点只能执行一个代码,但切换很快,所以出现同时执行的错觉。- 程序运行时,即使没有自己创建线程,后台也会有多个线程,main线程、gc线程。
- 同一个进程中,如果开辟多线程,线程的运行由调度器安排调度,调度器与系统相关,执行的先后顺序不能人为干预。
- 对同一份资源操作时,存在资源抢夺问题,需要加入并发控制。
- 线程会带来额外的开销,CPU调度时间等。
线程创建
继承Thread
创建步骤
- 自定义线程类继承
Thread类 - 重写
run()方法,线程执行体编写 -
举个栗子
本栗实现简单的线程创建和执行,线程执行顺序由调度器安排,无法人为控制 ```java /**
@author 相彪 */ public class OneThread extends Thread{
private String name;
public OneThread(String name) {
this.name = name;
}
@Override public void run() {
for (int i = 0; i < 20; i++) {try {Thread.sleep(1000);System.out.println(name + " running : " + i);} catch (InterruptedException e) {e.printStackTrace();}}
}
public static void main(String[] args) {
OneThread oneThread = new OneThread("Thread 1 ");OneThread twoThread = new OneThread("Thread 2 ");OneThread threeThread = new OneThread("Thread 3 ");OneThread fourThread = new OneThread("Thread 4 ");OneThread fiveThread = new OneThread("Thread 5 ");oneThread.start();twoThread.start();threeThread.start();fourThread.start();fiveThread.start();
} }
// 部分结果 Thread 5 running : 17 Thread 3 running : 17 Thread 1 running : 17 Thread 2 running : 17 Thread 4 running : 17 Thread 4 running : 18 Thread 2 running : 18 Thread 1 running : 18 Thread 3 running : 18 Thread 5 running : 18
从结果可见,线程不能按照代码编写的顺序执行
<a name="Xzzov"></a>## 实现Runnable<a name="g37C7"></a>### 创建步骤- 定义一个类实现`Runnable`接口- 实现`run()`方法,内容编写- 创建线程对象,调用`start()`方法启动线程<a name="I3DEB"></a>### 举个栗子```javaclass RunnableThread implements Runnable{private String name;public RunnableThread(String name) {this.name = name;}@Overridepublic void run() {for (int i = 0; i < 20; i++) {try {Thread.sleep(1000);System.out.println(name + " running : " + i);} catch (InterruptedException e) {e.printStackTrace();}}}public static void main(String[] args) {RunnableThread oneThread = new RunnableThread("Thread 1 ");RunnableThread twoThread = new RunnableThread("Thread 2 ");RunnableThread threeThread = new RunnableThread("Thread 3 ");RunnableThread fourThread = new RunnableThread("Thread 4 ");RunnableThread fiveThread = new RunnableThread("Thread 5 ");new Thread(oneThread).start();new Thread(twoThread).start();new Thread(threeThread).start();new Thread(fourThread).start();new Thread(fiveThread).start();}}
该栗子在Thread基础上修改,同样的,得出结果可见线程执行不会按照编写顺序。
对比
两种方式都具备多线程能力,但启动方式不同
继承Thread:子类对象.start()
实现Runnable:传入目标对象+Thread对象.start()
但实现Runnable更推荐,可以避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用。
实例变量与线程安全
自定义线程类中的实例变量针对其他线程存在共享与不共享的区别。
不共享
各自线程都拿到各自的变量进行操作,不互相影响
public class MyThread extends Thread {private int count = 5;public MyThread(String name) {super();this.setName(name);}@Overridepublic void run() {super.run();while (count > 0) {count--;System.out.println("�� " + this.currentThread().getName()+ " ���的count=" + count);}}}public class Run {public static void main(String[] args) {MyThread a = new MyThread("A");MyThread b = new MyThread("B");MyThread c = new MyThread("C");a.start();b.start();c.start();}}
共享
也就是多个线程公用一个变量,容易想到的是购买商品时的库存数量变化
如果还执行上面的代码,那么count数量会凌乱。在某些JVM中进行i--分为三步
- 获取原有i值
- 计算i-1
- 对i进行赋值
在以上3个步骤中,若多个线程同时访问,一定会出现线程安全问题。可对上述代码进行如下更改:
public class MyThread extends Thread {private int count = 5;@Overridesynchronized public void run() {super.run();count--;System.out.println("�� " + this.currentThread().getName()+ " ���的count=" + count);}}
但此时还需要注意另一个问题:i--与System.out.println
println源码如下:
public void println(String x) {synchronized (this) {print(x);newLine();}}
可见它是线程全的,但是i--并非线程安全,需要进一步优化,如改成AtomicInteger
方法
currentThread()
isAllive
判断当前线程是否处于活跃状态
class MyThread extends Thread {@Overridepublic void run() {System.out.println("run=" + this.isAlive());}}public static void main(String[] args) {MyThread mythread = new MyThread();System.out.println("begin ==" + mythread.isAlive());mythread.start();System.out.println("end ==" + mythread.isAlive());}
sleep
在指定的毫秒数内让当前正在执行的线程休眠(暂停运行),该线程指 this.currentThread()返回的线程
public class MyThread1 extends Thread {@Overridepublic void run() {try {System.out.println("run threadName="+ this.currentThread().getName() + " begin");Thread.sleep(2000);System.out.println("run threadName="+ this.currentThread().getName() + " end");} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
getId
返回线程的唯一标识
停止线程
java中有以下3种方法可以终止正在运行的线程
- 使用退出标志,让线程正常退出,即run方法完成后退出
- 使用stop强行终止(不推荐、已过期,会产生不可预料结果)
-
判断线程是否停止
Thread中提供了2种方法来判断 this.interrupted():当前线程是否已经中断(当前线程:指运行this.interrupted的线程)public static boolean interrupted() {return currentThread().isInterrupted(true);}
示例 ```java public class MyThread extends Thread { @Override public void run() {
super.run();for (int i = 0; i < 500000; i++) {System.out.println("i=" + (i + 1));}
} }
public class Run2 { public static void main(String[] args) { Thread.currentThread().interrupt(); System.out.println(“是否停止1=” + Thread.interrupted()); System.out.println(“是否停止2=” + Thread.interrupted()); System.out.println(“end!”); } }
// 结果 是否停止1=true 是否停止2=false end!
从结果来看,方法interrupted确实判断出当前线程是否为停止状态,但为什么第2个布尔值为false:测试当前线程是否已经中断,线程中的中断状态由该方法清除。也就是说,如果连续两次调用该方法,则第二次返回false。<br />interrupted方法具有清除状态的功能,所以第二次调用返回false。2. `this.isInterrupted()`:线程是否已经中断```javapublic boolean isInterrupted() {return isInterrupted(false);}
与前者不同的时,该方法不会清除状态,也就是两个打印都为true
停止线程-异常法
- 在run方法中使用for+break退出
示例:
public class MyThread extends Thread {@Overridepublic void run() {super.run();for (int i = 0; i < 500000; i++) {if (this.interrupted()) {System.out.println("停止状态!退出!");break;}System.out.println("i=" + (i + 1));}}}public static void main(String[] args) {try {MyThread thread = new MyThread();thread.start();Thread.sleep(2000);thread.interrupt();} catch (InterruptedException e) {System.out.println("main catch");e.printStackTrace();}System.out.println("end!");}
上述方法虽然可以停止线程,但是如果for下面还有语句,还是会继续运行,最好的解决办法是在判断中断内抛出异常,交由上一层处理
@Overridepublic void run() {super.run();try {for (int i = 0; i < 500000; i++) {if (interrupted()) {System.out.println("停止状态!退出!");throw new InterruptedException();}System.out.println("i=" + (i + 1));}System.out.println("继续");} catch (InterruptedException e) {System.out.println("进入异常范围");e.printStackTrace();}}
停止线程-暴力停止
stop方法停止线程,但是该方法不推荐,因为会产生额外的问题,已作废。
如下简单示例
public class MyThread extends Thread {@Overridepublic void run() {try {this.stop();} catch (ThreadDeath e) {System.out.println("catch()");e.printStackTrace();}}}public static void main(String[] args) {MyThread thread = new MyThread();thread.start();}
结果:
该方法已被作废,如果强制让线程停止,则有可能让一些清理性的工作无法完成。另一种情况是对加锁的对象进行“解锁”,导致数据得不到同步处理。其在功能上具有缺陷,所以不建议在程序中使用该方法。
暂停线程
暂停就意味着线程还可以恢复运行,可以使用suspend和resume来进行暂停、恢复线程
使用
public class MyThread extends Thread {private long i = 0;public long getI() {return i;}public void setI(long i) {this.i = i;}@Overridepublic void run() {while (true) {i++;}}}public class Run {public static void main(String[] args) {try {MyThread thread = new MyThread();thread.start();Thread.sleep(5000);// Athread.suspend();System.out.println("A= " + System.currentTimeMillis() + " i="+ thread.getI());Thread.sleep(5000);System.out.println("A= " + System.currentTimeMillis() + " i="+ thread.getI());// Bthread.resume();Thread.sleep(5000);// Cthread.suspend();System.out.println("B= " + System.currentTimeMillis() + " i="+ thread.getI());Thread.sleep(5000);System.out.println("B= " + System.currentTimeMillis() + " i="+ thread.getI());} catch (InterruptedException e) {e.printStackTrace();}}}

从结果可见,线程的确被暂停,而且还可以恢复成运行状态,但是有弊端
独占
如果使用不当,容易造成公共的同步对象独占,导致其他线程无法访问到同步对象。
不同步
使用时容易出现因为线程的暂停导致数据不同步的情况。
目前,suspend和resume已经被标为作废。
yield
放弃当前的CPU资源,让给其他线程占用,但放弃的时间不确定,有可能刚刚放弃,又会立刻获得CPU时间片。
public class MyThread extends Thread {@Overridepublic void run() {long beginTime = System.currentTimeMillis();int count = 0;for (int i = 0; i < 50000000; i++) {// Thread.yield();count = count + (i + 1);}long endTime = System.currentTimeMillis();System.out.println("用时:" + (endTime - beginTime));}}public static void main(String[] args) {MyThread thread = new MyThread();thread.start();}
未放开注释:
放开注释:
优先级
线程的优先级分为1-10这10个等级,设置优先级源码
public final void setPriority(int newPriority) {ThreadGroup g;checkAccess();if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {throw new IllegalArgumentException();}if((g = getThreadGroup()) != null) {if (newPriority > g.getMaxPriority()) {newPriority = g.getMaxPriority();}setPriority0(priority = newPriority);}}
注意:
- 线程的优先级具有继承性,如A线程启动B线程,则B线程与A线程优先级一样。
- 但当优先级的等级差距很大时,谁先执行完和代码的调用顺序无关,CPU尽量将执行资源让给优先级较高的线程执行,但不是绝对,优先级具有一定的规则性。
- 优先级高的线程并不一定要执行完再执行优先级低的线程,
- 优先级高的线程运行的较快
守护线程
任何一个守护线程都是整个JVM中非守护线程的保姆,当所有其他线程都结束工作时,它才结束。
