JAVA并发编程(一)为什么需要线程,java 如何创建多线程?
线程介绍
线程(Thread)是程序运行的执行单元,依托于进程存在。一个进程中可以包含多个线程,多线程可以共享一块内存空间和一组系统资源,因此线程之间的切换更加节省资源、更加轻量化,因而也被称为轻量级的进程。
什么是进程
进程(Processes)是程序的一次动态执行,是系统进行资源分配和调度的基本单位,是操作系统运行的基础,通常每一个进程都拥有自己独立的内存空间和系统资源。简单来说,进程可以被当做是一个正在运行的程序。
为什么需要线程
程序的运行必须依靠进程,进程的实际执行单元就是线程。
为什么需要多线程
多线程可以提高程序的执行性能。例如,有个 90 平方的房子,一个人打扫需要花费 30 分钟,三个人打扫就只需要 10 分钟,这三个人就是程序中的“多线程”。
java 创建多线程的三种方式
- 继承 Thread 类,重写 run 方法
- 实现 Runnable 接口,实现 run 方法
- 实现 Callable 接口,实现 call 方法
下面分别来看看线程创建和使用的具体代码。
1)继承 Thread 类
请参考以下代码:
class ThreadTest {
public static void main(String[] args) throws Exception {
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread");
}
}
程序执行结果:
Thread
2)实现 Runnable 接口
请参考以下代码:
class ThreadTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable");
}
}
程序执行结果:
Runnable
3)实现 Callable 接口
class ThreadTest {
public static void main(String[] args) throws Exception {
MyCallable callable = new MyCallable();
// 定义返回结果
FutureTask<String> result = new FutureTask(callable);
// 执行程序
new Thread(result).start();
// 输出返回结果
System.out.println(result.get());
}
}
class MyCallable implements Callable {
@Override
public String call() {
System.out.println("Callable");
return "Success";
}
}
以上程序执行结果如下:
Callable
Success
可以看出,Callable 的调用是可以有返回值的,它弥补了之前调用线程没有返回值的情况,它是随着 JDK 1.5 一起发布的。
4)JDK 8 创建线程
JDK 8 之后可以使用 Lambda 表达式很方便地创建线程,请参考以下代码:
new Thread(() -> System.out.println("Lambda Of Thread.")).start();
线程高级用法
线程等待
使用 wait() 方法实现线程等待,代码如下:
System.out.println(LocalDateTime.now());
Object lock = new Object();
Thread thread = new Thread(() -> {
synchronized (lock){
try {
// 1 秒钟之后自动唤醒
lock.wait(1000);
System.out.println(LocalDateTime.now());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
以上程序执行结果如下:
2019-06-22T20:53:08.776
2019-06-22T20:53:09.788
注意:当使用 wait() 方法时,必须先持有当前对象的锁,否则会抛出异常 java.lang.IllegalMonitorStateException。
线程唤醒
使用 notify()/notifyAll() 方法唤醒线程。
- notify() 方法随机唤醒对象的等待池中的一个线程;
- notifyAll() 唤醒对象的等待池中的所有线程。
使用如下:
Object lock = new Object();
lock.wait();
lock.notify();
// lock.notifyAll();
线程休眠
// 休眠 1 秒
Thread.sleep(1000);
等待线程执行完成
Thread joinThread = new Thread(() -> {
try {
System.out.println("执行前");
Thread.sleep(1000);
System.out.println("执行后");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
joinThread.start();
joinThread.join();
System.out.println("主程序");
以上程序执行结果:
执行前
执行后
主程序
yield 交出 CPU 执行权
new Thread(){
@Override
public void run() {
for (int i = 1; i < 10; i++) {
if (i == 5) {
// 让同优先级的线程有执行的机会
this.yield();
}
}
}
}.start();
注意:yield 方法是让同优先级的线程有执行的机会,但不能保证自己会从正在运行的状态迅速转换到可运行的状态。
线程中断
使用 System.exit(0) 可以让整个程序退出;要中断单个线程,可配合 interrupt() 对线程进行“中断”。
使用代码如下:
Thread interruptThread = new Thread() {
@Override
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
System.out.println("i:" + i);
if (this.isInterrupted()) {
break;
}
}
}
};
interruptThread.start();
Thread.sleep(10);
interruptThread.interrupt();
线程优先级
在 Java 语言中,每一个线程有一个优先级,默认情况下,一个线程继承它父类的优先级。可以使用 setPriority 方法设置(1-10)优先级,默认的优先级是 5,数字越大表示优先级越高,优先级越高的线程可能优先被执行的概率就越大。
设置优先级的代码如下:
Thread thread = new Thread(() -> System.out.println("Java"));
thread.setPriority(10);
thread.start();
死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
比如,当线程 A 持有独占锁 a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 A B 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
死锁示意图如下所示:
死锁代码:
Object obj1 = new Object();
Object obj2 = new Object();
// 线程1拥有对象1,想要等待获取对象2
new Thread() {
@Override
public void run() {
synchronized (obj1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2) {
System.out.println(Thread.currentThread().getName());
}
}
}
}.start();
// 线程2拥有对象2,想要等待获取对象1
new Thread() {
@Override
public void run() {
synchronized (obj2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1) {
System.out.println(Thread.currentThread().getName());
}
}
}
}.start();
- 扫码关注“火龙果编程”公众号,早日成为编程大神
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。