线程学习记录一
多线程相关概念
- 线程是程序内部的一条执行路径,main函数相当于程序的主线程,是一条单独的执行路径。
- 如果程序中只有一条执行路径,那么这个程序就是单线程的程序。
- 多线程是指从软硬件上实现多条执行流程的技术。
- 一个应用程序相当于系统中的一个进程,进程是线程的集合。
多线程的创建
Java 是通过 java.lang.Thread 类来代表线程的。
方式一、继承 Thread 类并重写 run 方法
public class ThreadTest {
public static void main(String[] args) throws Exception {
// 3.创建 Thread 类对象
Thread t = new MyThread();
// 4.调用 start 方法启动线程
t.start();
// 主线程任务需放在子线程创建之后,否则相当于单线程
for (int i = 0; i < 10; i++) {
System.out.println(i + "---main");
}
}
}
// 1.定义一个子类 MyThread 继承线程类java.lang.Thread
class MyThread extends Thread {
// 2.重写run()方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i + "---child");
}
}
}
Q:为什么不直接调用 run 方法,而调用 start 方法启动线程?
A:直接调用 run 方法会当成普通方法执行,此时相当于还是单线程执行;只有调用start方法才是启动一个新的线程执行。
方式二、实现 Runnable 接口
public class ThreadTest {
public static void main(String[] args) throws Exception {
// 方式一、
// 3.创建 Runnable 任务对象
Runnable r = new MyRunnable();
// 4.把任务对象交给 Thread 线程类处理
Thread t = new Thread(r);
// 5.调用 start 方法启动线程
t.start();
//方式二、
//使用匿名内部类形式实现 Runnable 接口,再交给 Thread 线程类处理,调用 start 方法启动线程
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i + "---runnableChild1");
}
}).start();
// 主线程任务需放在子线程创建之后,否则相当于单线程
for (int i = 0; i < 10; i++) {
System.out.println(i + "---main");
}
}
}
// 1.定义一个线程任务类 MyRunnable 实现 Runnable 接口
class MyRunnable implements Runnable{
// 2.重写 run 方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i + "---runnableChild");
}
}
}
方式三、JDK 5.0新增:实现Callable接口
public class ThreadTest {
public static void main(String[] args) throws Exception {
// 3.创建 Callable 对象,传入参数100
Callable<Integer> c = new MyCallable(100);
// 4.使用 FutureTask 把 Callable 对象封装成线程任务对象
FutureTask<Integer> ft = new FutureTask<>(c);
// 5.把线程任务对象交给 Thread 处理(FutureTask<V> 是 RunnableFuture<V> 的实现类,而RunnableFuture<V> 接口继承了 Runnable 接口)
Thread t = new Thread(ft);
// 6.调用 start 方法启动线程,执行任务
t.start();
// 7.线程执行完毕后,通过 FutureTask 的 get() 方法去获取任务执行的结果
int res = ft.get();
System.out.println(res);
// 主线程任务需放在子线程创建之后,否则相当于单线程
for (int i = 0; i < 10; i++) {
System.out.println(i+"---main");
}
}
}
// 1.定义一个线程任务类 MyCallable 实现 Callable 接口
class MyCallable implements Callable<Integer> {
private int num;
public MyCallable(){}
public MyCallable(int num) {
this.num = num;
}
// 2.重写 call 方法,处理线程要做的事,将结果返回
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= num; i++) {
sum += i;
}
return sum;
}
}
三种方式的总结
Thread类的常用方法
线程安全
多个线程同时访问一个共享资源且存在修改该资源的操作,可能会发生数据冲突问题,但是执行读操作不会发生数据冲突。
线程安全问题出现的原因:
- 多个线程并发
- 同时访问共享资源
- 存在修改共享资源
线程同步
线程同步的核心思想:加锁,把共享资源进行上锁,每次只能一个线程进入操作,完毕以后进行解锁。即让多个线程实现先后依次访问共享资源。
方式一、同步代码块
- 作用:将出现线程安全问题的核心代码上锁。
- 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
- 格式:
synchronized (同步锁对象) { //操作共享资源的代码(核心代码) }
规范上:建议使用共享资源作为锁对象。
- 对于实例方法建议使用 this 作为锁对象。
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象
方式二、同步方法
- 作用:将出现线程安全问题的方法上锁。
- 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。
- 格式:
修饰符 synchronized 返回值类型 方法名称( 形参列表 ) { // 操作共享资源的代码 }
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
- 对实例方法默认用this作为的锁对象。但是代码要高度面向对象!
- 对静态方法默认用类名.class作为的锁对象。
方式三、Lock锁
- JDK1.5以后并发包新增Lock接口以及相关实现类用来实现锁功能。
- 需要手动获取和释放锁。
- synchronized同步函数不能手动添加、释放锁。
- Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象。
- 官方给出的例子:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock();
}
}
}
- public ReentrantLock(boolean fair) 此类的构造函数接受一个可选的公平参数。设置时true,在争用情况下,锁有利于授予对等待时间最长的线程的访问权限。否则此锁不保证任何特定的访问顺序。
- 可以使用interrupt方法中断线程。(子线程调用 interrupt 方法会使线程执行抛出异常,在try…catch中获取异常并进行下一步操作)
死锁
多线程中同步嵌套同步,导致锁无法释放。
线程通信
- 线程间相互发送数据,线程间共享一个资源即可实现线程通信。
- 线程通信前提:线程通信通常是在多个线程操作同一个共享资源的时候需要进行通信,且要保证线程安全。
- 线程通信实际应用场景:生产者与消费者模型,生产者线程生产完数据后唤醒消费者,然后等待自己,消费者消费完该数据后唤醒生产者,然后等待自己。
- Object类 的等待和唤醒方法:(应使用当前同步锁对象进行调用)
相关问题
- 什么是线程安全?
多个线程同时访问同一个共享资源且存在修改该资源的操作,可能会发生数据冲突。 - 如何解决多线程之间线程安全问题?
线程同步或锁 - 为什么使用线程同步或使用锁能解决安全问题呢?
让多个线程实现先后依次访问共享资源。对会产生线程安全问题的代码块上锁,当前线程执行完毕后释放锁,再由下一个线程进入。 - 什么是线程之间的同步?
多个线程共享同一个资源,不会受到其它线程的干扰。 - 什么是同步代码块?
就是将可能会发生线程安全问题的代码包括起来,只让当前一个线程进行执行,包裹的代码执行完成之后才能释放锁,然后才能让其他线程进行执行。 - 多线程同步的分类?
同步代码块、同步方法。 - 同步代码块与同步方法区别?
同步代码块使用自定义锁(明锁),同步函数使用隐式锁。 - 同步方法与静态同步方法区别?
同步方法使用this锁,静态同步方法使用字节码文件即类.class。 - 什么是线程死锁?
同步中嵌套同步 。 - sleep与wait区别?
sleep() | wait() |
---|---|
Thread类 | Object类 |
调用后线程不会释放对象锁 | 调用后线程会放弃对象锁 |
使程序暂停执行指定的时间,达到指定时间后又自动恢复运行状态 | 程序会进入无限等待,当此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态 |