文章目录
- 1. 线程创建方式
- 1.1 继承Thread
- 1.2 声明一个实现Runnable接口的类
- 1.3 利用Callable接口、FutureTask类来实现
- 2. 线程同步
- 2.1 同步代码块
- 2.2 同步方法
- 2.3 Lock锁
- 3. 线程同步
- 4. 线程池
1. 线程创建方式
1.1 继承Thread
定义子类,继承Thread,创建对象,并调用start
启动线程
- 优点:编码简单
- 缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展
public class Main {
// main方法是有一条默认的主线程执行的
public static void main(String[] args) {
// 1. 创建线程类的对象,代表一个线程
Thread t = new MyThread();
// 2. 启动线程
t.start();
for (int i = 0; i < 10000; i++) {
System.out.println("主线程输出:" + i);
}
}
}
注意,是调用Thread的start方法,而不是run方法!!!
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("子线程输出:" + i);
}
super.run();
}
}
1.2 声明一个实现Runnable接口的类
- 优点:只是实现了一个接口,还可以继承一个类,实现其他接口,扩展性强
- 缺点:需要多创建一个Runnable对象
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("子线程输出:" + i);
}
}
}
static void test_Runnable() {
// Runnable target = new MyRunnable(); // 任务对象(不是线程对象)
// new Thread(target).start();
// lambda
new Thread(() -> {
for (int i = 0; i < 10000; i++) {
System.out.println("子线程输出:" + i);
}
for (int i = 0; i < 10; i++) {
System.out.println("主线程输出:" + i);
}
}
1.3 利用Callable接口、FutureTask类来实现
- 优点:可以返回线程执行完后的结果
- 缺点:编码复杂
class MyCallable implements Callable<String> {
int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
// 描述线程的任务,返回线程执行后的结果
// 求1-n的和
int sum = 0;
for(int i = 1; i <= n; i++)
sum += n;
return "1~n的总和是:" + sum;
}
}
static void test_Callble() {
// 1. 创建Callable对象
MyCallable call = new MyCallable(50);
// 2. 把Callable对象封装成FutureTask对象
// 1. 是一个任务对象,实现了Runnable对象
// 2. 可以在线程执行完后,调用get方法获取
FutureTask<String> f1 = new FutureTask<>(call);
new Thread(f1).start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程输出:" + i);
}
// 获取线程执行完毕后的结果
String s = null;
try {
// 会等到线程执行完毕,这行代码再执行
s = f1.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(s);
}
我发现啊,Java的多线程和C++的多线程不一样的点是,Java中创建子线程,如果主线程先执行完了,子线程没有执行完,子线程会继续执行;但是C++会被终止。
不过join
方法都有着相同的用处,那就是阻塞主线程,等待线程执行完毕后,再执行主线程后面的代码。
2. 线程同步
2.1 同步代码块
synchronized(同步锁) {
访问共享资源的核心代码
}
这个同步锁是一个字符串也可以(双引号在内存中存在常量区,只有一份),只要是一个唯一对象即可 。
但最好是用共享资源作为锁,比如说this
。
如果要调用静态方法,同步锁采用类名.class
,锁住整个class。
2.2 同步方法
把访问共享资源的核心方法上锁,保证线程安全。这样能保证一个对象中,只有一个方法在执行,其他方法都无法执行。
修饰符 synchronized 返回值类型 方法名(形参列表){
操作共享资源得到代码
}
2.3 Lock锁
Lock是一个接口类,可以用实现类ReentrantLock
来实例化一个锁,来使用。
Lock lk = new ReentrantLock();
lk.lock();
lk.unlock();
3. 线程同步
来了来了,条件遍历来了。注意,一定要和锁搭配使用。
来一个经典的例子吧,2个线程交替打印A和B
package TestDemo;
// 2个线程交替打印A和B10次
public class Test2 {
public static int count;
static final Object lock = new Object(); // 锁
public static void main(String[] args){
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "打印A");
lock.notify();
try {
if(i < 9)
lock.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "A线程");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "打印B");
lock.notify();
try {
if(i < 9)
lock.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}, "B线程");
t1.start();
t2.start();
}
}
4. 线程池
JDK5提供了代表线程池的接口:ExecutorService
。比较常用的实现类是ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- corePoolSize:指定线程池的核心线程数量
- maximumPoolSize:指定线程池的最大线程数量
- keepAliveTime:指定临时线程的存活时间
- unit:指定临时线程存货的时间单位
- workQueue:指定线程池的任务队列
- threadFactory:指定线程池的任务工厂
- handler:指定线程池的拒绝策略(任务队列满了后,新任务来了怎么处理)
线程池默认是不会死亡的,除非调用shutdown()
,或者shutdownNow()
。
- 临时线程什么时候创建?
新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程,去执行新任务,而不是任务队列的任务(插队)。 - 什么时候会开始拒绝新任务?
核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。
Executors
是线程池的一个工具类,提供了很多静态方法用于返回不同特点的线程池对象。