基本概念
程序(program): 是为完成特定任务,用某种语言编写的一组指令的集合。即值一段静态代码,静态对象。
进程(progress):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它的产生、存在和消亡的过程。–生命周期
进程作为资源分配的单位,系统在运行时为每个进程分配不同的内存区域
线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径。
若一个京城同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程有独立的运行栈和程序计数器,线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间->他们从同一堆中访问对象,可以访问相同的变量和对象。这就使得线程之间的通信你好忙的很简便高效。但是多个线程操作共享的系统资源就会带来一定的安全隐患。
并行和并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀
同步和异步
需要等待结果返回才能继续运行就是同步
不需要等待结果返回就能继续运行就是异步
多线程的优点:
提高应用程的响应。对图形化界面更有意义,可增强用户体验
提高计算机系统的CPU利用率
改善程序结构,将长而且复杂的进程分为多个线程,独立运行,利与理解和修改。
合适需要多线程时机
程序需要同时执行两个或者多个任务
程序需要实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等
需要一些后台运行的程序时
多线程的创建与应用 方式一:继承与Thread类的子类
创建一个继承与Thread类的子类
重写Thread类的run()方法
创建Thread子类的对象
通过该对象调用start()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class MyThred extends Thread { @Override public void run () { for (int i = 0 ; i < 100 ; i+=2 ) { System.out.println(i); } } } public class ThreadTest { public static void main (String[] args) { MyThred myThred = new MyThred (); myThred.start(); } }
获取当前线程的名称:Thread.currentThread().getName()
创建Thread匿名子类
1 2 3 4 5 6 7 8 new Thread (){ @Override public void run () { System.out.println("nihao" ); } }.start(); new Thread (() -> System.out.println("nihao" )).start();
线程的优先级
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static final int MIN_PRIORITY = 1 ; public static final int NORM_PRIORITY = 5 ; public static final int MAX_PRIORITY = 10 ;
如何获取和设置当先线程的优先级
1 2 Thread.currentThread().setPriority(3 ); int priority = Thread.currentThread().getPriority();
只是从概率上高优先级的先执行
买票问题中private state int ticket = 100;
方法二:使用Runnable方法
创建一个实现了Runnable接口的类对象
实现Runnable中的抽象方法:run()
将此对象作为参数传递到Tread类的构造器中,创建Thread类的对象
通过Thread类的对象调用start()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class ThreadTest { @Test public void Mytest () { Runnable impRunnable = new Runnable () { @Override public void run () { log.debug("创建成功" ); } }; Thread thread = new Thread (impRunnable,"线程名称" ); thread.start(); new Thread (myThred).start(); } } Runnable impRunnable = () -> log.debug("创建成功" );
买票问题中private int ticket = 100;
创建线程的两种方式的比较 实现的方式更好些:
java是单继承,继承Thread会破坏继承体系。
实现的方式可以天然的实现共享数据。
更容易与线程池等高级API相配合
联系
Thread也是实现了Runnable接口,两种方法都需要重写run()
方法三:使用Callable方法 与使用Runnable相比,Callable方法更加强大
可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助Future Task类,比如获取返回结果
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ThreadNew { public static void main (String[] args) { FutureTask<Integer> futureTask = new FutureTask <>(new Callable <Integer>() { @Override public Integer call () throws Exception { log.debug("创建成功" ); return 100 ; } }); Thread thread = new Thread (futureTask, "ok" ); thread.run(); } }
原理之线程运行 栈与栈帧 Java Virtual Machine Stacks (Java虚拟机栈)
JVM中由堆、栈、方法区所组成,其中的栈就是给线程使用,每个线程启动后,虚拟机就会为其分配一块栈内存
每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
每个线程只能用有一个活动栈帧,对应着正在执行的那个方法
线程上下文切换 因为以下一些原因导致cpu不在执行当前的线程,转而执行拧一个线程的代码
现成的CPU时间片用完
垃圾回收
有更高优先级的线程需要先运行
线程自己调用了sleep,yield,wait,join,park,synchronized,lock等方法
当上下文切换发生时,由操作系统保存当前现成的状态,并恢复另外一个线程的状态,Java中对应的概念就是程序计数器,他的作用是记住下一条jvm指令的执行地址,是线程私有的。
线程的生命周期
Thread类中的常用方法
start()
: 启动当前线程,调用线程中的run()方法
run()
: 通常需要重写该方法,将创建的线程要执行的操作声明在此方法中
currentThread()
: 静态方法,返回当前代码的线程
getName()
: 获取当前线程的名字
setName(String name)
: 设置当前线程的名字
yield()
: 释放当前CPU的执行权
join()
: 在线程a中调用线程b的join()方法,此时b线程就进入阻塞状态,知道a线程执行完,b线程才结束阻塞状态
stop()
: 当执行此方法时结束该线程
sleep()
: 当前线程阻塞一定的时间(指定的毫秒数),在指定的时间内该线程是阻塞状态
isAlive()
: 判断当前线程是否存活
setPriority()
设置线程优先级
sleep与yield sleep
调用sleep会让当前线程从Running进入Timed Waiting状态
其他线程可以使用interrupt方法来打断正在睡眠的线程,这时候sleep方法会抛出InterruptedException
睡眠结束的线程未必会立刻执行
建议使用TimeUnit的sleep代替Thread的sleep来获得更好地可读性
yield
调用yield会让当前线程从Running进入Runabble状态,然后任务调度器可以调度执行其他同优先级的线程。如果这时没有同优先级的线程,那么不能保证当前线程暂停的结果
具体的实现依赖于操作系统的任务调度器
join方法详解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Slf4j public class JoinTest { static Integer num = 0 ; public static void main (String[] args) throws InterruptedException { log.debug("开始" ); Thread thread = new Thread (() -> { log.debug("开始" ); sleep(1000 ); num = 10 ; },"线程1" ); thread.start(); System.out.println(num); thread.join(); System.out.println(num); } }
thread.join();
等待thread
线程完成之后再进行主线程。
interrupt方法详解 打断sleep,wait,join的线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public static void main (String[] args) { Thread thread = new Thread (() -> { log.debug("sleep....." ); try { Thread.sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("结束睡眠" ); }, "t1" ); thread.start(); log.debug("开始打断" ); thread.interrupt(); log.debug(String.valueOf(thread.isInterrupted())); }
1 2 3 4 5 6 7 8 9 19:35:32.025 [t1] DEBUG InterruptTest - sleep..... 19:35:32.025 [main] DEBUG InterruptTest - 开始打断 19:35:32.027 [main] DEBUG InterruptTest - true 19:35:32.028 [t1] DEBUG InterruptTest - 结束睡眠 java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at InterruptTest.lambda$main$0(InterruptTest.java:17) at java.lang.Thread.run(Thread.java:748)
两阶段终止模式 在一个线程中T1如何优雅的终止另一个线程T2,这里的优雅指的是给T2一个料理后事的机会
错误思路
使用线程对象的stop()方法停止线程
stop方法会真正杀死线程,但是如果这时线程锁住了共享资源,那么当他被杀死后就再也没有机会释放锁,其他线程也在也无法获取锁
使用System.exit()方法停止整个线程
正确思路 两阶段终止模式
1 2 3 4 5 6 7 8 9 10 graph TD w("while(true)") --> a a("有没有被打断") -- 是 --> b("料理后事") b--> c("循环结束") a -- 否 --> d("睡眠两秒") d -- 无异常 --> e("执行监控记录") d -- 有异常 --> i("设置打断标记") i --> w e --> w
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class TwoPhaseTerminationTest { public static void main (String[] args) throws InterruptedException { TPT tpt = new TPT (); tpt.start(); Thread.sleep(3500 ); tpt.stop(); } } class TPT { private Thread monitor; public void start () { monitor = new Thread (() -> { while (true ){ Thread thread = Thread.currentThread(); if (thread.isInterrupted()){ System.out.println("料理后事" ); break ; } try { Thread.sleep(500 ); System.out.println("执行监控" ); } catch (InterruptedException e) { e.printStackTrace(); thread.interrupt(); } } }); monitor.start(); } public void stop () { monitor.interrupt(); } }
interrupt
会打断park
的线程,但是打断状态为true
时park
将不起作用。
主线程与守护线程 默认情况下,Java进程需要等待所有进程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其他非守护线程结束了,即使守护线程的代码没有执行完,也会强制执行结束。
线程的安全问题 原因:操作共享数据
方法一:同步代码块 synchronized(同步监视器){}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class MyThread extends Thread { private int ticket = 100 ; public void run () { synchronized (MyThread.class){ if (ticket > 0 ){ ticket--; } } } } class MyThread2 implements Runnable { private int ticket = 100 ; public void run () { synchronized (this ){ if (ticket > 0 ){ ticket--; } } } }
操作共享数据的代码,即为需要被同步的代码
共享数据:多个线程共同操作的变量
同步监视器,俗称:锁,任何一个对象都可以充当锁。要求是每个人都要共用同一把锁
不能包含少了,也不能包含多了
优点:
局限性:
方法二:同步方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class MyThread3 extends Thread { private static int ticket = 100 ; public void run () { show(); } public static synchronized void show () { if (ticket > 0 ){ ticket--; } } } class MyThread4 implements Runnable { private int ticket = 100 ; public synchronized void run () { if (ticket > 0 ){ ticket--; } } }
总结:
同步方法依然涉及到同步监视器,只是不需要显式的声明
继承使用的是当前类,实现使用的是当前对象
懒汉式的线程安全问题
效率较差
1 2 3 4 5 6 7 8 9 10 11 12 13 class Bank { private Bank () {} private static Bank instance = null ; public static synchronized Bank getInstance () { if (instance == null ){ instance = new Bank (); } return instance; } }
效率高一些
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Bank1 { private Bank1 () {} private static Bank1 instance = null ; public static Bank1 getInstance () { if (instance == null ){ synchronized (Bank.class){ if (instance == null ){ instance = new Bank1 (); } } } return instance; } }
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方先放弃,这就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞当中,无法继续
解决方法:
专门的算法、原则
尽量 减少同步资源的定义
避免嵌套同步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 public class DeadLock { public static void main (String[] args) { StringBuffer s1 = new StringBuffer (); StringBuffer s2 = new StringBuffer (); new Thread (){ @Override public void run () { synchronized (s1) { try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } s1.append("a" ); s2.append("1" ); synchronized (s2) { s1.append("b" ); s2.append("2" ); } } } }.start(); new Thread (new Runnable () { @Override public void run () { synchronized (s2) { try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } s1.append("c" ); s2.append("3" ); synchronized (s1) { s1.append("d" ); s2.append("4" ); } } } }).start(); System.out.println(s1); System.out.println(s2); } }
方法三:lock锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Windows implements Runnable { private int ticket = 100 ; private ReentrantLock lock = new ReentrantLock (); @Override public void run () { while (true ){ try { lock.lock(); if (ticket > 0 ){ Thread.sleep(100 ); System.out.println(ticket); ticket--; }else { break ; } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } } public class LockTest { public static void main (String[] args) { Windows windows = new Windows (); new Thread (windows).start(); new Thread (windows).start(); } }
synchronized和lock锁的异同:
相同点:都能解决线程安全问题
不同点:lock锁手动的锁定和手动的解锁,synchronized自动释放
线程的通信 wait()
调用wait()方法的线程进入阻塞状态,并且释放同步监视器。
notify()
调用notify()方法使线程唤醒
notifyAll()
唤醒所有被wait()的方法
上述的方法必须使用在同步代码块或者同步方法之中。
上述的方法的调用者必须是同步代码块或同步方法中的同步监视器。否则会出现异常
上述的方法定义在Object类中
sleep()和wait()方法的异同 相同点:两者都可以使得当前线程进入阻塞状态
不同点:
两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wai()
调用的条件不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或者同步方法中
关于是否释放不同监视器:如果两个方法都在同步代码块或者同步方法中,sleep()不会释放锁
JDK5.0新增的线程创建接口 新增方式一Callable方法 与使用Runnable相比,Callable方法更加强大
可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助Future Task类,比如获取返回结果
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ThreadNew { public static void main (String[] args) { FutureTask<Integer> futureTask = new FutureTask <>(new Callable <Integer>() { @Override public Integer call () throws Exception { log.debug("创建成功" ); return 100 ; } }); Thread thread = new Thread (futureTask, "ok" ); thread.run(); } }
新增方式二线程池 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时使用时直接获取,使用结束后放回。可以避免重复创建和销毁,实现重复利用,类似于生活中的公共交通。
主要特点是:线程复用;控制最大并发数;管理线程
优点:
线程池相关的API的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class NumThread implements Runnable { @Override public void run () { for (int i = 0 ; i < 100 ; i++) { System.out.println(i); } } } public class ThreadPool { public static void main (String[] args) { ExecutorService service = Executors.newFixedThreadPool(10 ); service.execute(new NumThread ()); service.shutdown(); } }