目录
一、线程的创建
1.继承Thread:
//自定义类继承Thread类
class MyThread extends Thread{
//重写run方法 run方法中就是线程要执行的任务
@Override
public void run() {
System.out.println("新线程 == 开始执行......");
for (int i = 100; i <=200 ; i++) {
System.out.println("新线程中的变量 :: "+ i);
}
System.out.println("新线程执行完了!");
}
}
public class TheadDemo1 {
public static void main(String[] args) {
//创建自定义线程对象
MyThread mt = new MyThread();
mt.start();//开启新线程,并执行run方法
System.out.println("main方法开始执行了.....");
for (int i = 1; i < 100; i++) {
System.out.println("main方法中的变量i = "+i);
}
System.out.println("main方法执行结束!!!");
}
}
2.实现Runable接口
//自定义线程任务类
class Task implements Runnable{
@Override
public void run() {
//Thread.currentThread();//获取当前正在执行的线程对象
String name = Thread.currentThread().getName();//获取当前线程对象的名称
System.out.println(name+" == 开始执行......");
for (int i = 200; i <=300 ; i++) {
System.out.println(name+" 中的变量 :: "+ i);
}
System.out.println(name+" 执行完了!");
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
//在main方法中,创建一个线程对象
Thread t1 = new Thread(new Task(),"新线程1");
t1.start();
// try {
// t1.join();//等待t1线程执行结束 后续的代码在t1线程执行完后,再执行
// System.out.println(t1.getName()+"已死亡");
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//把t1线程的优先级设置为:高 1=10,数字越大越高,如要设置优先级,两优先级之间差距要较大,不然cpu无法识别
t1.setPriority(10);
//Thread.currentThread()获取当前对象
String name = Thread.currentThread().getName();
Thread.currentThread().setPriority(1);
//线程睡眠,sleep是静态方法,Thread.sleep书写在哪里,当前对象就进入睡眠
// try {
// Thread.sleep(10);//线程睡眠 10 毫秒
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("main方法开始执行了.....");
for (int i = 1; i < 100; i++) {
System.out.println(name+" 中的变量i = "+i);
}
System.out.println("main方法执行结束!!!");
}
}
3.方法
①Thread.currentThread();//获取当前正在执行的线程对象
②Thread.currentThread().getName();//获取当前线程对象的名称
③join();//等待当前线程执行结束 后续的代码再执行
④sleep();//线程睡眠,毫秒值
⑤ setPriority();//将目标线程设置优先级:高 1=10,数字越大越高,若设置优先级,两线程之间的优先级差距要大,不然cpu识别不出,且优先级低不代表后执行,只是执行概率变低。
二、线程安全
为避免多个线程同时对共享数据进行读改写操作,数据更新不同步、出现错乱,
要在某个线程操作数据时,禁止其他线程使用。使用同步方式解决。
1.使用synchronized关键字,两种写法:
①同步代码块(有显式锁)
②同步方法(没有显式的指定锁,但有默认存在的锁对象,非static方法锁就是this,静态是类名.class)
保证多个线程针对同一个对象操作。不然展示出的数据仍然乱序。
//线程任务:卖票
class Ticket implements Runnable {
int number = 500;
// Object lock = new Object();//对象锁(同步锁),可以是任意类型,用于第一种方法使用
// @Override
// public void run() {//1.同步代码块
// while (true) { //模拟一直售票状态
// synchronized (lock) {
// synchronized (this.getClass) {//也可以使用
// if (number > 0) {
// System.out.println(Thread.currentThread().getName()+"在卖:"+number+"号票");
// number--;
// }
// }
// }
// }
@Override
public synchronized void run() {//2,同步方法
while (true) { //模拟一直售票状态
if (number > 0) {
System.out.println(
Thread.currentThread().getName() + "在卖:" + number + "号票");
number--;
}
}
}
}
public class TicketTest {
public static void main(String[] args) {
Ticket tick = new Ticket();//创建卖票任务
Thread t1 = new Thread(tick, "窗口1:");
Thread t2 = new Thread(tick, "窗口2=");
Thread t3 = new Thread(tick, "窗口3~");
//执行多个窗口卖票
t1.start();
t2.start();
t3.start();
}
2.Lock锁(涵盖了synchronized的功能)
//线程任务:卖票
class Ticket implements Runnable {
int number = 500;
Lock l = new ReentrantLock(); //Lock锁 多态
@Override
public void run() {
while (true) {
l.lock();//lock需紧跟try/catch,并在finally第一行闭锁。
try {//操作共享数据的代码放在锁机制中,确保数据同步(不出现数据错乱)
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "在卖:" + number + "号票");
number--;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}
}
三、死锁
两个线程对两个同步锁对象具有循环依赖时,就会大概率的出现死锁
两个线程分别拿到一把锁,都等待下一把锁,进入死锁,关机重启。
//线程一中
synchronized (objA) {
System.out.println("嵌套1 objA");
synchronized (objB) {
System.out.println("嵌套1 objB");
}}
//线程二中
synchronized (objB) {
System.out.println("嵌套2 objB");
synchronized (objA) {
System.out.println("嵌套2 objA");
}}
四、唤醒
当某线程进入等待this.getClass().wait(),该线程释放锁,如无唤醒操作,则无限等待。
此时可调用this.getClass().notify();唤醒在此对象监视器(锁对象)上等待的单个线程(若存在多个线程在等待状态,则由jdk决定随机唤醒一个,notifyAll()则可以唤醒所有在此对象监视器(锁对象)上等待的线程)。
五、线程池
若并发的线程数量多,且每个线程都是执行一个短时效的任务就结束,这样频繁创建线程就会大大降低系统的效率(因为频繁创建和销毁线程需要时间)。 若大量线程在执行,会涉及到线程间上下文的切换,会极大的消耗CPU运算资源。
线程池的优点:降低资源消耗。 提高响应速度。 提高线程的可管理性。
创建线程池可使用:
①Executors类(会被规范报红)
②ThreadPoolExecutor(手动创建,参数较多)
ThreadPoolExecutor包含了7个核心参数,参数含义:
- corePoolSize:核心线程池的大小
- maximumPoolSize:最大线程池的大小
- keepAliveTime:当线程池中线程数大于corePoolSize,并且没有可执行任务时大于corePoolSize那部分线程的存活时间
- unit:keepAliveTime的时间单位
- workQueue:用来暂时保存任务的工作队列
- threadFactory:线程工厂提供线程的创建方式,默认使用Executors.defaultThreadFactory()
- handler:当线程池所处理的任务数超过其承载容量或关闭后继续有任务提交时,所调用的拒绝策略
核心参数
ThreadPoolExecutor中包含了七大核心参数,如果需要对线程池进行定制化操作,需要对其中比较核心的参数进行一定程度的认识。
corePoolSize
ThreadPoolExecutor会根据corePoolSize和maximumPoolSize在构造方法中设置的边界值自动调整池大小,也可以使用setCorePoolSize和setMaximumPoolSize动态更改,关于线程数量的自动调整分为以下两种场景:
线程数量小于corePoolSize
当在线程池中提交了一个新任务,并且运行的线程少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求。
线程数量介于corePoolSize和maximumPoolSize之间
若运行的线程数多于corePoolSize但少于maximumPoolSize,则仅当队列已满时才会创建新线程。
如果corePoolSize和maximumPoolSize相同,那么可以创建一个固定大小的线程池。
keepAliveTime
keepAliveTime参数用来来设置空闲时间。如果池当前有多个corePoolSize线程,多余的线程如果空闲时间超过将会被终止。
workQueue
workQueue参数用来指定存放提交任务的队列,任何BlockingQueue都可以用来传输和保存提交的任务。关于队列大小与线程数量之间存在这样的关系:
如果线程数少于corePoolSize,对于提交的新任务会创建一个新的线程处理,并不会把任务放入队列
如果线程数介于corePoolSize和maximumPoolSize之间,新提交的任务会被放入阻塞队列中
如果线程池处于饱和状态,即无法创建线程也无法存放在阻塞队列,那么新任务将交由拒绝策略来处理
threadFactory
该参数提供了线程池中线程的创建方式,这里使用了工厂模式ThreadFactory创建新线程,默认情况下,会使用 Executors.defaultThreadFactory,它创建的线程都在同一个ThreadGroup中,并具有相同的NORM_PRIORITY优先级和非守护进程状态。
handler
如果线程池处于饱和状态,没有足够的线程数或者队列空间来处理提交的任务,或者是线程池已经处于关闭状态但还在处理进行中的任务,那么继续提交的任务就会根据线程池的拒绝策略处理。
此处ThreadPoolExecutor参考CSDN博主「星光Starsray」的原创文章,十分详细,原文链接:https://blog.csdn.net/qq_38721537/article/details/124565092
可直接使用Thread或Runnable创建线程,但是Callable要和线程池同时使用,且有泛型,即有返回值。
Callable支持返回结果且可以抛出异常,但由于有使用限制,因此多数使用于需要返回值时。
处理Runnable任务:
class ComTask implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "在开发~~~");
}
}
public class Test01 {
public static void main(String[] args) {
//创建线程池(公司) 有3个电脑(3个Thread)//开发手册爆红
// ExecutorService es = Executors.newFixedThreadPool(3);
ThreadPoolExecutor te = new ThreadPoolExecutor(3,3,1L, TimeUnit.MINUTES, new LinkedBlockingQueue<Runnable>());
//模拟:3个电脑 教 5个开发
te.submit(new ComTask());
te.submit(new ComTask());
te.submit(new ComTask());
te.submit(new ComTask());
te.submit(new ComTask());
}
}
处理callable任务:
//2、定义Callable任务
//线程任务:Callable
class Task implements Callable<Integer> {
private int n;
public Task(int n) {
this.n = n;
}
@Override
public Integer call() throws Exception {
int sum = 0;
//循环
System.out.println(Thread.currentThread().getName());
for (int i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
}
public class Test {
public static void main(String[] args) {
//1、创建线程池
ExecutorService es = Executors.newFixedThreadPool(3);
//2、定义Callable任务,计算10以内和
Task task = new Task(10);
//3、把Callable任务,提交给线程池
Future<Integer> future = es.submit(task);
es.submit(new Task(11));
es.submit(new Task(12));
es.submit(new Task(13));
es.submit(new Task(14));
es.submit(new Task(15));
//4、从Future对象中获取call方法的返回值
Integer sum = null;
try {
sum = future.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("执行结果:"+sum);
}
}