多线程
1:什么是线程
线程是计算机能够进行运营调度的最小单位,线程被包含在进程中。
2:实现线程的方式
1:extends Thread
2:implements Runnable/Callable(Future.get方法获取返回值)
3:线程池
3.1:为什么要用线程池
a:每次new对象,性能较差。
b:缺乏统一管理,可能无限制的创建线程,过多占用CPU资源。
c:重用已存在的线程,减少对象创建关闭开销。
d:有效控制最大线程并发数,提高系统资源利用率。
3.2 :线程池创建方式
Executors(不推荐使用,会导致OOM问题),ThreadPoolExecutors
3.2.1线程池分类
newFixedThreadPool():固定线程数的线程池
缺点:如果某任务执行时间过长,而导致大量任务堆积在阻塞队列中,或者说在某一时刻大量任务进来则会导致机器内存使用不断飙升,最终导致OOM
使用场景:处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。
newCachedThreadPool():
缺点:如果任务的提交速度大于线程处理任务的速度,那么就会不断地创建新线程极端情况下会耗尽CPU和内存资源;CachedThreadPool允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
使用场景:适用于并发执行大量短期的小任务
newSingleThreadExecutor():
缺点:阻塞队列LinkedBlockingQueue为无界队列,容易导致OOM
使用场景:适用于串行执行任务的场景,一个任务一个任务地执行
newScheduledThreadPool():
使用场景:周期性执行任务的场景,需要限制线程数量的场景
3.2.2线程池大小设置
N为CPU核心数
CPU密集型(利用CPU进行一系列计算操作)N+1
I/O密集型(网络读取,文件读取)2N
3:继承Thread和实现Runnable选择
java是单继承的。所以当你需要继承其他类的时候,需要实现Runnable方法
4:start()和run()区别
start方法用来启动新创建的线程,而且在start方法中会调用run方法,这和直接调用run方法不一样,单纯调用run方法并不会开启新的线程,只是在原来的线程中执行了run方法。
5:如何确保线程安全
5.1synchronized关键字
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
四、当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
public class ThreadLock implements Runnable {
public static Integer integer = 10;
public static void main(String[] args) {
ThreadLock threadLock = new ThreadLock();
Thread t1 = new Thread(threadLock, "窗口1");
Thread t2 = new Thread(threadLock, "窗口2");
t1.start();
t2.start();
}
@Override
public void run() {
while (integer > 1) {
synchronized (this) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "========卖出了第" + integer + "张票");
integer--;
}
}
}
}
5.1.1:synchronized使用注意
尽量缩小🔒,锁住代码的范围
5.2:Lock
Lock是在Java1.6之后引入的,让锁有了可操作性,意思就是可以在我们需要的时候去获取锁和释放锁,但是从使用方面来说,没有synchronized便捷
public class ThreadLock {
private Lock lock=new ReentrantLock();
void test(Thread thread){
// 获取锁对象
lock.lock();
System.out.println(thread.getName());
// 释放锁对象
lock.unlock();
}
public static void main(String[] args) {
ThreadLock threadLock=new ThreadLock();
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
threadLock.test(Thread.currentThread());
}
},"Lock锁1");
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
threadLock.test(Thread.currentThread());
}
},"Lock锁2");
thread1.start();
thread2.start();
}
}
5.2.1:Lock锁注意事项
Lock与TryLock
Lock在获取锁的时候,如果拿不到锁,就会一直在等待,直至获取🔒,但是对于TryLock来说,如果拿不到锁就会返回一个boolean值,停止等待获取锁