什么是线程
进程:是操作系统进行资源分配的最小单位。
线程:是一个进程内部的最小执行单元,是操作系统进行任务调度的最小单元,隶属于进程。
如何创建线程
- 继承Thread类,本质上也是实现Runnable接口
public class MyThread extends Thread {
@Override //重写Run方法
public void run() {
}
}
// 调用
MyThread thread = new MyThread();
thread.start() //启动线程时,执行Run()方法
- 实现Runnable接口
定义:
public class MyThread implements Runnable{
@Override
public void run() {
}
}
//调用
MyThread mythread = new MyThread();
//创建一个线程作为外壳,将r包起来,
Thread thread = new Thread(mythread);
thread.start();
继承方式和实现方式的联系与区别
区别:
继承Thread:线程代码存放在Thread子类MyThread的Run方法中
实现Runnable:存放在接口的子类的run方法中
实现Runnable的好处
- 避免单线程的局限性。如果一个类已经继承了其他类,就不能再继承Thead类来实现多线程。
- Runnable接口只有一个抽象方法,不需要再继承Thread类的构造函数和其他方法,代码实现更加简洁。
- 可以将任务逻辑与线程调度逻辑分离,使得代码更加模块化,方便扩展和重用。
public class MyThread implements Runnable{
//任务逻辑
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("MyThread:"+i);
}
}
}
public class Test {
public static void main(String[] args) {
//创建一个线程中需要执行的任务,并没有真正的创建线程
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
//启动线程
thread.start();
//主线程逻辑
for (int i = 0; i < 1000; i++) {
System.out.println("main:"+i);
}
}
}
输出结果可以看到主线程和子线程交替执行,展现了Runnable接口实现多线程任务的效果。
Thread类中的方法
常用方法
方法原型 | 说明 |
---|---|
void start() | 启动线程 |
final String getName() | 返回线程的名称 |
final void setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 返回线程的优先级 |
final void join() | 等待线程终止 |
static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
static void sleep(long millis) | 让当前正在执行的线程休眠(暂停执行),休眠时间由milli s(毫秒)指定 |
yield() | 线程让步 |
线程优先级
计算机只有一个CPU,各个线程轮流获得使用权,优先级较高的线程有更多机会获得CPU的机会。
一般情况下,线程的默认优先级是5,可以通过setPriority()来设置优先级
调度方法
同优先级使用时间片策略,是先进先出形成队列。
高优先级使用抢占式策略
线程生命周期
-
新建状态:使用new关键字和Thread类或其子类创建一个线程对象后,该线程对象就处于新建状态。保持直到程序start()这个线程。
-
就绪状态:当线程对象调用了start()方法之后,就到了就绪状态。就绪状态处于就绪队列中,然后等待JVM里线程调度器调用。
-
运行状态:就绪状态获取到CPU资源就可以执行run()方法,此时便是运行状态。
-
阻塞状态:如果一个线程执行了sleep(睡眠),挂起等方法,失去所占用的资源后,就会从运行状态变为阻塞状态。睡眠时间已到,或者重新获取资源后,可进入就绪状态。
-
死亡状态:一个线程执行完成或其他终止条件发生时,就切换到死亡状态。
多线程
定义:多线程是指程序中包含多个执行单元,可以并发执行。
用处:程序需要同时执行俩个或多个任务。
优点:提高CPU的利用率,,提高程序的响应,
缺点:线程也是程序,线程越多所占有的内存就越多。多线程需要协调和管理,所以需要CPU跟踪线程。
线程同步
并发与并行
并发:俩个或多个程序在同一时间间隔内发生。宏观上是同时发生的,微观上是轮流交替发生的。
并行:俩个或多个程序在同一时刻发生。
单核CPU同一时刻只能执行一个程序,只能并发执行。
多核CPU同一时刻可以同时执行多个程序,并行执行。
多线程同步
多线程同时读写到一份共享资源时,可能会引发冲突,所以出现线程同步机制。
同步就是 排队+锁
几个线程之间要排队,一个一个对资源进行操作,而不是同时操作。
为了保证数据被正确访问,加入锁。
确保一个时间点只有一个线程访问共享资源。可以给共享资源加入锁。
//使用synchronized(同步锁)关键字同步方法或代码块。
synchronized (同步锁){
// 需要被同步的代码;
}
//synchronized还可以放在方法声明中,表示整个方法,为同步方法。
public synchronized void show (String name){
// 需要被同步的代码;
}
同步锁可以是任何对象,但必须是唯一的,保证多线程获得的师同一个对象(充当锁标记)。
同步执行过程
- 第一个线程访问,锁定同步对象,执行其中的代码
- 第二个线程访问,发现同步对象被锁定,无法访问。
- 第一个线程执行完毕,释放同步对象。
- 第二个线程访问,发现同步对象没有锁,然后锁定并访问。
Lock锁
一个线程持有锁会导致其他需要此锁的线程挂起。在多线程竞争下,加锁,释放锁会导致较多的上下文切换和调度延时,引起性能问题。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源访问的工具。
每次只能有一个线程对Lock对象加锁,线程开始访问共享资源前应该获得lock对象
Lock lock = new ReentrantLock();
lock.lock();//使用开始前加锁
此处是对共享资源的访问。
lock.unlock();//使用完毕后必须释放锁
线程死锁
在并发环境下,各进程因为资源竞争而造成的相互等待对方手里的资源,造成线程阻塞,都无法向前推进的现象,就是死锁。
什么是死锁,死锁产生的必要条件,如何避免锁
线程通信
指的是线程之间的相互作用。
三个方法
.wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
.notify一旦执行此方法,就会唤醒被wait的一个线程。有多个线程被wait,唤醒优先级最高的。
.notifyAll一旦执行此方法,就会唤醒所有被wait的线程。
.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
新增创建线程方式
实现Callable接口与使用Runnable相比,Callable功能更强大些。
• 相比run()方法,可以有返回值
• 方法可以抛出异常
• 支持泛型的返回值
• 需要借助FutureTask类,获取返回结果
public class Sum implements Callable<Integer> {
int sum=0;
@Override
public Integer call() throws Exception {
for (int i = 0; i <= 10; i++) {
sum+=i;
}
return sum;
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Sum sum = new Sum();
//接收任务
FutureTask<Integer> futureTask = new FutureTask<>(sum);
Thread thread = new Thread(futureTask);//创建次任务的线程
thread.start();
Integer s = futureTask.get();//获取线程中的返回值
System.out.println(s); // 打印结果55
}
}