1.线程与进程之间的关系?
进程相当与一个应用程序,一个公司,而线程相当于程序里面的一些操作,公司里面负责做不同工作的人。
2.线程在java中如何理解?
线程之间共享方法区和堆内存,但是栈内存不共享。
3.如何开启多线程?
线程可以通过继承或则接口实现,
1.继承的话就是实现一个Thread类,需要重写其中的run()方法,ps:多线程开启的时候,run()方法与主栈中的main()方法地位是一样的。重写run()之后,然后在main()中new Thread()之后,调用start()方法就打开了多线程。
package com.weijisheng.thread;
import com.sun.org.apache.bcel.internal.generic.NEW;
/**
* @author weijisheng
* @create 2021-07-05 22:27
*/
public class MyThread {
public static void main(String[] args) {
MyThread1 thread1 = new MyThread1();
//start()方法的作用是:启动了一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
//这段代码的任务是为了开启一个新的栈空间,只要新的栈开辟出来,start()方法就结束了。线程就启动成功了。
//启动成功的线程会自动调用run()方法,并且run()方法在分支栈的栈底部(压栈)。
//run()方法在分支栈的栈底部,main()方法在主栈的栈底部。run()和main()是平级的。
thread1.start();
// thread1.run();这个方法不会开辟新的栈空间
for (int i = 1; i <= 1000; i++) {
System.out.println("主线程在启动" + i);
}
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
System.out.println("副线程在启动" + i);
}
}
}
2.实现接口的话就是实现Runnable接口,需要注意的是必须要把实现Runnable接口的类传入new出来的Thread()类里面才是多线程开始,然后调用Thread()的start()方法就打开了多线程。这里值得一提的是就是匿名内部类,可以直接new Thread(new Runnable(){public void run()}
);匿名内部类不是new的接口,new[匿名类]实现接口{实现方法}
package com.weijisheng.thread;
import com.sun.org.apache.bcel.internal.generic.NEW;
/**
* @author weijisheng
* @create 2021-07-05 22:27
*/
public class MyThread {
public static void main(String[] args) {
MyThread1 thread1 = new MyThread1();
//start()方法的作用是:启动了一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
//这段代码的任务是为了开启一个新的栈空间,只要新的栈开辟出来,start()方法就结束了。线程就启动成功了。
//启动成功的线程会自动调用run()方法,并且run()方法在分支栈的栈底部(压栈)。
//run()方法在分支栈的栈底部,main()方法在主栈的栈底部。run()和main()是平级的。
thread1.start();
// thread1.run();这个方法不会开辟新的栈空间
for (int i = 1; i <= 1000; i++) {
System.out.println("主线程在启动" + i);
}
}
}
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
System.out.println("副线程在启动" + i);
}
}
}
4.线程的生命周期?
5.如何得到线程的名字?
5.1.当前线程对象.setName();设置线程的名字
5.2.当前线程对象.getName();得到线程的名字
java.lang.Thread()里面有个得到当前线程对象的静态方法Thread.currentThread();//常用
6.线程的sleep()方法
6.1.java.lang.Thread()里面 静态方法Thread.sleep() 可以让当前线程进行睡眠,出现在哪个线程中哪个线程就会睡眠。
6.2.线程中断
合理终端线程的方式:可以打一个布尔值来终端线程。
package com.weijisheng.thread;
/**
* @author weijisheng
* @create 2021-07-08 9:33
*/
//合理结束线程的方法
public class ThreadStop {
public static void main(String[] args) {
MyRunnableT t = new MyRunnableT();
Thread thread = new Thread(t);
thread.setName("分支线程");
thread.start();
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.run=false;
}
}
class MyRunnableT implements Runnable {
boolean run=true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(run){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}else {
//在return;之前有什么没有保存的数据可以在这里保存
return;
}
}
}
}
7.关于线程的调度问题(了解)
1.常见的线程调度模型
1.1.抢占式调度模型:
哪个线程的优先级比较高,抢到的cpu时间片的概率就高一点,java采用的就是抢占式调度模型。
1.2.均分式调度模型:
平均分配cpu时间片,每个线程占有的cpu时间片长度一样,平均分配,一切平等。有一些编程语言,采用的是这种。
2.Java中与线程调度有关的方法
实例方法:
int getPriority() 获取线程调度的优先级
最低优先级是1,最高优先级是10,默认优先级是5
void setPriority(int newPriority) 设置线程的优先级
静态方法:static void yield() 停止执行当前线程,执行其他线程。
void join() 合并线程:内存上栈等待。
8.线程安全问题
8.1 什么时候数据在多线程并发的环境下会存在安全问题呢?
1.多线程并发
2.有共享数据
3.共享数据有修改行为
8.2 如何解决线程安全问题呢?
1.线程排队执行2.用排队问题解决线程安全问题。这种机制成为
**线程同步机制**。
Synchoronized(共享内容){}可以实现线程安全。三大变量中局部变量永远不存在线程不安全,因为一个线程一个栈。常量也不会有线程安全问题。
Synchoronized出现在实例方法上,一定锁的是this。不能是其他对象了,这种方式不灵活。出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围。导致程序执行的效率降低。优点:代码写得少了,节俭了。如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。
如果使用局部变量的话,建议使用StringBuilder,因为局部变量不存在线程安全问题,选择StringBuilder,StringBuffer效率较低。
Synchoronized三种书写方式:
第一种:同步代码块,比较灵活。
Synchoronized(线程共享对象){
同步代码块;
}
第二种:在实例变量上使用Synchoronized关键字,表示共享对象一定是this,并且同步代码块一定是整个方法体。
第三种:在静态方法上使用Synchoronized
表示找类锁。类锁永远只有1把。就算创建了100个对象,那类锁也只有一把。
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
对象锁:1个对象1把锁,100个对象100把锁
类锁:100个对象也可能只有1把类锁
死锁代码重要
package 线程安全;
import com.sun.org.apache.bcel.internal.generic.NEW;
/**
* @author weijisheng
* @create 2021-07-15 23:36
*/
//死锁代码
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
//线程共享o1,o2
Thread myThread1 = new MyThread1(o1, o2);
Thread myThread2 = new MyThread2(o1, o2);
myThread1.start();
myThread2.start();
}
}
class MyThread1 extends Thread {
Object o1;
Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
}
}
}
}
- Lock锁的介绍
- Lock锁的使用
讲解
java.util.concurrent.locks.Lock
机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更加面向对象
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock()
:加同步锁。public void unlock()
:释放同步锁。
代码如下:
package 线程安全.窗口卖票;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author weijisheng
* @create 2021-08-10 0:07
*/
public class MyRunnable implements Runnable {
// 共享变量
int tickets = 100;
Lock lock1 = new ReentrantLock();//Lock锁
@Override
//synchronized(锁对象){}锁对象可以是任意类的对象
public void run() {
//synchronized (this){}//加锁
// 线程的任务代码---卖票
lock1.lock();//加锁
while (true) {
/* try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
if (tickets < 1) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 暂停100ms模拟收钱的操作
System.out.println(Thread.currentThread().getName() + ":正在出售第" + tickets + "张票");
tickets--;
}
lock1.unlock();//释放锁
}
}
package 线程安全.窗口卖票;
/**
* @author weijisheng
* @create 2021-08-10 0:09
*/
public class Test {
public static void main(String[] args) {
/*
需求: 模拟电影院4个窗口卖100张电影票
分析:
售票窗口: 使用线程来模拟
4个窗口共同卖100张票
4个窗口卖票的任务是一样的(线程的任务代码是一样的)
*/
// 创建任务对象
MyRunnable mr = new MyRunnable();
// 创建4个窗口---创建4条线程
Thread t1 = new Thread(mr, "窗口1");
Thread t2 = new Thread(mr, "窗口2");
Thread t3 = new Thread(mr, "窗口3");
Thread t4 = new Thread(mr, "窗口4");
// 启动线程,执行任务
t1.start();
t2.start();
t3.start();
t4.start();
}
}
9.开发中如何避免线程安全问题?
第一种方案:尽量使用局部变量代替"实例变量和静态变量"。
第二种方案:如果必须是实例变量可以创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)。
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候只能选择synchronized。线程同步机制。
10.线程其他内容
10.1.守护线程。
java语言中线程分为两大类:
一类是:用户线程。
一类是:守护线程(后台线程)
其中最有代表的是:垃圾回收线程(守护线程)
守护线程的特点:
一般守护线程就是一个死循环,所有的用户线程只要结束,守护线程自动结束。
注意:主线程main是一个用户线程。
守护线程用在什么地方呢?
每天00:00的时候系统数据自动备份。
这个需要使用到定时器,并且我们可以将定时器设置为守护线程,一直在那里看着,每到00:00的时候备份一次,所有的用户线程如果结束了。守护线程自动退出,没有必要进行数据备份了。
引用.setDaemon(true);//设置守护线程
10.2定时器。
作用:间隔特定的时间,执行特定的程序。
定时器的作用:
间隔特定的时间,执行特定的程序。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只需要简单的配置,就可以完成定时器的任务。底层依旧是使用了java.util.Timer().
10.3实现线程的第三种方式 :FutureTask方式,实现Callable接口(JDK8新特性)
10.4关于object类中的wait和notify方法。(生产者和消费者模式)
第一:wait()和notify()不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是object类中自带的方法。
wait和notify也不是线程对象调用。
t.wait不对,t.notify也不对。
第二:wait方法作用?
Object o=new Object();
o.wait();
表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。并且释放之前占有o对象上的锁。
第三:notify方法作用?
o.notify是让正在o对象上等待的线程唤醒。
表示唤醒正在o对象上处于等待的所有线程。
o.notify只会通知,不会释放之前占有的o对象上的锁。
以下代码只供学习使用,自写的,能保证线程安全,考虑依旧不周全。
package 线程安全.线程模拟购票;
import java.util.Scanner;
/**
* @author weijisheng
* @create 2021-07-16 10:27
*/
public class ThreadSafe {
public static void main(String[] args) {
Ck ck = new Ck();
ck.setTicket(100);
Thread t1 = new MyThread1(ck);
Thread t2 = new MyThread1(ck);
t1.setName("t1购票线程");
t2.setName("t2购票线程");
t1.start();
t2.start();
}
}
class MyThread1 extends Thread {
Ck ck;
public MyThread1(Ck ck) {
this.ck = ck;
}
public void gouPiao(int num) {
int ticket1 = ck.getTicket1(num);
System.out.println(Thread.currentThread().getName() + "购买了" + num + "张票" + "剩余" + ticket1 + "张票");
}
public MyThread1() {
}
public void run() {
MyThread1 thread1 = new MyThread1(ck);
Scanner scanner = new Scanner(System.in);
System.out.println(Thread.currentThread().getName()+"请输入想要购买的票数");
int i = scanner.nextInt();
thread1.gouPiao(i);
}
}
class MyThread2 extends Thread {
Ck ck;
public MyThread2(Ck ck) {
this.ck = ck;
}
public void gouPiao(int num) {
int ticket1 = ck.getTicket1(num);
System.out.println(Thread.currentThread().getName() + "购买了" + num + "张票" + "剩余" + ticket1 + "张票");
}
public MyThread2(){
}
@Override
public void run() {
MyThread2 thread1 = new MyThread2();
Scanner scanner = new Scanner(System.in);
System.out.println(Thread.currentThread().getName()+"请输入想要购买的票数");
int i = scanner.nextInt();
thread1.gouPiao(i);
}
}
-----------------------------------------------------------------------------------------------------
package 线程安全.线程模拟购票;
/**
* @author weijisheng
* @create 2021-07-16 10:21
*/
public class Ck {
private int ticket;
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
public int getTicket1(int JiZhang){
synchronized (this){
int ticket1 = this.getTicket();
int after=ticket1-JiZhang;
this.setTicket(after);
return after;
}
}
}
关于线程补充知识
同步锁是谁?
对于非static方法,同步锁就是this。 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
格式:
修饰符 synchronized 返回值类型 方法名(形参列表){
}
锁对象:
非静态同步方法: 锁对象是this
静态同步方法: 锁对象是该方法所在类的字节码对象(类名.class)
- 多线程的安全性问题-可见性
讲解
-
概述: 一个线程没有看见另一个线程对共享变量的修改
-
例如下面的程序,先启动一个线程,在线程中将一个变量的值更改,而主线程却一直无法获得此变量的新值。
package 线程安全.线程的安全性问题可见性;
/**
* @author weijisheng
* @create 2021-08-11 20:27
*/
public class MyThread extends Thread {
//共享变量(主和子共享)
static boolean flag=false;
public void run(){
//暂停五秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改flag的值
flag=true;
System.out.println("子线程把flag的值修改为true");
}
}
package 线程安全.线程的安全性问题可见性;
/**
* @author weijisheng
* @create 2021-08-11 20:26
*/
public class Test {
/*多线程的的安全性问题,可见性
* 概述:一个线程没有看见另一个线程对共享变量的修改
* 例如:先启动一个线程,在线程中将一个变量的值更改,另一个变量一直无法获得*/
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
//主线程
while(true){
//System.out.println("模拟耗时操作");//如果代码太简单,时间太短的话,死循环就会出问题。
if(MyThread.flag==true){
System.out.println("结束死循环");
break;
}
}
/*期望结果:子线程修改共享变量flag的值为true后,主线程就会结束死循环
* 实际结果:子线程修改共享变量flag的值为true后,主线程没有结束死循环
* 原因:子线程对共享变量flag修改后的值,主线程不可见*/
}
}
-
原因:
-
Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。
-
简而言之: 就是所有共享变量都是存在主内存中的,线程在执行的时候,有单独的工作栈内存,会把共享变量拷贝一份到线程的单独工作内存中,并且对变量所有的操作,都是在单独的工作内存中完成的,不会直接读写主内存中的变量值
– 多线程的安全性问题-原子性
讲解
-
概述:所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,多个操作是一个不可以分割的整体。
-
请看以下示例:
package 线程安全.线程原子性问题;
/**
* @author weijisheng
* @create 2021-08-11 21:40
*/
public class MyThread extends Thread {
//共享变量
static int a;
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
a++;
}
System.out.println("子线程加载完毕");
}
}
package 线程安全.线程原子性问题;
/**
* @author weijisheng
* @create 2021-08-11 21:39
*/
public class Test {
public static void main(String[] args) {
new MyThread().start();
for (int i = 0; i < 10000; i++) {
MyThread.a++;
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程加载完毕"+MyThread.a);//a的值不固定有时候是对的有时候是不对的。
}
}
原因:两个线程对共享变量的操作产生覆盖的效果
volatile关键字概述
- volatile是一个"变量修饰符",它只能修饰"成员变量",它能强制线程每次从主内存获取值,并能保证此变量不会被编译器优化。
- volatile能解决变量的可见性、有序性;
- volatile不能解决变量的原子性
原子性问题如何解决呢?
1.共享变量原子性问题: - 原因:多条线程对共享变量的多次操作,产生了覆盖的操作。
- 原子类,同步锁,Lock锁
2.代码块原子性问题: - 原因:一段代码,被一条线程执行,可能会被其他线程打断,从而导致数据混乱。
- 解决办法:同步锁,Lock锁。
- AtomicInteger类的工作原理-CAS机制
-
在JDK的并发包里提供了几个非常有用的并发容器和并发工具类。供我们在多线程开发中进行使用。
- 知识点–CopyOnWriteArrayList
代码如下:
CountDownLatch允许一个或多个线程等待其他线程完成操作。
代码如下:
- CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门
代码如下:
Semaphore的主要作用是控制线程的并发数量。
synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore可以设置同时允许几个线程执行。
Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
代码如下:
- Semaphore的主要作用是控制线程的并发数量。
synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore可以设置同时允许几个线程执行。
Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
代码如下:
11.线程池
线程池概念
- **线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:
线程池的好处
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
-----注意事项:
- 线程池的原理: 创建线程池的时候初始化指定数量的线程,当有任务需要线程执行的时候,就在线程池中随机分配空闲线程来执行当前的任务;如果线程池中没有空闲的线程,那么该任务就进入任务队列中进行等待,等待其他线程空闲下来,再执行任务.(线程重复利用)
- Java里面线程池的顶级接口是
java.util.concurrent.Executor
,但是严格意义上讲Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工厂类来创建线程池对象。
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
-
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行任务 -
public <T> Future<T> submit(Callable<T> task)
:获取线程池中的某一个线程对象,并执行任务Future接口:用来记录线程任务执行完毕后产生的结果。
使用线程池中线程对象的步骤:
- 创建线程池对象。
- 创建Runnable接口子类对象。(task)
- 提交Runnable接口子类对象。(take task)
- 关闭线程池(一般不做)。
示例代码如下:
小结:
1.同步代码块和同步锁案例
2.volatile关键字的使用
3.原子类的使用
4.线程池的使用
5.理解高并发线程安全问题出现的原因\解决办法
并发包:---->理解做好笔记--(休息的时候复习)
- 能够解释安全问题的出现的原因
可见性问题:
原因:一条线程对共享变量的修改,对其他线程不可见
解决办法: volatile关键字修饰共享变量
有序性问题:
原因: 编译器可能会对代码进行重排,造成数据混乱
解决办法:volatile关键字修饰共享变量
原子性问题:
共享变量原子性问题:
原因:多条线程对共享变量的多次操作,产生了覆盖的效果
解决办法: 原子类,同步锁,Lock锁
代码块原子性问题:
原因:一段代码,被一条线程执行,可能会被其他线程打断,从而造成数据混乱
解决办法: 同步锁,Lock锁
- 能够使用同步代码块解决线程安全问题
同步代码块:
格式:
synchronized(锁对象){
}
锁对象:
1.语法上,可以是任意类的对象
2.如果多条线程想要实现同步,那么这多条线程的锁对象必须一致
- 能够使用同步方法解决线程安全问题
同步方法:
格式:
修饰符 synchronized 返回值类型 方法名(形参列表){}
锁对象;
非静态同步方法: 锁对象this
静态同步方法:该方法所在类的字节码对象(类名.class)
- 能够说出volatile关键字的作用
解决可见性,有序性问题,不能解决原子性问题
- 能够说明volatile关键字和synchronized关键字的区别
1.volatile只能修饰成员变量,synchronized可以修饰代码块或者方法
2.volatile是强制要求子线程每次使用共享变量都是重新从主内存中获取
synchronized实现的是互斥访问
3.volatile只能解决可见性,有序性问题,不能解决原子性问题,但synchronize都可以解决
- 能够理解原子类的工作机制
cas机制: 比较并交换
- 能够掌握原子类AtomicInteger的使用
构造方法\成员方法
- 能够描述ConcurrentHashMap类的作用
当成Map集合使用,只是线程安全
- 能够描述CountDownLatch类的作用
允许一个或多个线程等待其他线程完成操作
- 能够描述CyclicBarrier类的作用
。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
- 能够表述Semaphore类的作用
控制线程并发的数量
- 能够描述Exchanger类的作用
2条线程之间交换数据
- 能够描述Java中线程池运行原理
线程池的原理: 创建线程池的时候初始化指定数量的线程,当有任务需要线程执行的时候,就在线程池中随机分配空闲线程来执行当前的任务;如果线程池中没有空闲的线程,那么该任务就进入任务队列中进行等待,等待其他线程空闲下来,再执行任务.(线程重复利用)
wait和sleep区别[重要]
- 对于sleep()方法,首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。
- sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态。
- wait()是把控制权交出去,然后进入等待,此对象的等待锁定池处于等待状态,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
- 在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁。