链接:blog.csdn.net/weixin_44104367/article/details/104481510
线程
线程的概念,百度是这样解释的:
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
进程:指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程——资源分配的最小单位。
线程:系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。
也就是,进程可以包含多个线程,而线程是程序执行的最小单位。
1.2 线程的状态
Notify 和 wait 的作用 首先看源码给出的解释,这里翻译了一下:
Notify
唤醒一个正在等待这个对象的线程监控。如果有任何线程正在等待这个对象,那么它们中的一个被选择被唤醒。选择是任意的,发生在执行的酌情权。一个线程等待一个对象通过调用一个{@code wait}方法进行监视。
notify()需要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁wait> 导致当前线程等待,直到另一个线程调用
{@link java.lang.Object#notify()}方法或 {@link java.lang.Object#notifyAll()}方法。 换句话说,这个方法的行为就像它简单一样 执行调用{@code wait(0)}。 当前线程必须拥有该对象的监视器。线程 释放此监视器的所有权,并等待另一个线程 通知等待该对象的监视器的线程唤醒 通过调用{@code notify}方法或 {@code notifyAll}方法。然后线程等待,直到它可以重新取得监视器的所有权,然后继续执行。
wait()的作用是使当前执行代码的线程进行等待,它是Object类的方法,该方法用来将当前线程置入预执行队列中,并且在wait所在的代码行处停止执行,直到接到通知或被中断为止。在调用wait方法之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait方法。
wait 和 sleep 的区别。
它们最大本质的区别是:sleep()不释放同步锁,wait()释放同步锁. 还有用法的上的不同是:sleep(milliseconds)可以用时间指定来使他自动醒过来,如果时间不到你只能调用interreput()来强行打断;wait()可以用notify()直接唤起.
wait和sleep的区别还有:
1. 相同 : sleep()和yield()都会释放CPU。
2. 不同: sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep()可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会;yield()只能使同优先级的线程有执行的机会。
死锁:指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。 死锁产生的四个必要条件(缺一不可):
我们可以根据死锁的四个必要条件破坏死锁的形成
并发:是指在某个时间段内,多任务交替的执行任务。当有多个线程在操作时,把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行。在一个时间段的线程代码运行时,其它线程处于挂起状。
并行:是指同一时刻同时处理多任务的能力。当有多个线程在操作时,cpu同时处理这些线程请求的能力。
区别就在于CPU是否能同时处理所有任务,并发不能,并行能
原子性:Atomic包、CAS算法、synchronized、Lock 可见性:synchronized、volatile(不能保证原子性) 有序性:happens-before规则
随着CPU核心的增多以及互联网迅速发展,单线程的程序处理速度越来越跟不上发展速度和大数据量的增长速度,多线程应运而生,充分利用CPU资源的同时,极大提高了程序处理速度。
public class ThreadCreateTest {
public static void main(String[] args) {
new MyThread().start();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
}
}
public class RunableCreateTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
}
}
public class CallableCreateTest {
public static void main(String[] args) throws Exception {
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
Integer sum = futureTask.get();
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
return sum;
}
}
实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承,但可以多实现啊),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。 实际开发中,阿里巴巴开发插件一直提倡使用线程池创建线程,原因在下方会解释,所以上面的代码我就只简写了一些demo
线程池,顾名思义,线程存放的地方。和数据库连接池一样,存在的目的就是为了较少系统开销,主要由以下几个特点:
Java提供四种线程池创建方式:
通过源码我们得知ThreadPoolExecutor继承自AbstractExecutorService,而AbstractExecutorService实现了ExecutorService
public class ThreadPoolExecutor extends AbstractExecutorService
public abstract class AbstractExecutorService implements ExecutorService
实际项目中,用的最多的就是ThreadPoolExecutor这个类,而《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 new ThreadPoolExecutor 实例的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
我们从 ThreadPoolExecutor入手多线程创建方式,先看一下线程池创建的最全参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
参数说明如下:
对于BlockingQueue个人感觉还需要单独拿出来说一下
BlockingQueue:阻塞队列,有先进先出(注重公平性)和先进后出(注重时效性)两种,常见的有两种阻塞队列:ArrayBlockingQueue和LinkedBlockingQueue
队列的数据结构大致如图:
队列一端进入,一端输出。而当队列满时,阻塞。BlockingQueue核心方法:1. 放入数据 put2. 获取数据take。常见的两种Queue:
基于数组实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
一段代码来验证一下:
package map;
import java.util.concurrent.*;
public class MyTestMap {
private static final int maxSize = 5;
public static void main(String[] args){
ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(maxSize);
new Thread(new Productor(queue)).start();
new Thread(new Customer(queue)).start();
}
}
class Customer implements Runnable {
private BlockingQueue<Integer> queue;
Customer(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
this.cusume();
}
private void cusume() {
while (true) {
try {
int count = (int) queue.take();
System.out.println("customer正在消费第" + count + "个商品===");
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Productor implements Runnable {
private BlockingQueue<Integer> queue;
private int count = 1;
Productor(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
this.product();
}
private void product() {
while (true) {
try {
queue.put(count);
System.out.println("生产者正在生产第" + count + "个商品");
count++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
基于链表的阻塞队列,内部也维护了一个数据缓冲队列。需要我们注意的是如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
java提供了4种丢弃处理的方法,当然你也可以自己实现,主要是要实现接口:RejectedExecutionHandler中的方法- AbortPolicy:不处理,直接抛出异常。
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
回到线程池创建ThreadPoolExecutor,我们了解了这些参数,再来看看ThreadPoolExecutor的内部工作原理:
进入execute方法可以看到:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
addWorker方法:
看源码第一反应就是这个CTL到底是个什么东东?有啥用?一番研究得出如下结论: ctl属性包含两个概念:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
我们点击workerCount即工作状态记录值,以RUNNING为例,RUNNING = -1 << COUNT_BITS;,即-1无符号左移COUNT_BITS位,进一步我们得知COUNT_BITS位29,因为Integer位数为31位(2的五次方减一)
private static final int COUNT_BITS = Integer.SIZE - 3;
既然是29位那么就是Running的值为
1110 0000 0000 0000 0000 0000 0000 0000
|||
31~29位
那低28位呢,就是记录当前线程的总线数啦
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
从上述代码可以看到workerCountOf
这个函数传入ctl之后,是通过ctl&CAPACITY操作来获取当前运行线程总数的。也就是RunningState|WorkCount&CAPACITY,算出来的就是低28位的值。因为CAPACITY得到的就是高3位(29-31位)位0,低28位(0-28位)都是1,所以得到的就是ctl中低28位的值。
而runStateOf
这个方法的话,算的就是RunningState|WorkCount&CAPACITY,高3位的值,因为CAPACITY是CAPACITY的取反,所以得到的就是高3位(29-31位)为1,低28位(0-28位)为0,所以通过&运算后,所得到的值就是高3为的值。
简单来说就是ctl中是高3位作为状态值,低28位作为线程总数值来进行存储。 总之这是个
看源码发现有两种近乎一样的方法,shutdownNow和shutdown,设计者这么设计自然是有它的道理,那么这两个方法的区别在哪呢?
shutdown会把线程池的状态改为SHUTDOWN,而shutdownNow把当前线程池状态改为STOP
shutdown只会中断所有空闲的线程,而shutdownNow会中断所有的线程。
shutdown返回方法为空,会将当前任务队列中的所有任务执行完毕;而shutdownNow把任务队列中的所有任务都取出来返回。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
就是任务在并不只执行创建时指定的firstTask第一任务,还会从任务队列的中自己主动取任务执行,而且是有或者无时间限定的阻塞等待,以保证线程的存活。 默认的是不允许
提及多线程又不得不提及多线程通信的机制。首先,要短信线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析:
题目:有两个线程A、B,A线程向一个集合里面依次添加元素"abc"字符串,一共添加十次,当添加到第五次的时候,希望B线程能够收到A线程的通知,然后B线程执行相关的业务操作。
package thread;
public class MyThreadTest {
public static void main(String[] args) throws Exception {
notifyThreadWithVolatile();
}
private static volatile boolean flag = false;
private static void notifyThreadWithVolatile() throws Exception {
Thread thc = new Thread("线程A"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i == 5) {
flag = true;
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
System.out.println(Thread.currentThread().getName() + "====" + i);
}
}
};
Thread thd = new Thread("线程B") {
@Override
public void run() {
while (true) {
while (flag) {
System.out.println(Thread.currentThread().getName() + "收到通知");
System.out.println("do something");
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return ;
}
}
}
};
thd.start();
Thread.sleep(1000L);
thc.start();
}
}
个人认为这是基本上最好的通信方式,因为A发出通知B能够立马接受并dosomething
加锁机制无非还是synchronized关键字或者JUC下的lock,这两者的异同以及使用synchronized锁机制 之 代码块锁
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8