线程
1.进程与线程
进程是拥有资源的基本单位,线程是调度和分配的基本单位。
线程一般不拥有资源,但线程可以访问隶属进程的资源,
即一个进程所拥有的资源,可供该进程下的所有线程共享
进程的切换开销远大于线程的切换开销
2.多线程
多线程:是指这个程序(一个进程)运行时产生的多个线程
3.并行与并发
并行:多个CPU实例或者多台机器同时执行一段处理逻辑,是真正的同时
并发:通过CPU调度算法,让用户看上去同时执行,实际上从CPU操作层面并不是真正的同时。
并发往往在场景中有公用的资源
线程的状态
可参考链接:https://blog.csdn.net/xingjing1226/article/details/81977129
- .新建(new):新创建了一个线程对象
- 可运行(RUNNABLE):线程对象创建后,其他线程(main线程)调用该对象的start()方法。该状态的线程位于可运行线程池中等待被线程调度选中,获取CPU的使用权
- 运行(RUNNING):可运行状态的线程获得了CPU的时间片,执行程序代码
- 阻塞(BLOCKED):阻塞状态是因为线程因为某种原因放弃了CPU 的使用权,即让出了时间片,暂时停止运行,直到线程进入可运行状态,才有机会再次获得时间片进入运行状态
- 死亡(DEAD):线程run()、main()方法执行结束,或者因为异常退出了run方法,则该线程结束生命周期。死亡的线程不可复生在一个死去的线程调用start()方法,会抛出java.lang.IllegalThreadStateException异常
阻塞的情况分为三种
- 等待阻塞:运行的线程执行o.wait()方法,jvm会把该线程放入等待队列中
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中
- 其他阻塞:运行的线程执行Thread.sleep(long
ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行状态
线程的五种状态图
一个线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程
线程的创建
java使用Thread类来代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示:
- 继承Thread类创建线程
- 实现Runnable接口创建线程
- 使用Callable和Future创建线程
- 使用线程池例如用Executor框架
线程创建的具体实现
------------------------继承Thread类创建线程---------------------
通过继承Thread类来创建并启动多线程的一般步骤如下
1】定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2】创建Thread子类的实例,也就是创建了线程对象
3】启动线程,即调用线程的start()方法
代码实现:
package com.example.demo.thread;
public class ThreadTest extends Thread
{
@Override
public void run()
{
System.out.println("我在创建线程");
}
public static void main(String[] args){
//创建并启动线程
new ThreadTest().start();
}
}
----------------------通过实现Runnable接口创建并启动线程----------------------------
1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体
2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3】第三部依然是通过调用线程对象的start()方法来启动线程
代码实现
package com.example.demo.thread;
public class RunableTest implements Runnable
{
@Override
public void run()
{
System.out.println("我在用runable创建线程");
}
public static void main(String[] args)
{
//创建一个线程任务
RunableTest task = new RunableTest();
//将任务交给Thread对象
Thread thread = new Thread(task);
thread.start();
}
}
自定义线程池
自定义线程池,可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池,用该类很容易实现自定义的线程池,这里先贴上示例程序:
package com.example.demo.thread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPool{
public static void main(String[] args){
//创建等待队列
BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);
//创建线程池,池中保存的线程数为3,允许的最大线程数为5
ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue);
//创建七个任务
Runnable t1 = new MyThread();
Runnable t2 = new MyThread();
Runnable t3 = new MyThread();
Runnable t4 = new MyThread();
Runnable t5 = new MyThread();
Runnable t6 = new MyThread();
Runnable t7 = new MyThread();
//每个任务会在一个线程上执行
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
pool.execute(t6);
pool.execute(t7);
//关闭线程池
pool.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行。。。");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程池的参数详解
1. corePollSize:核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
2.maximumPoolSize:最大线程数。表明线程中最多能够创建的线程数量。
3.keepAliveTime:空闲的线程保留的时间。
4.TimeUnit:空闲线程的保留时间单位。
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒
5.BlockingQueue<Runnable>:阻塞队列,存储等待执行的任务。参数有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue可选。
6.ThreadFactory:线程工厂,用来创建线程
7.RejectedExecutionHandler:队列已满,而且任务量大于最大线程的异常处理策略。
有以下取值
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程的状态管理
1、线程睡眠—sleep:
调用sleep线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。
线程睡眠的原因:线程执行的太快,或需要强制执行到下一个线程。
线程睡眠的方法(两个):sleep(long millis)在指定的毫秒数内让正在执行的线程休眠。
sleep(long millis,int nanos)在指定的毫秒数加指定的纳秒数内让正在执行的线程休眠。
public class SynTest {
public static void main(String[] args) {
new Thread(new CountDown(),"倒计时").start();
}
}
class CountDown implements Runnable{
int time = 10;
public void run() {
while (true) {
if(time>=0){
System.out.println(Thread.currentThread().getName() + ":" + time--);
try {
//睡眠时间为1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
每隔1秒会打印一次
2、线程让步—yield:
该方法和sleep方法类似,也是Thread类提供的一个静态方法,可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。相当于只是将当前线程暂停一下,然后重新进入就绪的线程池中,让线程调度器重新调度一次。也会出现某个线程调用yield方法后暂停,但之后调度器又将其调度出来重新进入到运行状态。
package com.example.demo.thread;
public class SynTest
{
public static void main(String[] args)
{
yieldDemo ms = new yieldDemo();
Thread t1 = new Thread(ms, "张三吃完还剩");
Thread t2 = new Thread(ms, "李四吃完还剩");
Thread t3 = new Thread(ms, "王五吃完还剩");
t1.start();
t2.start();
t3.start();
}
}
class yieldDemo implements Runnable
{
int count = 20;
@Override
public void run()
{
while (true)
{
if (count > 0)
{
System.out.println(Thread.currentThread().getName() + count-- + "个瓜");
if (count % 2 == 0)
{
// 线程让步
Thread.yield();
}
}
}
}
}
sleep和yield的区别:
①sleep方法声明抛出InterruptedException,调用该方法需要捕获该异常。yield没有声明异常,也无需捕获。
②、sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后 ,是直接进入就绪状态。
3.线程合并—join:
当B线程执行到了A线程的.join()方法时,B线程就会等待,等A线程都执行完毕,B线程才会执行。
join可以用来临时加入线程执行。
package com.example.demo.thread;
public class SynTest
{
public static void main(String[] args) throws InterruptedException {
yieldDemo ms = new yieldDemo();
Thread t1 = new Thread(ms, "张三吃完还剩");
Thread t2 = new Thread(ms, "李四吃完还剩");
Thread t3 = new Thread(ms, "王五吃完还剩");
t1.start();
//线程合并
t1.join();
t2.start();
t3.start();
System.out.println( "主线程");
}
}
class yieldDemo implements Runnable
{
int count = 20;
@Override
public void run()
{
while (true)
{
if (count > 0)
{
System.out.println(Thread.currentThread().getName() + count-- + "个瓜");
if (count % 2 == 0)
{
// 线程让步
Thread.yield();
}
}
}
}
}
设置优先级
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
MAX_PRIORITY =10
MIN_PRIORITY =1
NORM_PRIORITY =5
ThreadDemo td = new ThreadDemo();
Thread t1 = new Thread(td,“张三”);
t1.priority(9); //设置优先级
t1.start(); //设置完毕