心得感悟
多线程的基本概念还是很容易理解的,以前只知道软件可以同时运行,今天终于了解了它的实现机制,还是有点意思的。多线程入门是比较简单,进门后再往前走那就有点难了,所以今天先不讲线程安全。
内容简概
- 一、多线程介绍
- 二、多线程的状态和生命周期
- 三、为什么要创建⼦线程
- 四、创建线程的两种⽅式
- 五、实现线程同步的两种方式
具体内容
一、多线程介绍
再说线程之前,我们先来了解一下进程。什么是进程呢?进程就是正在运行的程序
。你可以按下Ctrl + Shift + ESC键调出任务管理器,里面会显示出当前正在运行的进程(见图一)。应用名称最后的小括号内的数字表示当前进程包含的线程个数
,你也可以展开列表查看具体的线程(见图二)。
一个进程中至少有一个线程
。一个进程中是可以有多个线程
的,这个应用程序也可以称之为多线程程序。
拿下载文件、视频举例,当你要下载多个文件或视频时,若是单线程
,只有当一个下载任务执行完后,才能执行另一个下载任务;若是多线程
,可以多个下载任务“同时”执行,这样大大提高了程序的运行效率,让CPU的使用率更高。


二、多线程的状态和生命周期
状态名 | 定义 |
---|---|
创建状态(new) | 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存 |
就绪状态(runnable) | 调用了start()方法, 等待CPU进行调度 |
运行状态(running) | 执行run()方法 |
阻塞状态(blocked) | 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用 |
死亡状态(terminated) | 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止) |

三、为什么要创建⼦线程
如果在主线程中存在比较耗时的操作,如下载视频、数据处理,这些操作会阻塞主线程,后面的任务必须等这些任务执行完后才能执行。为了不阻塞主线程
,需要将耗时的任务放在子线程中去处理。
四、创建线程的两种⽅式
1. 第一种方式:Thread
写一个类,使它继承
Thread
class TestThread extends Thread{
// 写一个类继承Thread
// 实现run方法
// 方法里面是具体要执行的代码
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name);
for (int i =1 ; i <= 100; i++) {
System.out.println(name + ":" +i);
}
super.run();
}
}
使用方式:使用
Thread
操作这个任务,然后用setName()
给进程命名,用start()
开始进程。
Thread t = new Thread(pt);
t.setName("子线程1");
t.start();
2. 第二种方式:Runnable
写一个类,使它实现
Runnable
接⼝
class MyThread implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
使用方式①:使用
Thread
操作这个任务,然后用setName()
给进程命名,用start()
开始进程。
Thread t = new Thread(mt);
t.setName("子线程1");
t.start();
使用方式②:使用匿名对象,然后直接在run()中写入执行语句,同样用
setName()
给进程命名,用start()
开始进程。这个方式仅适用于只使用一次这个任务的情况。
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
});
t.setName("子线程3");
t.start();
使用方式③:创建线程的同时直接开启线程任务,不需要操作线程对象本身,但是无法为这个进程命名。
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+";"+i);
}
}
}).start();
使用方式④:使用Lambda表达式,但事实不建议使用,因为阅读性差。
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+";"+i);
}
}).start();
五、实现线程同步的两种方式
1. 第一种方式:synchronized
使⽤
synchronized
同步代码块。我们写一个卖火车票的程序。
public class lock {
public static void main(String[] args){
Ticket ticketCQ = new Ticket("重庆");
Thread t1 = new Thread(ticketCQ);
t1.start();
Ticket ticketSH = new Ticket("上海");
Thread t2 = new Thread(ticketSH);
t2.start();
}
}
class Ticket implements Runnable{
//定义所有车票的数量
public static int num = 100;
String name;
//定义
public Ticket(String name){
this.name = name;
}
static final Object obj = new Object();
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
// 判断有没有票
synchronized (obj){
// 需要同步的代码
if (num > 0) {
System.out.println(name + "出票:" + num);
num--;
}
else {
break;
}
}
}
}
}
也可以用同步方法实现,同步方法的本质其实就是同步代码块。只需将部分代码改为如下代码即可。
@Override
public void run() {
synchronized (this){
test();
}
}
public synchronized void test() {
for (int i = 1; i <= 100; i++) {
// 判断有没有票
synchronized (obj){
// 需要同步的代码
if (num > 0) {
System.out.println(name + "出票:" + num);
num--;
}
else {
break;
}
}
}
}
2. 第二种方式:ReentrantLock
使⽤
ReentrantLock
同步
class Ticket implements Runnable{
//定义所有车票的数量
public static int num = 100;
String name;
//定义
public Ticket(String name){
this.name = name;
}
//创建一个可重复的锁
static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
// 判断有没有票
// 加锁
lock.lock();
//需要同步的代码
if (num > 0) {
System.out.println(name + "出票:" + num);
num--;
lock.newCondition();
}
else {
break;
}
lock.unlock();
}
}
}
网友评论