多线程概念
简单理解就是同时做多个事情,而不是一件一件的排队做,比如上厕所的时候玩手机。。。
多线程与平时的方法有啥区别呢?
可以看出,多线程
在执行的效率上是高于单线程
的
线程与进程
进程简单理解是一个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>
### 举个栗子
```java
class RunnableThread implements Runnable{
private String name;
public RunnableThread(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) {
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);
}
@Override
public 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;
@Override
synchronized 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 {
@Override
public 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 {
@Override
public 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 block
e.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()`:线程是否已经中断
```java
public boolean isInterrupted() {
return isInterrupted(false);
}
与前者不同的时,该方法不会清除状态,也就是两个打印都为true
停止线程-异常法
- 在run方法中使用for+break退出
示例:
public class MyThread extends Thread {
@Override
public 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下面还有语句,还是会继续运行,最好的解决办法是在判断中断内抛出异常,交由上一层处理
@Override
public 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 {
@Override
public 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;
}
@Override
public void run() {
while (true) {
i++;
}
}
}
public class Run {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.start();
Thread.sleep(5000);
// A
thread.suspend();
System.out.println("A= " + System.currentTimeMillis() + " i="
+ thread.getI());
Thread.sleep(5000);
System.out.println("A= " + System.currentTimeMillis() + " i="
+ thread.getI());
// B
thread.resume();
Thread.sleep(5000);
// C
thread.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 {
@Override
public 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中非守护线程的保姆,当所有其他线程都结束工作时,它才结束。