继承Thread类
java.lang.Thread
,重写run()方法优缺点:
public class MyThread extends Thread{
@Override
public void run() {
// 线程的执行任务
for (int i = 1; i <= 5; i++) {
System.out.println("子线程MyThread -> " + i);
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
// 创建MyThread线程类的对象 代表一个线程
Thread thread = new MyThread();
// 启动线程(自动执行run方法)
thread.start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程MainThread -> " + i);
}
}
}
实现Runnable接口
优缺点:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程MyRunnable-> " + i);
}
}
}
public class ThreadTest2 {
public static void main(String[] args) {
// 创建任务对象
Runnable target = new MyRunnable();
// 把 任务对象 交给 线程对象 处理
new Thread(target).start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程Main -> " + i);
}
}
}
匿名内部类的写法
public class ThreadTest2_2 {
public static void main(String[] args) {
// 1.创建匿名内部类
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println("子线程 -> " + i);
}
}
};
// 2.再交给Thread线程对象
Thread thread = new Thread(target);
// 3.启动线程
thread.start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程Main -> " + i);
}
}
}
实现Callable接口
优缺点:
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
// 假设 求1~n的和 并返回
for (int i = 1; i <= n; i++) {
sum += i;
}
return "线程求出1~" + n + "的和:" + sum;
}
}
public class ThreadTest3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建Runnable对象
Callable<String> callable = new MyCallable(100);
// 封装成FutureTask
FutureTask<String> futureTask = new FutureTask<>(callable);
// 交给Thread对象
Thread thread = new Thread(futureTask);
// 启动线程
thread.start();
// 获取线程执行完毕后返回的结果
String result = futureTask.get();
System.out.println("result = " + result);
}
}
案例:模拟线程安全问题。小红和小明同时取钱(同一个账户)
public class ThreadTest {
public static void main(String[] args) {
Account account = new Account("ICBC-100", 100000);
new DrawThread(account, "小明").start(); // 小明
new DrawThread(account, "小红").start(); // 小红
}
}
public class DrawThread extends Thread{
private final Account account;
public DrawThread(Account account, String name) {
super(name);
this.account = account;
}
@Override
public void run() {
account.drawMoney(100000);
}
}
public class Account {
private String cardId;
private double money; // 余额
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "取钱后,剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
**作用:**把访问共享资源的核心代码给上锁,一次保证线程安全
synchronized (同步锁) {
访问共享资源的核心代码
}
注意事项
在案例中添加同步代码块 (实例方法使用this
作为锁对象)
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
synchronized (this) {
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "取钱后,剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
}
如果要求在静态方法中保证线程安全,同步锁应该是 类名.class
public static void test() {
synchronized (类名.class) {
// 核心代码
}
}
作用:把访问共享资源的核心方法给上锁,以此保证线程安全
原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进入执行
修饰符 synchronized 返回值类型 方法名称 (形参列表) {
操作共享资源的代码
}
在案例中添加同步方法
public synchronized void drawMoney(double money) {
String name = Thread.currentThread().getName();
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "取钱后,剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
}
底层原理
this
作为锁的对象类名.class
作为锁的对象Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活/方便/强大
Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象
public class Account {
// 创建一个锁对象
private final Lock lock = new ReentrantLock();
//略
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
try {
lock.lock(); //加锁
if (this.money >= money) {
System.out.println(name + "来取钱" + money + "成功");
this.money -= money;
System.out.println(name + "取钱后,剩余:" + this.money);
} else {
System.out.println(name + "来取钱,余额不足");
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock(); // 解锁
}
}
}
注意事项
final
修饰try...catch...finally
包裹住加锁与解锁操作,保证即使出现异常,也可确保能解锁线程池就是一个复用线程的技术
不断创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来
线程池接口:ExecutorService
**方式一:**使用 ExecutorService
的实现类 ThreadPoolExecutor
创建一个线程池对象
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
corePoolSize
指定线程池的核心线程数量maximumPoolSize
线程池的最大线程数量(maximumPoolSize > corePoolSize)keepAliveTime
临时线程的存活时间unit
指定临时线程存活的时间单位(秒/时/分/天)workQueue
指定线程池的任务队列ThreadFactory
指定线程池的线程工厂RejectedExecutionHandler
指定线程池的任务拒绝策略(线程都在忙,任务队列也满了,新任务来了该怎么处理)ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
策略 (参数七) | 详解 |
---|---|
ThreadPoolExecutor.AbortPolicy() | 丢弃任务并抛出RejectedExecutionException异常。默认策略 |
ThreadPoolExecutor.DiscardPolicy() | 丢弃任务,但是不抛出异常。(不推荐做法) |
ThreadPoolExecutor.DiscardOldestPolicy() | 抛弃队列等待最久的任务,然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy() | 有主线程负责调用任务的run()方法从二绕过线程池直接执行 (老板亲自执行任务) |
注意事项
**方式二:**使用 Excutors
(线程池的工具类) 调用方法返回不同特点的线程池对象
方法名称 | 说明 |
---|---|
ExcutorService newFixedThreadPool(int nThreads) | 创建固定线程数量的线程池 |
ExcutorService newSingleTHreadExecutor() | 创建只有一个线程的线程池 |
ExcutorService newCachedThreadPool() | 线程数量随着任务增加而增加 |
ExcutorService newScheduledThreadPool(int corePoolSize) |
ExecutorService pool = Executors.newFixedThreadPool(3);
ExecutorService pool = Executors.newSingleThreadExecutor();
核心线程数量到底配置多少
计算密集型的任务:核心线程数量 = CPU的核数 + 1
IO密集型的任务:核心线程数量 = CPU的核数 * 2
public static void main(String[] args) {
// 1.通过ThreadPoolExecutor创建一个线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS, new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
Runnable target = new MyRunnable();
// 线程池会自动创建新线程,自动处理这个任务,自动执行
pool.execute(target); // 第一个线程
pool.execute(target); // 第二个线程
pool.execute(target); // 第三个线程
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 临时线程的创建时机
pool.execute(target);
pool.execute(target);
// 到了新任务拒绝的时机
pool.execute(target);
// 线程池不会主动关闭,程序会一直运行
pool.shutdown(); // 等待线程池任务全部完成后,再关闭线程池
//pool.shutdownNow();// 立即关闭线程,不管是否还有任务在执行
}
public class ThreadPoolTest2 {
public static void main(String[] args) throws Exception{
// 1.通过ThreadPoolExecutor创建一个线程池对象
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
// 2.使用线程处理Callable任务
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
System.out.println("f1.get() = " + f1.get());
System.out.println("f2.get() = " + f2.get());
System.out.println("f3.get() = " + f3.get());
System.out.println("f4.get() = " + f4.get());
// 线程池不会主动关闭,程序会一直运行
pool.shutdown(); // 等待线程池任务全部完成后,再关闭线程池
//pool.shutdownNow();// 立即关闭线程,不管是否还有任务在执行
}
}
**进程:**正常运行的程序/软件就是一个独立的进程
**线程:**线程是属于进程的,一个进程中可以同时运行很多个线程
进程中的多个线程是并发和并行执行的
并发:
并行:
多线程是怎么执行的