开启多线程的两种方法
继承Thread类
- 子类继承Thread类(重写
run()
方法) - 启动线程:
子类对象.start()
- 由于Java是单继承,不建议使用继承Thread的方法进行实现多线程
实现Runnable接口
-
实现Runnable接口(重写
run()
方法) -
启动线程:将该接口的实现类(你正在使用的类)作为参数传入Thread对象后,使用
Thread对象.start()
进行启动 -
避免了Java的单继承性,方便同一个对象被多个线程使用的情况
示例:
public class Test1 implements Runnable{ @Override public void run(){ //方法体 } } public static void main(String[] args){ //创建Runnable实现类 Test1 t1 = new Test1(); //创建线程对象,通过线程对象开启线程,代理 new Thread(t1).start(); //主函数方法体 }
实现Callable接口
- 实现callable接口,需要返回值类型
- 重写call方法,需要处理异常 返回值为 E
- 创建目标对象
- 创建执行服务:
ExeutorService ES = Executors.newFixedThreadPool(int);
int为线程池的大小 - 提交执行:
Future<E> result = ES.submit(目标对象);
- 获取结果:
E r = result.get()
- 关闭服务:
ES.shutdownNow()
线程的其他方法
线程的状态 Thread.State
- 枚举类型
getState()
方法,返回Thread.State 类型
Thread.State | 说明 |
---|---|
NEW | 尚未启动的线程处于此状态。 |
RUNNABLE | 在Java虚拟机中执行的线程处于此状态。 |
BLOCKED | 被阻塞等待监视器锁定的线程处于此状态。 |
WAITING | 正在等待另一个线程执行特定动作的线程处于此状态。 |
TIMED_WAITING | 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。 |
TERMINATED | 已退出的线程处于此状态。 |
改变线程优先级 Priority
-
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
-
优先级用数字表示,从1~10
◆ Thread.MIN_ PRIORITY = 1;
◆ Thread.MAX_ PRIORITY = 10;
◆ Thread.NORM_ _PRIORITY= 5; -
getPriority()
获取优先级‘;setPriority(int x)
修改优先级
线程休眠 sleep
sleep(时间 ms)
指定当前线程阻塞的毫秒数- 需要处理异常
- 休眠时间到达后线程进入就绪状态
- 可用于模拟延时、倒计时、方便检测多线程错误
- 每个对象都有一个锁,sleep不会释放锁
线程停止
- 不推荐使用jdk推荐的
stop()
destroy()
方法 - 推荐让线程自己停下来(跑完次数)
- 建议使用标志位进行终止线程 (用
flag = false
来结束run方法
中的while(ture)
循环)
线程礼让 yield
yield()
礼让线程,让当前正在执行的线程暂停,但不阻塞- 将线程从运行态转化成就绪态
- 礼让不一定成功
线程的强制执行 join
join()
优先执行该线程,等此线程执行完后才能执行其他线程,此时其他线程阻塞
其他线程
守护线程和用户线程 Deamon
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户现场执行完毕
- 虚拟机不必等待守护线程执行完毕
- 用户线程: mian线程
- 守护线程:gc线程(垃圾回收)、操作日志、内存监控等
- 设置守护线程:
thread.setDaemon(true)
默认是false的用户线程 - 注: 必须先设置成守护线程后才能启动线程
线程同步 Synchronized
- 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
synchronized
。 - 当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可.
- 存在以下问题:
◆一个线程持有锁会导致其他所有需要此锁的线程挂起;
◆在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
◆如果一个优先级高的线程等待-个优先级低的线程释放锁 会导致优先级倒置,引起性能问题
使用方式:
- synchronized方法
public synchronized void method(int args){
//代码部分
}
synchronized方法控制对“对象”的访问, 每个对象对应一 把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞;
方法一旦执行,就独占该锁.直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁:,继续执行
- synchronized 块
public void method(int args){
synchronized(线程对象){
}
}
◆同步块: synchronized (Obj ){}
◆Obj称之为同步监视器
◆Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
◆同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身,或者是class
◆同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码.
- 第二个线程访问 ,发现同步监视器被锁定,无法访问.
- 第一个线程访问完毕 ,解锁同步监视器.
- 第二个线程访问, 发现同步监视器没有锁,然后锁定并访问
线程通信
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件.
◆对于生产者,没有生产产品之前,要通知消费者等待.而生产了产品之后,又需要马上通知消费者消费
◆对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费
◆在生产者消费者问题中,仅有synchronized是不够的
◆synchronized可阻止并发更新同一个共享资源,实现了同步
◆synchronized不能用来实现不同线程之间的消息传递(通信)
通信方法:
Object类提供了几个方法,但此方法只能在同步方法或者同步代码块中使用,否则会抛出IllegalMonitorStateException
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,此方法会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用了wait方法的线程,优先级别高的线程优先调度 |
线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影
响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。
可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:.
-
提高响应速度(减少了创建新线程的时间)
-
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
便于线程管理(…)
名称 | 说明 |
---|---|
corePoolSize | 核心池的大小 |
maximumPoolSize | 最大线程数 |
keepAliveTime | 线程没有任务时最多保持多长时间后会终止 |
unit | 对应上面存活时间的时间单位 |
workQueue | 阻塞队列 |
常用的线程池实现类:
newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
这里我们可以看到,
- 核心线程池数量为 0,也就是它不会永久保留任何线程;
- 最大容量是
Integer.MAX_VALUE
; - 每个线程的存活时间是 60 秒,也就是如果 1 分钟没有用这个线程就被回收了;
- 最后用到了同步队列。
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
这个线程池的特点是:
- 线程池中的线程数量是固定的,也是我们创建线程池时需要穿入的参数;
- 超出这个数量的线程就需要在队列中等待。
newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
这个线程池顾名思义,里面只有 1 个线程。
演示使用:
//1.创建服务,创建线程池
ExecutorService ES = Executors.newFixedThreadPool(int nubs);
//2.执行
ES.execute(new MyThread());
//3.关闭连接池
ES.shutdown();
资料来源于网络