普通方法与多线程方法区别
普通方法与多线程方法的执行顺序如下
一个进程中可以有多个线程,进程由系统分配
多线程的创建方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
继承Thread类
- 自定义线程类继承Thread类
- 重写
run()
方法,编写线程执行体 - 创建线程对象,调用
start()
方法启动线程
/**
* 多线程创建
*/
public class ThreadTest1 extends Thread {
// 重写
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("多线程..." + i);
}
}
public static void main(String[] args) {
ThreadTest1 threadTest1 = new ThreadTest1();
// 启动线程
threadTest1.start();
for (int i = 0; i < 50; i++) {
System.out.println("主方法..." + i);
}
}
}
注意:线程不一定立即执行,由CPU安排调度
Thread类常见方法:
Thread类也是实现了Runnable,因为继承具有局限性(只能单继承),所以推荐使用实现Runnable方式
实现Runnable接口
- 自定义类实现Runnable接口
- 实现
run()
方法,编写线程执行体 - 创建线程对象调用
start()
方法启动线程
/**
* 多线程
*/
public class ThreadTest2 implements Runnable {
// 重写
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("多线程..." + i);
}
}
public static void main(String[] args) {
ThreadTest1 threadTest2 = new ThreadTest1();
// 启动线程
new Thread(threadTest2).start();
for (int i = 0; i < 50; i++) {
System.out.println("主方法..." + i);
}
}
}
并发问题
例如抢票问题
/**
* 多线程问题
*/
public class Ticket implements Runnable {
// 票数
private int ticketNum = 10;
@Override
public void run() {
while (ticketNum > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到第" + ticketNum-- + "张票");
}
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "张三").start();
new Thread(ticket, "李四").start();
new Thread(ticket, "王五").start();
}
}
执行结果
王五拿到第10张票
张三拿到第9张票
李四拿到第10张票
张三拿到第8张票
李四拿到第7张票
王五拿到第6张票
张三拿到第3张票
王五拿到第5张票
李四拿到第4张票
王五拿到第2张票
李四拿到第1张票
张三拿到第0张票
数据出现紊乱,线程不安全
实现Callable接口
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务
- 提交执行
- 获取结果
- 关闭服务
/**
* 多线程创建
*/
public class ThreadTest3 implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
// 操作内容
System.out.println(Thread.currentThread().getName());
return false;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadTest3 item1 = new ThreadTest3();
ThreadTest3 item2 = new ThreadTest3();
ThreadTest3 item3 = new ThreadTest3();
// 创建执行服务
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 提交执行并获取结果
Boolean result1 = executorService.submit(item1).get();
Boolean result2 = executorService.submit(item2).get();
Boolean result3 = executorService.submit(item3).get();
// 关闭服务
executorService.shutdown();
}
}
三种实现方式的区别
方式 | 有点 | 缺点 |
---|---|---|
继承Thread类 | 编程简单,可直接使用Thread中方法 | 可扩展性差,无法获得结果 |
实现Runnable接口 | 扩展性强 | 不能直接使用Thread类中方法,无法获得结果 |
实现Callable接口 | 扩展性强,可获得结果 | 不能直接使用Thread类中方法,编程复杂 |
线程状态
线程方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 线程休眠时间,毫秒 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程 |
boolean isAlive() | 测试线程是否处于活动状态 |
线程停止
JDK提供的方法不建议使用了,推荐线程自己结束,示例
public class ThreadTest implements Runnable {
// 线程是否结束标识
private boolean flag = true;
@Override
public void run() {
while (flag) {
System.out.println("run...");
}
}
// 对外提供结束方法
public void stop() {
this.flag = false;
}
}
线程休眠
sheep()
方法能指定当前线程阻塞多少毫秒,存在InterruptedException
异常,当阻塞结束后会进入就绪状态,用于模拟网络延时,倒计时等,每个对象都有一个锁,sheep()
不会释放锁
守护进程
// 默认是false表示用户进程,设置为true为守护进程
thread.setDaemon(true);
线程分为用户进程和守护进程,虚拟机不用等待守护进程执行完毕,也就是说,当同一个方法中的用户线程结束后,即便守护进程是个死循环在下一轮监监控也会将守护进程结束
并发
同一个资源(对象、数据)被多个线程同时操作
线程同步
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问的准确性,在访问时会加入锁机制(synchronized),当一个对象获得对象的排它锁,独占资源,其他线程必须等待,使用后再释放锁,存在问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延迟,引发性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置问题,影响性能
同步方法
public synchronized void method() {
// TODO
}
synchronized方法控制对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象锁才能执行,否则线程阻塞,方法一旦执行,就独占该锁,直到方法返回才释放锁,后面阻塞的线程才能获得锁,继续执行
缺陷:若将一个大的方法声明为synchronized将会影响效率
锁的对象就是变化的量,需要增删改的对象
同步代码块
synchronized (Obj) {
// TODO
}
Obj称为同步监视器,可以是任何对象,是需要锁的变化的量,同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,即对象本身,或者class
CopyOnWriteArrayList
JUC下的一个集合,线程安全
Lock锁
从JDK5.0开始,Java提供了更强大的线程同步锁机制–显式定义同步锁对象来实现同步
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run () {
try {
// 加锁
lock.lock();
} finally {
// 解锁
lock.unlock();
}
}
synchronized和lock
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- Lock是显式锁(手动开启和关闭),synchronized是隐式锁,出了作用域就自动释放锁
- 使用Lock时JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供了更多的子类)
使用顺序:Lock > 同步代码块 > 同步方法
生产者和消费者
涉及到线程之间的通信问题,生产者生产出来后通知消费者消费,如果消费者消费发现没有就等待
管程法
核心在于需要一缓冲区,来负责商品的控制
// 消费者消费商品
public synchronized void pop() {
// 没有货物
if(countGoods == 0) {
try {
// 等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 等待生产者通知开始消费
countGoods--;
// 消费完,通知生产者生成
this.notifyAll();
}
// 生产者同理
信号灯法
同理
线程池
线程的频繁创建和销毁,使用了大量的资源,尤其在并发情况下,对性能影响很大
解决:提前创建好多个线程,放入线程池中,使用时直接获取,用完放回池中
优点:
- 提高响应速度(减少创建新线程时间)
- 降低资源消耗
- 便于线程管理
- corePoolSize:核心池大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后终止
线程池使用
JDK5.0提供了线程池相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口
- void execute(Runnable command):执行任务,没有返回值,一般用来执行Runnable
- Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
- void shutdown:关闭连接池
Executors:工具类、连接池的工厂类,用于创建并返回不同类型的线程池
public class ThreadTest2 {
public static void main(String[] args) {
// 创建线程池,参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
// 启动线程
service.execute(new ThreadTest());
service.execute(new ThreadTest());
service.execute(new ThreadTest());
// 关闭线程池
service.shutdown();
}
}
class ThreadTest implements Runnable {
@Override
public void run() {
System.out.println("执行");
}
}
Callable结合FutureTask获取结果
// 查询用户总数
Callable<Integer> callableUserCount = () -> userService.count();
FutureTask<Integer> taskUserCount = new FutureTask<>(callableUserCount);
Thread threadUserCount = new Thread(taskUserCount);
// 查询用户总数(直接写代码方式)
Callable<Long> callableAll = () -> mongoTemplate.count(new Query(), InvitationRegister.COLLECTION_NAME);
FutureTask<Long> taskAllUserNum = new FutureTask<>(callableAll);
Thread threadAllUser = new Thread(taskAllUserNum);
// 查询会员总数
Callable<Integer> callableVipCount = () -> userService.count(wrapperVip);
FutureTask<Integer> taskVipCount = new FutureTask<>(callableVipCount);
Thread threadVipCount = new Thread(taskVipCount);
// 统一启动线程
threadUserCount.start();
threadVipCount.start();
// 获取结果
Integer userCount = taskUserCount.get(10, TimeUnit.SECONDS);
Integer vipCount = taskVipCount.get(10, TimeUnit.SECONDS);