多线程
概念
- 多进程:window系统就是一个多进程的系统,同时运行多个应用程序。CPU交替执行这些进程,进程执行的间隔比较短(几毫秒)。CPU要执行哪些进程、执行多长时间,是由CPU决定的
- 多线程:线程是一段顺序代码或是一个任务(代码),进程中包含多个线程。也是由CPU交替执行这些线程。完成多线程的并发执行。
多线程的实现
Thread方式实现
Thread类的对象就是一个多线程,JVM允许多个Thread对象中的代码并发的运行。
定义一个子类,继承Thread类,重写run方法,在run方法中编写线程执行的代码
实现一个火车买票的例子:
public class MyThread extends Thread { public MyThread(String name){ super(name); } @Override public void run() { //卖火车票 1~1000 for (int i = 1; i <= 1000; i++) { System.out.println("窗口"+Thread.currentThread().getName()+"卖了"+i+"张票"); } } } public class Main { public static void main(String[] args) { MyThread t1 = new MyThread("A"); MyThread t2 = new MyThread("B"); MyThread t3 = new MyThread("C"); //不要调用run方法,否则还是顺序执行 //start方法两件事:1.启动多线程;2.在线程中执行run t1.start(); t2.start(); t3.start(); } }Runnable接口方式
单独定义线程要执行的任务,定义一个类实现Runnable接口,创建Thread类的对象出入Runnable对象
public class MyRunnable implements Runnable{ String name; public MyRunnable(String name){ this.name = name; } @Override public void run() { //卖火车票 1~1000 for (int i = 1; i <= 1000; i++) { System.out.println(name+"窗口"+Thread.currentThread().getName()+"卖了"+i+"张票"); } } }public static void main(String[] args) { MyRunnable r = new MyRunnable("哈西站"); Thread t1 = new Thread(r,"A"); Thread t2 = new Thread(r,"B"); MyRunnable r2 = new MyRunnable("哈尔滨站"); Thread t3 = new Thread(r2,"C"); //t1.start() 两件事: 1.启动多线程 ; 2. 调用r.run() t1.start(); t2.start(); t3.start();}Runnable方式的好处:
- MyRunnable类还可以继承其他类
- 比较灵活:例如实现分组统计票数
线程的生命周期
- new (新建状态)
- runnable(可执行状态) :start方法进入状态
- running(执行状态):CPU调用线程,yield方法进入到runnable状态 ; 进入阻塞状态
- blocking(阻塞状态) : 只能有running进入blocking ;只能blocking进入runnable
- dead(死亡状态):run方法执行结束或出现异常

Thread类的方法
- Thread.yield方法:当前线程进入到runnable状态,让CPU结束当前线程的调度。但是我们不能保证CPU再次调度这个线程。
- 阻塞线程方法:
Thread.sleep方法: 当前线程休眠指定的毫秒数,进入blocking状态,当达到设定的时间,进入runnable
应用:启动一个服务器备份任务的多线程,在每天凌晨2点执行一段代码(可能用时10分钟),用sleep完成
public class BackRunnable implements Runnable{ @Override public void run() { while(true){ Calendar calendar = Calendar.getInstance(); if(calendar.get(Calendar.HOUR_OF_DAY)==2){ System.out.println("执行备份程序(10分钟)"); } try { Thread.sleep(60*60*1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }join方法: 当A线程在B线程中执行了join方法 ,B线程要等待A线程执行完毕后,再执行join后面的代码。如果A线程没有执行完毕 ,B线程进入阻塞
public class Main { public static void main(String[] args) { MyRunnable r = new MyRunnable(); Thread t1 = new Thread(r,"A"); Thread t2 = new Thread(r,"B"); Thread t3 = new Thread(r,"C"); //t1.start() 两件事: 1.启动多线程 ; 2. 调用r.run() t1.start(); t2.start(); t3.start(); //判断t1线程是否执行完毕,如果没有执行完毕 当前线程要阻塞 try { t1.join(); t2.join(); t3.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("卖完3000张票了,火车发车了-----------------"); } }- 守护线程 : 守护线程一般是为其他线程提供服务的一个线程,当其守护的线程结束后,自己自动结束。我们可以创建一个多线程,setDaemon()方法变为守护线程,是其他线程的守护线程
Thread t1 = new Thread(new PrinceRunnable());Thread t2 = new Thread(new PrincessRunnable());t2.setDaemon(true);t1.setPriority(Thread.MAX_PRIORITY);t1.start();t2.start();- 设置线程优先级方法:setPriority()方法: 低、普通、高 ,线程默认的优先级是普通
线程锁
多个线程在使用同一个资源(对象、数据)时,只能保证让一个线程使用,否则将会出现数据问题。
当一个线程使用这个对象时,为对象加锁,另一个线程就不能使用这个对象,将等待,直到锁被释放,等待的线程才能使用这个对象
synchronized关键字
- synchronized修饰方法: 当一个线程执行了这个方法,线程要获得这个对象的互斥锁(对象锁,每个对象都 有对象锁),如果获得了就能执行这个方法,如果获得不了(锁已被其他线程获得),则等待(runnable)。方法执行完毕后,锁被释放
public class Account { int money = 20000; public synchronized void add(){ money+=1000; System.out.println("存1000余额是:"+money); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void sub(){ money-=1000; System.out.println("取1000余额是:"+money); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { Account account = new Account(); AddRunnable add = new AddRunnable(account); SubRunnable sub = new SubRunnable(account); new Thread(add).start(); new Thread(sub).start(); } }- synchronized语句块
public void add(){ //线程要获得哪个对象的互斥锁 synchronized (this) { money += 1000; System.out.println("存1000余额是:" + money); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } public void sub(){ synchronized (this) { money -= 1000; System.out.println("取1000余额是:" + money); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }}synchronized属于重量级锁,在获得锁、释放锁时效率低,
JVM将synchronized锁设置了四个级别:无锁>偏向锁>轻量锁>重量锁
CAS算法
死锁问题
A线程等待B线程释放锁,B线程要等待A线程释放锁 ,程序并没有结束,但是程序什么也没有执行
public class TestLock implements Runnable{ boolean flag ; static Object obj1 = new Object(); static Object obj2 = new Object(); public TestLock(boolean flag) { this.flag = flag; } @Override public void run() { while(true) { if (flag) { synchronized (obj1) { synchronized (obj2) { System.out.println("obj1---obj2"); } } } else { synchronized (obj2) { synchronized (obj1) { System.out.println("obj2---obj1"); } } } } } public static void main(String[] args) { TestLock lock1 = new TestLock(true); TestLock lock2 = new TestLock(false); new Thread(lock1).start(); new Thread(lock2).start(); } }如何避免死锁:
- 每个线程 加锁顺序尽量一致,最简单的但是业务需求可能是不允许的
- 服务生策略 , 创建一个守护线程,判断线程的状态是否是死锁状态,中断等策略,实现较容易
- 设置加锁时间,实现较难
线程八锁: 对象锁的八种情况
- 线程占用了对象的互斥锁时,其他线程依然可以访问这个对象没有synchronized修饰的方法
- 当线程执行到sleep时,不释放互斥锁
- 当线程访问对象synchronized非静态方法时,获得的是对象锁;当线程访问类的synchronized静态方法时,获得的是类锁
线程间的通信(wait和notify)
- wait方法 :无参方法, 是Object类的方法,意味着所有对象都有wait方法。当调用一个对象的wait方法,当前获得这个对象互斥锁的线程将等待;如果调用对象的wait方法时,没有线程获得这个对象的互斥锁,将抛出异常。
wait方法:带毫秒值参数的方法
- notify方法:当调用了一个对象的notify方法时,在这个对象上等待的线程中随机唤醒一个线程。
notifyAll方法:唤醒当前对象所有等待的线程。
wait和sleep的区别
wait可以被主动唤醒(notify方法),会释放互斥锁,属于Object类的方法
sleep只能被动唤醒(达到指定时间),不释放互斥锁,属于Thread类的方法
使用wait和notify实现生产者和消费者的模型
有一个一定数量的资源,有很多生产者生产资源,很多消费者消费资源,实现一个模型让资源保持一定的数量。
public class Resource { int count = 20; public synchronized void add(){ notifyAll(); while(count>20){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count++; System.out.println("生产了一个,剩余:"+count); /* try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }*/ } public synchronized void sub(){ notifyAll(); while(count<20){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count--; System.out.println("消费了一个,剩余:"+count);/* try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }*/ } public static void main(String[] args) { Resource resource = new Resource(); AddRunnable addRunnable = new AddRunnable(resource); SubRunnable subRunnable = new SubRunnable(resource); Thread p1 = new Thread(addRunnable); Thread p2 = new Thread(addRunnable); Thread p3 = new Thread(addRunnable); Thread s1 = new Thread(subRunnable); Thread s2 = new Thread(subRunnable); Thread s3 = new Thread(subRunnable); p1.start(); p2.start(); p3.start(); s1.start(); s2.start(); s3.start(); }}线程池
多线程需求:
- 开发一个游戏,只用到3个线程,创建多线程完成需求,不需要使用线程池
- 有一个Web服务器(网站的服务器),例如公司的网站,每个用户访问网站发送一次请求响应就结束,服务器对请求响应是并发操作的处理,需要创建大量的多线程。new Thread方法就会耗费很多资源,服务器会变慢。 使用线程池管理多线程对象
- 有一个网站系统Web服务器(淘宝、支付宝),访问量非常巨大,会使用微服务架构。
线程池是用来管理多线程(创建、状态、销毁等)的一种缓冲池技术。
线程池的核心类ThreadPoolExecutor
ThreadPoolExecutor构造器的核心参数:
-
核心线程数:如果核心线程数量的线程都忙且队列满了,再来任务时,就会创建新的线程;如果线程数大于核心线程数,且空闲线程的空闲时间大于了设定的存活时间,要销毁空闲的线程,直到线程数量达到了核心线程数。
-
最大线程数:线程池能创建的最多线程数量。
-
存活时间:空闲的时间
-
时间单位:存活时间的单位
-
队列:当达到了最大线程数,来任务时,将在队列中等待,有空闲线程则会消费队列中的任务。
-
拒绝策略:当线程都忙,队列也满了,采取设置的拒绝策略,包括:
拒绝并抛出异常
在线程池执行的线程中,执行这个任务。
队列中最旧的任务放弃
直接放弃,什么也不做
示例:
public class TestRunnable implements Runnable { int i ; public TestRunnable(int i){ this.i = i; } @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"执行了"+i+"号任务"); } }public class Teset { public static void main(String[] args) { //组建10个任务 List<Runnable> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(new TestRunnable(i)); } ThreadPoolExecutor pool = new ThreadPoolExecutor(2,5, 5, TimeUnit.SECONDS, new LinkedBlockingDeque(2)); for (Runnable runnable:list) { pool.execute(runnable); } }}线程池的工作原理:
刚创建线程池时,没有线程对象,当执行任务时,开始创建线程对象,当线程数等于核心线程数且都忙,来新任务时将其存入队列中;当核心线程数的线程都忙且队列也满了,开始创建新的线程执行新来的任务,线程数不能超过最大线程数。如果忙线程数达到最大且队列也满了,再来任务,采取拒绝策略。当空闲线程数大于核心线程数且超出了设定的存活时间,就线程进行销毁。最终的空闲线程数量等于核心线程数量。
Java中常用的线程池
这些常用的线程池大部分封装了ThreadPoolExecutor ,在某些场景下我们使用指定的线程池会更方便。
- FixedThreadPool线程池:指定固定数量的线程数,不会销毁线程池中的线程,使用场景是:线程数量固定时效率最高的
//组建10个任务 List<Runnable> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(new TestRunnable(i)); } ExecutorService executorService = Executors.newFixedThreadPool(5); for (Runnable runnable:list){ executorService.execute(runnable); }- CachedThreadPool线程数:最大线程数就是int的最大值,空闲线程存活1分钟,使用场景:适合任务量多执 行时间短的场景
//组建10个任务 List<Runnable> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(new TestRunnable(i)); } ExecutorService executorService = Executors.newCachedThreadPool(); for (Runnable runnable:list){ executorService.execute(runnable); } executorService.shutdown();- ScheduledThreadPool线程池:设置核心线程数,使用场景:执行大量定时任务线程
public static void main(String[] args) { //组建10个任务 List<Runnable> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { list.add(new TestRunnable(i)); } ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); for (int i = 0; i < list.size(); i++) { executorService.schedule(list.get(i),i,TimeUnit.SECONDS); } }- SingleThreadPool线程池 : 只有一个线程执行任务,使用场景:多个任务按顺序执行或多个任务同步需求
public static void main(String[] args) { //组建10个任务 List<Runnable> list = new ArrayList<>(); for (int i = 0; i < 5; i++) { list.add(new TestRunnable(i)); } ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < list.size(); i++) { executorService.execute(list.get(i)); } }- WorkStealingThreadPool线程池:先执行完的线程会分担其他线程的任务执行,使用场景:任务数量较固定 且任务执行时间较长。WorkStealingThreadPool中的线程是守护线程
public static void main(String[] args) { //组建10个任务 List<Runnable> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(new TestRunnable(i)); } ExecutorService executorService = Executors.newWorkStealingPool(5); for (int i = 0; i < list.size(); i++) { executorService.submit(list.get(i)); } ExecutorService executor = Executors.newWorkStealingPool(2); //由于上面的是守护线程,让主线程休眠100000毫秒 try { Thread.sleep(100000); } catch (InterruptedException e) { e.printStackTrace(); }}Java SE
Related articles
部分信息可能已经过时