充分利用cpu的多核心,实现高效率的程序。在单核cup上并不能实现高效率。
通过Thread类,实例化Thread类,重写run()方法。再调用start()方法。start()方法的含义是定义好线程需要执行的代码,交给系统去调度,系统会之后的合适时间启动一个线程去执行run()方法中的代码。
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
* - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
*
* A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called Object.wait()
* on an object is waiting for another thread to call
* Object.notify() or Object.notifyAll() on
* that object. A thread that has called Thread.join()
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
*
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
多个线程共同执行的代码片段。临界区中对变量的操作是不安全的,这里的不安全是指程序可能得不到预期的结果。
不安全的原因是:
1. cup和内存之间存在高速缓存,cup处理处理内存时,先将值copy到高速缓存,再计算,计算完的结果再结果写入高速缓存,最后将高速缓存的值刷新回内存中。
2. 临界区的代码指令在多线程情况下,可能会出现交叉执行。
synchronized关键字包裹临界区代码。
synchronized的底层实现是对象锁,当代码执行到synchronized时,会对synchronized修饰的对象的对象头进行修改,同时native层生成一个Monitor对象,这个Monitor对象内存地址会被存储到synchronized修饰的对象的对象头中;Monitor对象中有3个关键属性owner,entryList,waitSet
owner:代表争抢到锁的线程
entryList:代表争抢失败的线程,进入阻塞状态,被加入到这个链表里。
waitSet:代表调用了Object.wait()方法的线程,进入等待状态,被加入到这个链表里。
synchronized可以使用地方
这两方法是Object方法,必须在synchronized块内使用。
wait:当前持有锁的线程让出锁的,进入waitSet中,线程状态变为waiting状态。在entryList中线程开始争抢锁,争抢到锁的线程成为锁的拥有者。
notify:从waitSet中随机取一个线程,将其唤醒。
notifyAll:唤醒所有wait线程,让他们争抢锁,只一个线程可以争抢到锁成为锁的拥有者。
案例:自定义阻塞队列
package org.example;
import java.util.concurrent.TimeUnit;
public class MyBlockQueue {
public static void main(String[] args) {
BlockQuene blockQuene = new BlockQuene(5);
Thread putThread = new Thread("putThread"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
blockQuene.put(i);
System.out.println("put" + i);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
putThread.start();
for (int i = 0; i < 10; i++) {
new Thread(String.valueOf(i)){
@Override
public void run() {
int e = blockQuene.get();
System.out.println(Thread.currentThread().getName() + "线程取到结果:" + e);
}
}.start();
}
}
public static class BlockQuene {
private final int[] elements;
private int getIndex, putIndex;
private int size;
public BlockQuene(int capcity) {
elements = new int[capcity];
getIndex = 0;
putIndex = 0;
size = 0;
}
public synchronized void put(int element) {
while (size == elements.length) {
//满了
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
elements[putIndex] = element;
putIndex = (putIndex + 1) % elements.length;
size++;
notifyAll();
}
public synchronized int get(){
while (size == 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int result = elements[getIndex];
getIndex = (getIndex + 1) % elements.length;
size--;
notifyAll();
return result;
}
}
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:救急线程在空闲状态下,存活的时间
unit:救急线程在空闲状态下,存活的时间的单位
workQueue:任务队列
threadFactory:线程工厂,用于实例化线程
handler:拒绝策略
线程池的基本工作流程:
(1) 公平锁或非公平锁(下面案例中有实际代码,自己执行一遍更容易理解)
a. 公平锁:先来的线程先执行,排成排按顺序。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
b. 非公平锁:后来的线程有可能先执行,可插队不一定按顺序。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
(2) 互斥锁
一次只能执行一个线程。
(3) 可重入锁
同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
(1) 可中断并且可以设置超时时间。
(2) 可以根据业务场景使用公平锁或非公平锁。
(3) 获取锁可设置超时。
(4) 可绑定多个条件(Condition)。
getHoldCount():当前线程调用 lock() 方法的次数。
getQueueLength():当前正在等待获取 Lock 锁的线程的估计数。
getWaitQueueLength(Condition condition):当前正在等待状态的线程的估计数,需要传入 Condition 对象。
hasWaiters(Condition condition):查询是否有线程正在等待与 Lock 锁有关的 Condition 条件。
hasQueuedThread(Thread thread):查询指定的线程是否正在等待获取 Lock 锁。
hasQueuedThreads():查询是否有线程正在等待获取此锁定。
isFair():判断当前 Lock 锁是不是公平锁。
isHeldByCurrentThread():查询当前线程是否保持此锁定。
isLocked():查询此锁定是否由任意线程保持。
tryLock():线程尝试获取锁,如果获取成功,则返回 true,如果获取失败(即锁已被其他线程获取),则返回 false。
tryLock(long timeout,TimeUnit unit):线程如果在指定等待时间内获得了锁,就返回true,否则返回 false。
lockInterruptibly():如果当前线程未被中断,则获取该锁定,如果已经被中断则出现异常。
(1) 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。
(2) 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
说明:线程一需要对表A、B、C依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是A、B、C,否则可能出现死锁。
(3) 在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。
a. 在lock方法与try代码块之间的方法调用抛出异常,无法解锁,造成其它线程无法成功获取锁。
b. 如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。
c. 在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。
d. 在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。
package org.example;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo1 {
public static void main(String[] args) throws InterruptedException {
// testUnsafe();
testSafe();
}
private static void testSafe() throws InterruptedException {
IntAdderSafe intAdderUnsafe = new IntAdderSafe();
Thread thread1= new Thread(() -> {
for (int i = 0; i < 5000; i++) {
intAdderUnsafe.increment();
}
});
Thread thread2= new Thread(() -> {
for (int i = 0; i < 5000; i++) {
intAdderUnsafe.increment();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("count is " + intAdderUnsafe.get());
}
private static void testUnsafe() throws InterruptedException {
IntAdderUnsafe intAdderUnsafe = new IntAdderUnsafe();
Thread thread1= new Thread(() -> {
for (int i = 0; i < 5000; i++) {
intAdderUnsafe.increment();
}
});
Thread thread2= new Thread(() -> {
for (int i = 0; i < 5000; i++) {
intAdderUnsafe.increment();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("count is " + intAdderUnsafe.get());
}
static class IntAdderUnsafe{
private int count = 0;
public void increment(){
count++;
}
public int get(){
return count;
}
}
static class IntAdderSafe{
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment(){
lock.lock();
try {
count++;
}finally {
lock.unlock();
}
}
public int get(){
return count;
}
}
}
CountDownLatch 是 Java 中的一个并发工具类,用于协调多个线程之间的同步。其作用是让某一个线程等待多个线程的操作完成之后再执行。它可以使一个或多个线程等待一组事件的发生,而其他的线程则可以触发这组事件。
1. CountDownLatch 可以用于控制一个或多个线程等待多个任务完成后再执行。
2. CountDownLatch 的计数器只能够被减少,不能够被增加。
3. CountDownLatch 的计数器初始值为正整数,每次调用 countDown() 方法会将计数器减 1,计数器为 0 时,等待线程开始执行。
CountDownLatch 的实现原理比较简单,它主要依赖于 AQS(AbstractQueuedSynchronizer)框架来实现线程的同步。
CountDownLatch 内部维护了一个计数器,该计数器初始值为 N,代表需要等待的线程数目,当一个线程完成了需要等待的任务后,就会调用 countDown() 方法将计数器减 1,当计数器的值为 0 时,等待的线程就会开始执行。
1. 主线程等待多个子线程完成任务后再继续执行。例如:一个大型的任务需要被拆分成多个子任务并交由多个线程并行处理,等所有子任务都完成后再将处理结果进行合并。
2. 启动多个线程并发执行任务,等待所有线程执行完毕后进行结果汇总。例如:在一个并发请求量比较大的 Web 服务中,可以使用 CountDownLatch 控制多个线程同时处理请求,等待所有线程处理完毕后将结果进行汇总。
3. 线程 A 等待线程 B 执行完某个任务后再执行自己的任务。例如:在分布式系统中,一个节点需要等待其他节点的加入后才能执行某个任务,可以使用 CountDownLatch 控制节点的加入,等所有节点都加入完成后再执行任务。
4. 多个线程等待一个共享资源的初始化完成后再进行操作。例如:在某个资源初始化较慢的系统中,可以使用 CountDownLatch 控制多个线程等待共享资源初始化完成后再进行操作。
CountDownLatch 适用于多线程任务的协同处理场景,能够有效提升多线程任务的执行效率,同时也能够降低多线程任务的复杂度和出错率。
五、注意事项
1. CountDownLatch 对象的计数器只能减不能增,即一旦计数器为 0,就无法再重新设置为其他值,因此在使用时需要根据实际需要设置初始值。
2. CountDownLatch 的计数器是线程安全的,多个线程可以同时调用 countDown() 方法,而不会产生冲突。
3. 如果 CountDownLatch 的计数器已经为 0,再次调用 countDown() 方法也不会产生任何效果。
4. 如果在等待过程中,有线程发生异常或被中断,计数器的值可能不会减少到 0,因此在使用时需要根据实际情况进行异常处理。
5.CountDownLatch 可以与其他同步工具(如 Semaphore、CyclicBarrier)结合使用,实现更复杂的多线程同步。
package org.example;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class CountDownLatchDemo1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
Worker worker1 = new Worker(latch,"张三");
Worker worker2 = new Worker(latch,"李四");
Worker worker3 = new Worker(latch,"王五");
Driver driver = new Driver(latch,"老王");
driver.start();
worker1.start();
worker2.start();
worker3.start();
driver.join();
}
static class Worker extends Thread{
private CountDownLatch latch;
public Worker(CountDownLatch latch,String name) {
super(name);
this.latch = latch;
}
@Override
public void run() {
try {
Log.d(getName() + " 开始装车");
Random random = new Random();
int timeout = random.nextInt(10)+ 1;
Log.d(timeout);
TimeUnit.SECONDS.sleep(timeout);
Log.d(getName() +" 开始装车,完成");
} catch (InterruptedException e) {
Log.d(getName() + "装车异常");
} finally {
latch.countDown();
}
}
}
static class Driver extends Thread{
private CountDownLatch latch;
public Driver(CountDownLatch latch,String name) {
super(name);
this.latch = latch;
}
@Override
public void run() {
Log.d("等待工人装车");
try {
latch.await();
Log.d("所有工人装车完毕,出发");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
CyclicBarrier是Java并发包中的一个同步工具,用于控制多个线程在某个时刻达到一个共同的屏障点(Barrier Point),然后再同时继续执行。当所有线程都到达屏障点时,屏障就会打开,所有线程可以继续执行,直到下一个屏障点再次等待所有线程到达。
1. 可重用
当所有线程到达屏障点后,可以重置屏障,让所有线程再次从屏障点开始执行。
2. 可带参数
可以指定一个Runnable对象,在所有线程都到达屏障点后,先执行这个Runnable对象。
3. 线程同步
CyclicBarrier会阻塞所有线程,直到所有线程都到达屏障点,然后才会继续执行。
4. 灵活性
可以指定屏障点的数量,即到达屏障点的线程数量,也可以在构造函数中指定一个处理器,处理所有到达屏障点的线程。
5. 线程安全
CyclicBarrier是线程安全的,可以安全地用于多线程环境。
CyclicBarrier的实现原理是基于ReentrantLock和Condition的,它使用一个计数器来记录到达屏障点的线程数量。当一个线程调用await()方法时,它会被阻塞并加入到等待队列中,直到计数器的值达到指定的数量。此时,所有被阻塞的线程都会被唤醒,并继续执行。
为了实现重用,CyclicBarrier会在计数器的值达到指定数量后,重新初始化计数器,并调用指定的Runnable对象。同时,它还会使用一个boolean类型的变量来记录屏障是否被打开。
1. 数据流水线
在一个数据流水线中,不同的线程负责不同的阶段,每个线程处理完数据后,需要等待其他线程完成,才能继续执行下一个阶段。
2. 并行计算
在并行计算中,多个线程需要同时开始执行任务,然后等待所有线程都完成任务后,再汇总结果。
3. 多线程初始化
在初始化多个线程时,某些线程需要等待其他线程初始化完成后,才能开始执行。
1. CyclicBarrier的reset()方法可以重置屏障,使计数器回到初始值。如果某个线程在等待时调用了reset()方法,所有正在等待的线程都会抛出BrokenBarrierException异常。
2. CyclicBarrier的构造函数中可以指定一个Runnable对象,当计数器值达到设定值时,会自动执行该Runnable对象。
3. CyclicBarrier的await()方法有多个重载版本,其中最常用的是不带参数的await()方法,它会让当前线程等待直到所有线程都到达屏障点。还有一个带参数的await(long timeout, TimeUnit unit)方法,它会让当前线程等待一定的时间,如果在指定的时间内其他线程没有到达屏障点,当前线程会抛出TimeoutException异常。
4. 在Java 8中,CyclicBarrier还新增了getNumberWaiting()和getParties()两个方法,可以分别获取当前正在等待的线程数量和参与屏障的线程总数。
5. CyclicBarrier适用于一组线程需要相互等待,然后一起执行下一步操作的场景,例如多线程计算矩阵和、多线程下载文件后合并等。
六、CyclicBarrier与CountDownLatch区别
1. CyclicBarrier的计数器可以重置,可以用来反复使用;而CountDownLatch的计数器只能使用一次;
2. CyclicBarrier可以让一组线程在达到屏障点之前互相等待,然后一起执行下一步操作;而CountDownLatch只能让等待的线程在计数器为0时同时开始执行下一步操作;
3. CyclicBarrier可以指定一个Runnable对象,在计数器值达到设定值时自动执行;而CountDownLatch没有类似的功能;
因此,需要根据具体的需求和场景来选择合适的同步工具。如果需要在多个线程之间反复同步,那么CyclicBarrier是一个更好的选择;如果只需要在所有线程都准备好后一起执行下一步操作,那么,CountDownLatch则是更合适的选择。
package org.example;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;
public class CyclicBarrierDemo1 {
public static void main(String[] args) {
int count = 3;
CyclicBarrier barrier = new CyclicBarrier(count, () -> {
Log.d("全部任务完成");
});
for (int i = 0; i < count; i++) {
new Thread(() -> {
try {
Log.d("开始执行任务");
Random random = new Random();
int time = random.nextInt(10);
Log.d("执行任务需要的时间:" + time + "秒");
TimeUnit.SECONDS.sleep(time);
Log.d("任务执行完成,开始等待");
barrier.await();
Log.d("等待完毕");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, "线程" + i).start();
}
}
}
Semaphore(信号量)是Java中一个用于多线程编程的工具类,它提供了一种控制访问共享资源的机制,以避免多个线程同时访问同一个共享资源造成的冲突问题。Semaphore可以看做是一种计数器,用来控制同时访问某个共享资源的线程个数,也就是说,它可以控制并发线程的数量。
1. 计数器:Semaphore内部维护了一个计数器,表示当前可用的许可证数量。
2. 许可证:Semaphore可以控制同时访问某个共享资源的线程个数,每个线程需要获取一个许可证才能访问共享资源。当许可证数量为0时,其他线程需要等待。
3. 公平性:Semaphore可以选择公平模式或非公平模式。在公平模式下,等待时间最长的线程会优先获取许可证。
4. 释放许可证:当一个线程访问共享资源结束后,需要释放许可证以便其他线程可以访问。
5. 动态调整:Semaphore的许可证数量可以动态调整。
Semaphore的实现基于AQS(AbstractQueuedSynchronizer)类,AQS是Java中一个同步工具类的基础框架,Semaphore就是基于AQS的共享模式实现的。
Semaphore维护了一个内部的计数器,每次请求一个许可证时,计数器减一,释放一个许可证时,计数器加一。当计数器为0时,其他请求许可证的线程会被阻塞,直到有许可证可用。Semaphore内部维护了一个等待队列,当许可证不可用时,请求许可证的线程会被加入等待队列中。
Semaphore实现了公平性和非公平性两种模式。在公平模式下,等待时间最长的线程会优先获取许可证。在非公平模式下,请求许可证的线程会直接尝试获取许可证,不会排队等待。
1. 限流:在高并发的场景下,可以使用Semaphore来限制同时访问某个资源的线程数量,从而达到限流的目的。
2. 线程池:可以使用Semaphore来控制线程池中的线程数量,当线程池中的线程数量达到Semaphore的许可证数量时,新的任务会被阻塞等待。
3. 生产者消费者模式:可以使用Semaphore来控制生产者和消费者的数量,当生产者或消费者数量达到Semaphore的许可证数量时,另一个角色会被阻塞等待,从而实现生产者消费者模式的协调和控制。
4. 线程间通信:Semaphore可以用于线程间的通信,当线程需要等待某些条件满足时,可以使用Semaphore来进行阻塞等待,并在条件满足时释放许可证。
5. 限制资源并发访问:Semaphore可以用于限制某个资源的并发访问数量,从而避免因过多的并发访问而导致资源崩溃或性能下降等问题。
1. 许可证数量的设置:许可证数量需要根据实际场景进行设置,如果许可证数量过少,可能会导致线程一直被阻塞等待,从而影响系统性能;如果许可证数量过多,可能会导致系统资源的浪费。
2. 许可证的获取和释放:在使用Semaphore时,需要确保许可证的获取和释放是成对出现的,即每个线程获取到的许可证一定要及时释放,否则可能会导致其他线程一直被阻塞等待,从而影响系统性能。
3. 错误处理:在使用Semaphore时,需要考虑可能出现的异常情况,如许可证数量不足、线程被中断等,需要进行合理的错误处理,以保证程序的健壮性和可靠性。
4. 公平模式和非公平模式:需要根据实际场景选择使用公平模式还是非公平模式,如果要保证所有线程获取许可证的顺序是按照请求的顺序,那么需要使用公平模式;否则,可以选择非公平模式以提高系统的性能。
5. 多个Semaphore实例的协调和控制:如果有多个Semaphore实例需要进行协调和控制,需要根据实际场景进行合理的设计和组织,以保证系统的可靠性和高效性。
使用时需要注意合理设置许可证数量,确保许可证的获取和释放是成对出现的,并进行合理的错误处理和模式选择,以达到最佳的效果。
package org.example;
import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo1 {
public static void main(String[] args) {
Resource resource = new Resource(3);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
resource.access();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程" + i).start();
}
}
static class Resource{
private final Semaphore semaphore;
Resource(int limit) {
this.semaphore = new Semaphore(limit);
}
public void access() throws InterruptedException {
semaphore.acquire();
try {
Log.d("开始访问资源");
Random random = new Random();
int time = random.nextInt(10);
Log.d("访问资源耗时:" + time + "s");
TimeUnit.SECONDS.sleep(time);
}finally {
Log.d("访问资源结束");
semaphore.release();
}
}
}
}