多线程
创建线程的三常见大方法:
继承Thread 类实现:
1、创建线程类:继承**Thread ** +重写run()方法
2、构建线程对象:创建子类对象
3、启动线程:通过子类调用start()
public class TestThread extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 实现多线程");
}
}
}
public class Test {
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
}
}
实现Runnable 接口实现
1、创建实现Runnable接口的实现类+重写Run()方法
2、创建一个实现对象
3、利用实现类对象创建Thread类对象
4、启动线程
public class TestRunnable implements Runnable {
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Runnable 实现多线程");
}
}
}
public class Test {
public static void main(String[] args) {
TestRunnable testRunnable = new TestRunnable();
Thread thread=new Thread(testRunnable);
thread.start();
}
}
实现Callable接口实现
1、创建实现Callable接口的实现类+重写call()方法
2、创建一个实现类对象
3、由Callable创建一个FutureTask
对象
4、由FutuerTask
创建一个Thread对象
5、启动线程
public class TestCallable implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i <10 ; i++) {
Thread.sleep(1000);
System.out.println("Callable 实现多线程 "+(i+1));
}
return null;
}
}
public class Test {
public static void main(String[] args) {
TestCallable testCallable = new TestCallable();
FutureTask<Integer> futureTask=new FutureTask<>(testCallable);
Thread thread1=new Thread(futureTask);
thread1.start();
}
}
- Thread: 继承方式, 不建议使用, 因为Java是单继承的, 继承了Thread就没办法继承其它类了,不够灵活
- Runnable: 实现接口,比Thread类更加灵活,没有单 继承的限制
- Callable: Thread和Runnable都是重写的run()方法并且 没有返回值,Callable是重写的call()方法并且有返回 值并可以借助FutureTask类来判断线程是否已经执行 完毕或者取消线程执行
- 当线程不需要返回值时使用Runnable,需要返回值时 就使用Callable,一般情况下不直接把线程体代码放 到Thread类中,一般通过Thread类来启动线程
用线程池创建(了解)
- 使用Executors类中的 newFixedThreadPool(int num)方法创建一个线 程数量为num的线程池
- 调用线程池中的execute()方法执行由实现 Runnable接口创建的线程;调用submit()方法执 行由实现Callable接口创建的线程
- 调用线程池中的shutdown()方法关闭线程池
线程池
概念:线程池就是首先创建一些线程,它们的集合称为线程 池。
作用:(1) 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
(2) 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行;
(3) 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳 定性,使用线程池可以进行统一的分配,调优和监控。
**创建:**常见的创建线程池:
newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。
newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。
newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。
newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。
newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。
线程的五种状态
新建状态
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态
阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程状态的转变
如何控制线程进入终止状态:
1.正常运行结束 2.stop ,destroy() 过时 3.通过标识判断
如何进入就绪状态:
1.start 2.线程切换 3.yield 礼让线程 4.阻塞解除
如何进入阻塞状态
1.sleep 2.join 插队 3.wait 4.IO
sleep:"抱着"资源睡觉
sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是 sleep() 方法不会释放“锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据
yield:让出CPU资源
yield() 方法和 sleep() 方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield() 方法只是使当前线程重新回到可执行状态,所以执行yield() 的线程有可能在进入到可执行状态后马上又被执行。让出CPU的使用权,从运行态直接进入就绪态。让CPU重新挑选哪一个线程进入运行状态。
join:等另一个线程执行
方法会使当前线程等待调用 join() 方法的线程执行结束之后,才会继续往后执行。
注意:
一个线程如果进入阻塞状态,阻塞解除没有办法直接恢复到运行,会直接恢复就绪
一个线程如果一旦终止,没有办法恢复
sleep 线程休眠|睡眠 (ms)
静态方法
“抱着”资源睡觉
当一个执行到sleep方法,当前线程就会休眠指定时间,在休眠过程中,让出CPU的资源
作用:
1.放大问题的可能性 2.模拟网络延迟 倒计时
线程同步
在Java里面,通过synchronized进行同步的保证。
它包括两种用法:synchronized 方法和 synchronized 块
synchronized 方法
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:
public synchronized void accessVal(int newVal);
synchronized 块
在代码块前加上synchronized关键字,并指定加锁的 对象
synchronized(syncObject){
//允许访问控制的代码
}
同步锁: synchronized
同步方法 : 同步静态方法 同步成员方法
同步块{} :
synchronized(this|类名.class|资源){
代码段...
}
{} -> 排队执行的代码块
this|类名.class|资源 : 线程要求的执行{}中代码的执行条件
注意:
同步的代码范围如果 太大,效率低,如果太小,锁不住
锁不变的内容(this|资源…|类…)
同步块: 类的class对象,相当于把真个类锁住了,效率相对较低–>推荐使用this
死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞 争资源或者由于彼此通信而造成的一种阻塞的现象
如何解决死锁问题:
- 往往是程序逻辑的问题。需要修改程序逻辑。
程要求的执行{}中代码的执行条件
注意:
同步的代码范围如果 太大,效率低,如果太小,锁不住
锁不变的内容(this|资源…|类…)
同步块: 类的class对象,相当于把真个类锁住了,效率相对较低–>推荐使用this
死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞 争资源或者由于彼此通信而造成的一种阻塞的现象
如何解决死锁问题:
- 往往是程序逻辑的问题。需要修改程序逻辑。
- 尽量不要同时持有两个对象锁。