多线程基础知识回顾
多线程的基础知识复习
1)进程和线程的区别
进程:
程序运行后,一个QQ,微信等就是一个进程。
线程:
线程是进程中的最小单元。说简单的话说,线程就是程序中不同的执行路径。
程序:
QQ是一个程序,是一个硬盘上的程序,
2)线程run方法和start方法的区别
public class T01_WhatIsThread {
//新建了静态内部类继承Thrread,线程修改1秒,输入T1
private static class T1 extends Thread {
@Override
public void run() {
for(int i=0; i<10; i ) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1");
}
}
}
//main方法
public static void main(String[] args) {
new T1().run();
//new T1().start();
for(int i=0; i<10; i ) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main");
}
}
}
输出
T1
T1
main
main
//这个时候注释18run方法 开始start方法 执行结果大不一样
T1
main
T1
main
#结论 therad的start方法 执行路径是分支的形式,而run方法是重上到下依次执行。
3)多线程的常用实现方法
//线程主要实现的方法有3种
public class T02_HowToCreateThread {
//集成Thread的方法
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}
//实现Runnable接口
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println("Hello MyRun!");
}
}
//三种线程不同的运行方式
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRun()).start();
//lamda表达式来执行一个线程
new Thread(()->{
System.out.println("Hello Lambda!");
}).start();
}
![https://img-blog.csdnimg.cn/20191030175728242.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3c2ODMzNTM5Nw==,size_16,color_FFFFFF,t_70](https://img-blog.csdnimg.cn/20191030175728242.jpeg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3c2ODMzNTM5Nw==,size_16,color_FFFFFF,t_70)
}
// lamda表达式也是一种方式,线程池也是一种方式
//Executors.newCachedThreadPool();
//通过线程池去拿到一个线程,而这个线程还是要执行runable或者start的方法。
4)线程最基本的方法
- 4.1 sleep
- 当前线程睡眠多少时间,由其他线程来执行
- 4.2 join
- 常用于等待另外一个线程结束,也就是保证线程有序执行。
//测试join线程 static void testJoin() { Thread t1 = new Thread(()->{ //线程1创建了10个线程 for(int i=0; i<10; i ) { System.out.println("A" i); try { //每个线程休眠500毫秒 Thread.sleep(500); //TimeUnit.Milliseconds.sleep(500) } catch (InterruptedException e) { e.printStackTrace(); } } }); //线程2 Thread t2 = new Thread(()->{ try { //线程1的线程加入。等待线程1结束 t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } //线程1结束后,才开始执行此代码 for(int i=0; i<10; i ) { System.out.println("B" i); try { Thread.sleep(500); //TimeUnit.Milliseconds.sleep(500) } catch (InterruptedException e) { e.printStackTrace(); } } }); //分别启动 t1.start(); t2.start(); }
- 常用于等待另外一个线程结束,也就是保证线程有序执行。
#执行结果
A0
A1
A2
A3
A4
A5
A6
A7
A8
A9
B0
B1
B2
B3
B4
B5
B6
B7
B8
B9
```
- 4.3 yield
- 当前线程先离开,返回到就绪的状态,至于这个线程是否会被CPU马上执行,还是先执行等待队列中已经等待的线程,这个不一定。看竞争
线程的状态图
- Ready(就绪) Running(正在运行) 都属于Runnable状态。
- TimedWaiting 的相关方法是指时间一到,就会自动恢复runnable状态
- Waiting 状态是指必须被唤醒才能进入Runnabel状态
- synchronized 得到同步代码块的锁 之前会进入阻塞状态,得到锁之后,线程运行
- Terminated 线程停止
- 不建议用Thread的stop方法来强行停止线程,有安全问题。
- 对于interrupt的一些说明:
#线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;
#调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
#我们会catch这个异常,再根据业务逻辑去处理线程的后续行为。
#代码示例
@Override
public void run() {
try {
// 1. isInterrupted()保证,只要中断标记为true就终止线程。
while (!isInterrupted()) {
// 执行任务...
}
} catch (InterruptedException ie) {
// 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
}
}
//interrupt 用于控制业务场景的用法极少,正常用法一般是某一个线程阻塞时间很长很长,通过interrupt来打断线程。
- 线程状态的小例子
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(this.getState());
for(int i=0; i<4; i ) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
public static void main(String[] args) {
Thread t = new MyThread();
System.out.println(t.getState());
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getState());
}
#输出结果
NEW
RUNNABLE
0
1
2
3
TERMINATED
synchronize 锁
-
需要注意的是 锁,所的是对象,而不是代码
- 比如:
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
synchronized(o) { // 任何线程要执行下面的代码,必须先拿到o的锁
count–;
System.out.println(Thread.currentThread().getName() " count = " count);
}
}
}
#问题
上述代码如果new了2个不同的object,o和o1,那synchronize(o) 是锁誰呢,还是锁o。```
- 比如:
-
synchronize 的几种锁的形式
- 锁this
private int count = 10; public void m() { synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁 count--; System.out.println(Thread.currentThread().getName() " count = " count); } }
- 锁方法
private int count = 10; public synchronized void m() { //等同于在方法的代码执行时要synchronized(this) count--; System.out.println(Thread.currentThread().getName() " count = " count); }
- 锁静态方法
private static int count = 10; public synchronized static void m() { //这里等同于synchronized(T.class) count--; System.out.println(Thread.currentThread().getName() " count = " count); } public static void mm() { synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以?(不可,因为没有new,) count --; } }
- 小思考
- 线面的线程如何输出?
2)加上synchronize后又有什么区别
3)加上volatile后又什么区别
- 线面的线程如何输出?
private /*volatile*/ int count = 100; public /*synchronized*/ void run() { count--; System.out.println(Thread.currentThread().getName() " count = " count); } public static void main(String[] args) { T t = new T(); for(int i=0; i<100; i ) { new Thread(t, "THREAD" i).start(); } } #1 打印列会出现重复或者实际减的数和打印的数不一致。 //打印结果抽取异常部分 THREAD81 count = 37 THREAD70 count = 37 #2 synchronize 既保证可见又保证一致性 #3 volatile 保证可见性,这个变量改后立马被线程发现。 #4 加了synchronize就不必加volatile。
-
同一个线程可以同时掉加锁和不加锁的方法,并不会导致一个方法有锁,而导致不加锁的方法无法执行。
-
读方法和写方法是否都需要上锁来保持一致,看具体的业务逻辑。如果写上锁,读不上锁,有可能脏读数据,这个根据业务来定。读上锁会让读取的效率大大降。
-
synchronize 是可重入锁,如果上锁的方法1,调用上锁的方法2,如果不可以重入,会产生死锁。
-
public class T { int count = 0; synchronized void m() { System.out.println(Thread.currentThread().getName() " start"); while(true) { count ; System.out.println(Thread.currentThread().getName() " count = " count); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if(count == 5) { int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch,然后让循环继续 System.out.println(i); } } } }} public static void main(String[] args) { T t = new T(); Runnable r = new Runnable() { @Override public void run() { t.m(); } }; new Thread(r, "t1").start(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(r, "t2").start(); } } #//打印结果 人为异常不补货,t2得到锁。继续执行 t1 count = 2 t1 count = 3 t1 count = 4 t1 count = 5 t2 start Exception in thread "t1" t2 count = 6 java.lang.ArithmeticException: / by zero at com.mashibing.juc.c_011.T.m(T.java:27) at com.mashibing.juc.c_011.T$1.run(T.java:39) at java.base/java.lang.Thread.run(Thread.java:844) t2 count = 7
-
推荐文章 锁升级的小段子
偏向锁(谁来谁第一,谁偏向)----》
竞争 自旋锁,(自旋10次或者 JDK目前规定为自旋线程超过CPU内核数的一半)
如果超过了自旋的上限,就升级重量级锁,重锁是OS(操作系统)级别的锁,并进入等待队列。- 锁使用的场景:
- 锁使用的场景:
-
总结
线程的概念、线程的启用方式,常用的方法介绍。
线程的状态机、
synchronize 锁的是对象,不是代码。
synchronize 锁的集中方式
synchronize 的锁的升级。
异常锁,默认放弃锁。除非catch跳过循环。
偏向锁、自旋锁 (公平、非公平)重量级锁。 -
作者QQ 68335397 有问题指正或者讨论学习,可以撩,博客会定期回复。感谢大家。