Java多线程
1. 基本概念
程序:是为完成特定任务,用某种语言编写的一组指令集合。即指一段静态代码,静态对象。
进程:是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生,存在和消亡的过程。–生命周期,进程作为资源分配的单位。
线程:进程可以进一步细化为线程,是一个程序内部的一条执行路径,若一个进程同一时间并行执行多个线程,就是支持多线程的。
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小。
- 一个进程的多个线程共享相同的内存单元/内存地址空间,他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更加简便,高效。但多个线程操作共享系统资源可能就会带来安全隐患。
2.多线程创建
创建多线程的四种方式:
-
方式一:继承于Thread类
-
创建一个继承Thread类的子类
-
重写Thread类的run(),将此线程执行的操作声明在run()方法中
-
创建Thread类的子类的对象
-
通过子类对象调用start()
class B extends Thread{ //创建继承Thread类的子类 @Override public void run() { //重写run方法 b(); } public void b(){ for (int i=0;i<100;i++){ if(i%2==0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } B b = new B(); //创建子类对象 b.start(); //对象调用start() 开启线程,执行run()
注意:
-
一个子类对象只能开启一次线程调用一次start()方法
2. start() 作用是开启线程,和执行run()方法,用对象调用run(),不能开启线程
-
-
-
方式二:实现Runnable接口
-
创建一个实现了Runnable接口的类
-
实现类去实现Runnable中的抽象方法:run()
-
创建实现类对象
-
将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
-
通过Thread类的对象调用start();
public class Demo04 { public static void main(String[] args) { C c = new C(); Thread thread = new Thread(c); thread.start(); } } class C implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ if(i%2==0){ System.out.println(Thread.currentThread().getName()+":" + i); } } } }
-
-
方式三:实现Callable接口
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
- call()可以拥有返回值
- call()可以抛出异常,被外面的操作捕获,获取异常信息。
- call()支持泛型
创建步骤:
- 创建一个Callable的实现类
- 实现call方法,将此线程需要执行的操作声明在call()中
- 创建Callable接口实现类的对象
- 将次Callanle接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start();
- FutureTask对象调用get()获取Callable中call方法的返回值
public class Demo03 { public static void main(String[] args) { A a = new A(); FutureTask futureTask = new FutureTask(a); Thread thread = new Thread(futureTask); thread.setName("1"); thread.start(); try { Object o = futureTask.get(); //此返回值为重写Callable方法的返回值 System.out.println("总和为: "+o); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class A implements Callable{ private int sum=0; @Override public Object call() throws Exception { for (int i =1;i<=100;i++) { if (i % 2 == 0) { sum += i; System.out.println(Thread.currentThread().getName() + ":" + i); } } return sum; } }
-
方式四:使用线程池
- 背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
- 思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用类似生活中公共交通工具
- 好处:提高响应速度(减少了创建新线程的时间),降低资源消耗(不需要每次创建线程),便于线程管理(carePoolSize核心池大小,maximumPoolSize最大线程数,keepAliveTime;线程没有任务时最多保持多长时间)
创建步骤:
-
提供指定线程数量的线程池
-
设置线程属性
-
执行指定的线程操作,需要提供实现Runnable接口或者Callable接口类型的对象
-
关闭连接池
public class Demo05 { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //向下转型为类,接口不能调用非自身方法 // service1.setCorePoolSize(15); service.execute(new C()); //适合Runnable service.submit(new B()); //适合Callable service.shutdown(); } } class B implements Callable{ private int sum=0; @Override public Object call() throws Exception { for(int i=0;i<100;i++){ if(i%2==0){ sum+=i; System.out.println(Thread.currentThread().getName() + ":" + i); } } return sum; } } class C implements Runnable{ @Override public void run() { for(int i=0;i<100;i++){ if(i%2!=0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } }
-
比较方式一和方式二:
- 开发中优先选择实现Runnable接口的方式
- 原因:实现的方式没有类的单继承性的局限性
- 原因:实现的方式更适合来处理多个线程有共享数据的情况
- 开发中优先选择实现Runnable接口的方式
-
联系:都实现了Runnable接口,两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
-
用两种方式实现模拟卖票
//方式一 public class Demo05 { public static void main(String[] args) { D d1 = new D(); D d2 = new D(); D d3 = new D(); d1.setName("一号窗口"); d2.setName("二号窗口"); d3.setName("三号窗口"); d1.start(); d2.start(); d3.start(); } } class D extends Thread{ private static int ticket = 100; @Override public void run() { while (true){ if(ticket<=0){ break; } System.out.println(getName() + "卖票,票号为:" + ticket); ticket--; } } } //方式二 public class Demo06 { public static void main(String[] args) { E e = new E(); Thread thread1 = new Thread(e); Thread thread2 = new Thread(e); Thread thread3 = new Thread(e); thread1.setName("一号窗口"); thread2.setName("二号窗口"); thread3.setName("三号窗口"); thread1.start(); thread2.start(); thread3.start(); } } class E implements Runnable{ private int ticket = 100; @Override public void run() { while (true){ if(ticket<=0){ break; } System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; } } }
3. Thread 常用方法
Thread常用方法:
- start():启动当前线程,调用当前线程的run();
- run(): 将创建的线程要执行的操作声明在此方法中,一般都需要重写此方法
- currentThread(): 静态方法,返回执行当前代码的线程
- getName(): 获取当前线程的名字
- setName(): 设置当前线程的名字
- yield(): 释放当前cpu的执行权
- join():在线程a中调用b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完之后,线程a才结束阻塞状态
- stop():已过时,当执行此方法时,强制结束当前线程
- sleep(long millitime):让当前线程睡眠单位毫秒,在时间内线程时阻塞状态
- isAlive():判断当前线程是否存活
4. 线程的调度
调度策略:
- 时间片
- 抢占式:高级优先级的线程抢占cpu、
Java调度方法:
- 同优先级线程组成先进先出队列,使用时间篇策略
- 对高优先级,使用优先调度的抢占式策略
线程的优先级:
-
NORM_PRIORITY = 5 (默认优先级) MIN_PRIORITY = 1 MAX_PRIORITY = 10
-
获取设置优先级
setPriority(int p);
getPriority(); -
说明:高优先级的线程要抢占低优先级的线程cpu执行权,只是从高概率上讲,高优先级的线程高概率情况下被执行,并不意味只有当高优先级执行完之后,此优先级的线程才执行
5.线程的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IGGjvqIi-1610897462431)(C:\Users\www\Desktop\Snipaste_2021-01-17_17-28-35.png)]
6.线程的同步
在Java中,我们通过同步机制,来解决线程安全问题
线程安全问题: 一个线程操作共享数据过程中,尚未操作完成,其他线程参与进来,也操作共享数据
如何解决:一个线程操作共享数据时,在没有完成之前,其他线程不能参与进来,操作此共享数据,即使先进来的线程阻塞了
也不能改变操作顺序。
同步机制实现的两种方式:
-
同步代码块:
synchronized(同步监视器){ //需要被同步的代码 }
说明:
-
操作共享数据的代码,即为需要被同步的代码。
2. 共享数据:多个线程共同操作的变量。 3. 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁(要求多个线程要共用一把锁)
补充: 在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
继承Thread类创建多线程方式中,我们可以考虑使用类.class充当同步监视器
//继承Thread类方式创建多线程加锁 public class Demo02 { public static void main(String[] args) { D d1 = new D(); d1.setName("一号窗口"); D d2 = new D(); d2.setName("二号窗口"); D d3 = new D(); d3.setName("三号窗口"); d1.start(); d2.start(); d3.start(); } } class D extends Thread{ private static int ticket = 100; private static Object object = new Object(); @Override public void run() { while (true){ synchronized(object) { if (ticket <= 0) { break; } System.out.println(getName() + "卖票,票号为:" + ticket); ticket--; } } } } //实现Runnable接口创建多线程方式加锁 public class Demo01 { public static void main(String[] args) { E e = new E(); Thread thread1 = new Thread(e); Thread thread2 = new Thread(e); Thread thread3 = new Thread(e); thread1.setName("一号窗口"); thread2.setName("二号窗口"); thread3.setName("三号窗口"); thread1.start(); thread2.start(); thread3.start(); } } class E implements Runnable{ private int ticket = 100; private Object object = new Object(); @Override public void run() { while (true){ synchronized(this) { if (ticket <= 0) { break; } else { System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; } } } } }
-
-
同步方法
如果操作共享数据的代码完整的声明一个方法中,我们不妨将此方法声明同步的
//继承Thread类方式创建多线程加锁 public class Demo02 { public static void main(String[] args) { D d1 = new D(); d1.setName("一号窗口"); D d2 = new D(); d2.setName("二号窗口"); D d3 = new D(); d3.setName("三号窗口"); d1.start(); d2.start(); d3.start(); } } class D extends Thread{ private static int ticket = 100; private static Object object = new Object(); @Override public void run() { while (true) { show(); if(ticket<=0){ break; } } } private static synchronized void show() { //此方法必须是静态方法,锁是 当前类.class if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; } } } //实现Runnable接口创建多线程方式加锁 public class Demo01 { public static void main(String[] args) { E e = new E(); Thread thread1 = new Thread(e); Thread thread2 = new Thread(e); Thread thread3 = new Thread(e); thread1.setName("一号窗口"); thread2.setName("二号窗口"); thread3.setName("三号窗口"); thread1.start(); thread2.start(); thread3.start(); } } class E implements Runnable{ private int ticket = 100; private Object object = new Object(); @Override public void run() { while (true){ show(); if(ticket<=0){ break; } } } private synchronized void show(){ //默认锁为 this if(ticket>0) { System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; } } }
注意:同步的方式,解决了线程的安全问题–好处
操做同步代码时,只有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低 --缺点
死锁–缺点