目录
多线程
一、创建线程
1、继承Thread类
Java是通过java.lang.Thread 类的对象来代表线程的。可以通过继承Thread类后,再重写Thread类中的run()方法。
// 1、定义一个线程类继承Thread类。
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
// 2、重写Thread类提供的run方法(线程执行任务的方法)
@Override
public void run() {
}
}
创建MyThread对象
// 创建Thread对象 并通过MyThread的有参构造器为线程起名字
Thread t1 = new MyThread("1号->");
// t1.setName("1号");
调用线程对象的start()方法,启动线程
t1.start();
优点:编码简单
缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。
2、实现Runnable接口
定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
class RunnableThread implements Runnable{
@Override
public void run() {
}
}
创建MyRunnable任务对象
Runnable target = new RunnableThread();
把MyRunnable任务对象交给Thread处理。
Thread thread = new Thread(target);
thread.start();
优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。
可以使用匿名内部类。
缺点:需要多一个Runnable对象。
3、实现Callable接口
通过前两种方式,假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。为了解决这个问题,JDK 5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)这种方式最大的优点:可以返回线程执行完毕后的结果。
创建任务对象
定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。
把Callable类型的对象封装成FutureTask(线程任务对象)。
class MyCallable implements Callable<Integer>{
private int res;
public MyCallable(int res) {
this.res = res;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= res; i++) {
sum += i;
}
return sum;
}
}
FutureTask<Integer> futureTask = new FutureTask<>(callable);
把线程任务对象交给Thread对象。
Thread thread = new Thread(futureTask);
调用Thread对象的start方法启动线程。
thread.start();
线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。
Integer integer = futureTask.get();
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。
缺点:编码复杂一点。
二、API
Thread
线程安全
一、线程安全问题
什么是线程安全问题?
多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。
线程安全问题出现的原因
- 存在多个线程在同时执行
- 同时访问一个共享资源
- 存在修改该共享资源
解决方案
在Java中,为了解决线程安全问题,引入了线程同步
二、线程同步
线程同步的思想
让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
线程同步的常见方案
加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来
让多个线程实现先后依次访问共享资源,这样就解决了安全问题。
同步代码块
作用:把访问共享资源的核心代码给上锁,以此保证线程安全。
synchronized(同步锁) {
访问共享资源的核心代码
}
原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行
注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。
锁对象的使用规范:
建议使用共享资源作为锁对象
对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。
同步方法
作用:把访问共享资源的核心方法给上锁,以此保证线程安全。
修饰符 synchronized 返回值类型 方法名称(形参列表) {
操作共享资源的代码
}
原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行
同步方法的底层原理:
同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
如果方法是实例方法:同步方法默认用this作为的锁对象。
如果方法是静态方法:同步方法默认用类名.class作为的锁对象。
两者对比:
是同步代码块好还是同步方法好一点?
范围上:同步代码块锁的范围更小,同步方法锁的范围更大。
可读性:同步方法更好。
Lock锁
Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。
Lock锁的常用方法:
线程通信
当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。