https://blog.csdn.net/beidaol/article/details/89135277
3 继承Thread类
调用run()方法后,主线程去执行完run()方法后再执行主线程的方法;
调用start()方法后,会新建一个子线程去执行run()方法,主线程和子线程交替执行。
12 线程休眠sleep
每个对象都有一个锁,sleep()不会释放锁
13 线程礼让yield
Thread.yield();
- 礼让线程,让当前线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功!看cpu心情
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程开始执行");
}
如果礼让成功,会让出cpu给b执行
否则cpu继续执行a
14 线程强制执行 join
插队执行线程
Thread.join()
15 观测线程状态
Thread.State state = thread.getState();
16 线程的优先级
thread.setPriority(4);
17 守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如后台记录操作日志,监控内存,垃圾回收等待
thread.setDaemon(true);//默认是false表示用户线程
18 线程同步机制
-
并发:同一个对象被多个线程同时操作,每个对象都有锁
-
由于同一进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题: -
一个线程持有锁会导致其他所有需要此锁的线程挂起
-
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
-
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题
19 三大不安全案例
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
输出结果小于10000,因为ArrayList不安全,两个线程同一时间给同一位置赋值时覆盖掉了,所以小于10000
20 同步方法及同步块
- synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized将会影响效率
- 方法里面需要修改的内容才需要锁,锁太多,浪费资源
- synchronized同步方法锁的是this,当前对像
- synchronized同步块可以锁任何东西
例如以下代码同步方法锁的是BuyTicket
class BuyTicket implements Runnable {
//票
private int ticketNums = 10;
// 外部停止
boolean flag = true;
@Override
public synchronized void run() {
// 买票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
if (ticketNums <=0) {
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
以下代码同步块锁的是account
class Account{
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
class Drawing extends Thread{
Account account;
int drawingMoney;
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
//所得对象是变化的量,需要增删改的
synchronized (account) {
if (account.money-drawingMoney<0) {
System.out.println(Thread.currentThread().getName()+"钱不够了,取不了");
return;
}
}
account.money = account.money - drawingMoney;
nowMoney = nowMoney + drawingMoney;
System.out.println(account+"余额为:"+account.money);
}
}
22 死锁
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种首尾相接的循环等待资源关系
上面列出的四个必要条件,只要想办法破其中一个或多个条件就可以避免死锁发生
23 LOCK锁
- Lock是显式锁(手动开启和关闭锁)syncronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,syncronized有代码块和方法锁
- 使用Lock锁,jvm将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体外)
@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (ticketNum > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNum--);
} else {
break;
}
} finally {
//解锁
lock.unlock();
}
}
}
25 管程法
执行wait()方法时,this(当前对像)线程会停这里,直到notify()唤醒才往下执行
26 信号灯法
27 线程池
线程池的优势
(1)降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。
线程池的主要参数
(1)corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)
(2)maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。
(3)keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
(4)workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
(5)threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
(6)handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。
线程池流程
1、判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。已满则。
2、判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则。
3、判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和策略。
原创文章,作者:745907710,如若转载,请注明出处:https://blog.ytso.com/277119.html