作者:程序员小白条,个人博客
相信看了本文后,对你的面试是有一定帮助的!
⭐点赞⭐收藏⭐不迷路!⭐
在Java中,使用ThreadLocal实现线程安全的具体步骤如下:
在Java中,子线程无法直接访问父线程的ThreadLocal变量,因为ThreadLocal是线程封闭的,每个线程都拥有自己独立的变量副本。
但是,如果在创建子线程时将父线程的ThreadLocal变量传递给子线程,子线程就可以访问父线程的ThreadLocal变量了。具体实现方法如下:
1.在父线程中创建并初始化ThreadLocal变量。
2.在创建子线程时,将父线程的ThreadLocal变量作为参数传递给子线程。
3.在子线程中通过参数获取父线程的ThreadLocal变量,并使用该变量。
public class ParentThread {
// 创建父线程的ThreadLocal变量
public static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) {
// 设置父线程的ThreadLocal变量
threadLocal.set("Hello World!");
// 创建子线程并将父线程的ThreadLocal变量传递给子线程
Thread childThread = new Thread(new ChildThread(threadLocal.get()));
childThread.start();
}
}
class ChildThread implements Runnable {
private String value;
public ChildThread(String value) {
this.value = value;
}
public void run() {
// 在子线程中获取父线程的ThreadLocal变量
String parentValue = this.value;
System.out.println("Parent Thread Local Value: " + parentValue);
}
}
线程sleep 和wait 的区别:
1、这两个方法来自不同的类分别是Thread和Object
2、最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)
4、sleep必须捕获异常,wait必须捕获异常(之前看到网上很多博客都说wait方法不需要抛出异常这个观点是错误的,千万不要被误导了!!!!!!!notify和notifyAll方法确实可以不用抛出异常)
sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。
共同点:
它们都可以被interrupted方法中断。
Thread.Sleep(1000) 意思是在未来的1000毫秒内本线程不参与CPU竞争,1000毫秒过去之后,这时候也许另外一个线程正在使用CPU,那么这时候操作系统是不会重新分配CPU的,直到那个线程挂起或结束,即使这个时候恰巧轮到操作系统进行CPU 分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去。另外值得一提的是Thread.Sleep(0)的作用,就是触发操作系统立刻重新进行一次CPU竞争,竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。
wait(1000)表示将锁释放1000毫秒,到时间后如果锁没有被其他线程占用,则再次得到锁,然后wait方法结束,执行后面的代码,如果锁被其他线程占用,则等待其他线程释放锁。注意,设置了超时时间的wait方法一旦过了超时时间,并不需要其他线程执行notify也能自动解除阻塞,但是如果没设置超时时间的wait方法必须等待其他线程执行notify。sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
在调用sleep()方法的过程中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
1.闭锁CountDownLatch闭锁是典型的等待事件发生的同步工具类,将闭锁的初始值设置1,所有线程调用await方法等待,当事件发生时调用countDown将闭锁值减为0,则所有await等待闭锁的线程得以继续执行。
2.阻塞队列BlockingQueue所有等待事件的线程尝试从空的阻塞队列获取元素,将阻塞,当事件发生时,向阻塞队列中同时放入N个元素(N的值与等待的线程数相同),则所有等待的线程从阻塞队列中取出元素后得以继续执行。
3.信号量Semaphore设置信号量的初始值为等待的线程数N,一开始将信号量申请完,让剩余的信号量为0,待事件发生时,同时释放N个占用的信号量,则等待信号量的所有线程将获取信号量得以继续执行。
4.栅栏CyclicBarrier设置栅栏的初始值为1,当事件发生时,调用barrier.wait()冲破设置的栅栏,将调用指定的Runable线程执行,在该线程中启动N个新的子线程执行。这个方法并不是让执行中的线程全部等待在某个点,待某一事件发生后继续执行。
特别注意:不能用“条件队列”,多个线程阻塞等待在条件队列上,事件发生时调用“条件队列”的notifyAll方法或者signalAll方法虽然能唤醒所有等待线程,但是只有一个线程能够获得该条件队列的锁得以调度执行,其它线程未获得锁仍将继续阻塞等待。
1.使用 synchronized 关键字
public class MyThread implements Runnable {
private int i;
public MyThread(int i) {
this.i = i;
}
@Override
public void run() {
synchronized (this) {
System.out.println(i++);
}
}
}
2.Atomic变量
使用AtomicInteger类来代替int类型,可以保证原子性。AtomicInteger类提供了线程安全的原子操作方法,如incrementAndGet()。
private AtomicInteger i = new AtomicInteger(0);
public void increment() {
i.incrementAndGet();
}
3.Lock对象
使用Lock对象来对共享变量进行同步,保证同一时刻只有一个线程能够访问该变量。
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
4.ThreadLocal变量
使用ThreadLocal变量来对共享变量进行封装,每个线程都拥有自己的副本,可以保证线程安全。
private ThreadLocal threadLocal = new ThreadLocal() {
@Override
protected Integer initialValue() {
return 0;
}
};
public void increment() {
threadLocal.set(threadLocal.get() + 1);
}
volatile 关键字用于标记一个变量是 volatile 的,这意味着这个变量的值可能会被多个线程同时修改,因此需要及时更新到内存中,以确保其他线程能够及时看到值的变化。
corePoolSize:核心线程数
maxiumPoolSize:最大线程数
keepAliveTime:空闲存活时间
unit: 时间单位
workQueue:任务队列
threadFactory: 线程工厂
handler:拒绝策略
线程池的执行流程有 3 个重要的判断点
如果在经过上诉三个过程后, 得到的结果都是 true , 那么就会执行线程池的拒绝策略.
二. 拒绝策略当任务过多且线程池的任务队列已满时, 此时就会执行线程池的拒绝策略, 线程池的拒绝策略默认有以下 4 种:
AbortPolicy:中止策略,线程池会抛出异常并中止执行此任务.CallerRunsPolicy:调用方运行策略,把任务交给添加此任务的(main)线程来执行.DiscardPolicy:放弃最新策略,忽略此任务,忽略最新的一个任务.DiscardOldestPolicy:放弃最旧策略,忽略最早的任务,最先加入队列的任务.
一般情况下,任务分为IO密集型和CPU密集型。
CPU密集型:一般设置为cpu核数+1
IO密集型:一般设置为2n,以IO能力为主。