Java多线程知识点总结
(1)什么是进程?什么是线程?
进程:
是并发执行程序在执行过程中分配和管理资源的基本单位,当程序进入内存圆形时即为线程
进程三大特点:
- 独立性: 进程是系统中独立存在的实体,它可以独立拥有资源,每个进程都有自己的独立空间。
- 动态性: 进程和程序的区别在于进程动态的,进程具有自己的生命周期
- 并发性:多个进程可以在单个处理器上并发执行互不影响
并行和并发:
- 并行是指同一时刻,多个命令同时执行;
- 并发是指在同一时刻,只有一条命令是在处理器上执行的,但多个进程命令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
线程:
- 线程(Thread)被称为轻量级进程,线程是进程的执行单元
- 线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程。线程可以拥有堆栈,局部变量…但不能拥有系统资源,它与父进程的其他线程共享该进程的所有资源
- 一个线程可与其父进程的其他线程共同拥有父进程共享变量和部分环境,共同非合作完成任务。
- 可以简便理解一个Class类执行时就是一个JVM的进程,其中的成员属性是子线程共享资源,main方法是主线程,其他方法是子线程,子线程(方法)相互合作共同完成Class任务。
- 线程是独立运行的,线程的执行是抢占式的,也就是说执行的线程可能被挂起,以便运行另一个线程。
- 一个进程可以创建或者撤销另一个线程,一个进程中的线程是并发执行的。
(2)多线程的运行状态
(1)线程一般具有5种基本状态 :
创建, 就绪, 运行, 阻塞, 终止
(2)
创建状态:在程序中
(3)线程的创建和使用
(1)线程的创建
在Java 中如果要想实现多线程,那么必须依靠线程的主题类,但这个线程主题类在定义的时候,也需要一类特殊的要求,这个类可以继承 Thread ,实现Runnable 接口或者实现 Callable 接口来完成定义。任何一个类只要继承了 Thread 类就可以成为一个类的主类,同时线程中需要覆写父类终点 run( )方法。
(2)线程的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class JavaDemoA { public static void main(String[] args) { new MyThread( "线程A:" ).start(); new MyThread( "线程B:" ).start(); new MyThread( "线程C:" ).start(); } } class MyThread extends Thread{ //线程的主体类 private String title; public MyThread(String title){ this .title=title; } @Override public void run() { for ( int i= 1 ;i<= 4 ;i++){ System.out.println( this .title+i); } } } |
(4)Runnable 接口实现多线程
Thread是 Runnable 的子类,继承了 Runnable 的接口,Thread 类的确可以方便实现多线程,但这种方式最大的缺陷就是单继承局限性
Runnable 接口从 JDK1.8 开始成为一个函数接口,可以直接利用lambda表达式来实现线程主体代码,同时在该接口中提供有run()方法进行线程功能定义
Thread构造方法:public Thread(Runnable target).
在Thread类中会保存有target属性,该属性保存的是Runnable的核心业务主体对象
当Thread.start() 方法启动多线程时也会调用Thread.run() 方法,而在 Thread.run() 会判断是否提供有target实例,如果提供则调用真实主体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class JavaDemoB { public static void main(String[] args) { new Thread( new MyThreadB( "线程A:" )).start(); new Thread( new MyThreadB( "线程B:" )).start(); new Thread( new MyThreadB( "线程C:" )).start(); } } class MyThreadB implements Runnable{ private String title; public MyThreadB(String title){ this .title=title; } @Override public void run() { for ( int i= 1 ;i<= 5 ;i++){ System.out.println(title+i); 加粗样式 } } } |
(5)Callable接口实现多线程
Runnable 接口实例化多线程可以避免单继承问题,但 Runnable 中的run() 方法不能返回操作结果,为啦解决这样的问题,从JDK1.5开始对于多线程的实现提供了一个新的接口 java.util.concurrent.Callable
Callable接口在定义时需要定义泛型。
通过FutureTask 实现Callable接口和Thread类的联系,并且通过FutureTask类获取Callable接口中call()方法的返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class JavaDemoC { public static void main(String[] args) throws Exception{ FutureTask<String> task= new FutureTask<>( new MyThreadC()); new Thread(task).start(); System.out.println(task.get()); } } class MyThreadC implements Callable<String> { @Override public String call() throws Exception { for ( int i= 1 ;i<= 5 ;i++){ System.out.println( "线程执行:i=" +i); } return "Callable实现多线程" ; } } |
多线程常用操作方法
(1)线程的命名和取得
public Thread (Runnable target,String name) 构造实例化线程对象,接受 Runnable 接口子类对象,同时设置线程名称;
public final void setName(String name) 普通 设置线程名称
public final String getName() 取得线程名字
每当实例化Thread类对象时,都会调用init()方法,并且在没有为线程命名时,自动命名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package ThreadTest; public class JavaDemoD { public static void main(String[] args) { MyThreadD myThread= new MyThreadD(); new Thread(myThread, "线程A" ).start(); //线程手动命名 new Thread(myThread).start(); //线程自动命名 new Thread(myThread, "线程B" ).start(); } } class MyThreadD implements Runnable{ @Override public void run() { //获取当前线程名称 System.out.println(Thread.currentThread().getName()); } } |
(2)线程休眠
线程有时候需要减缓执行速度,所以Thread 类提供啦sleep()方法
public static void sleep(long millis) throws InterruptedException设置线程毫秒数,时间一到自动唤醒
public static void sleep(long millis,int nanos) throws InterruptedException设置线程毫秒数,纳秒数,时间一到自动唤醒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class JavaDemoE { public static void main(String[] args){ Runnable runnable=(()->{ //Runnable接口实例 for ( int i= 1 ;i<= 5 ;i++){ System.out.println(Thread.currentThread().getName()+i); try { Thread.sleep( 2000 ); //强制让线程休眠2秒 } catch (InterruptedException e) { e.printStackTrace(); } } }); new Thread(runnable, "线程A:" ).start(); new Thread(runnable, "线程B:" ).start(); } //本程序两个线程对象,每一个线程对象执行时都要休眠一秒,因为多线程的启动和执行都是有操作系统, //随机分配,虽然看起来A,B线程同时休眠,但也有先后顺序 } |
(3)线程中断
Thread 中提供的线程方法很多都会抛出InterruptedException中断异常,所以线程在执行过程中可以被另一个线程中断。
public boolean isInterrupted( ) 普通方法 判断线程是否被中断
public void interrupt( ) 普通 中断线程执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class JavaDemoF { public static void main(String[] args) throws InterruptedException { Thread thread= new Thread(()->{ System.out.println( "线程累了想休眠10S" ); try { Thread.sleep( 10000 ); System.out.println( "时间到,自然醒来" ); } catch (InterruptedException e) { System.out.println( "被强制唤醒,继续工作" ); } }); thread.start(); //线程执行启动 Thread.sleep( 2000 ); //先让子线程运行2秒 if (!thread.isInterrupted()){ //如果线程没有中断 System.out.println( "让线程终止休眠" ); thread.interrupt(); //线程中断 } } } |
(4)线程强制执行
在多线程并发执行中每个线程对象都会交替执行,如果某个线程对象需要优先完成执行,可以使用 join ()方法强制执行,待其执行完毕后其他线程才会继续执行
public final void join() throws InterruptedException
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
|
public class JavaDemoG { public static void main(String[] args) { Thread threadB= new Thread( new MyThreadH(), "线程B" ); Runnable runnable=(()->{ for ( int i= 1 ;i<= 9 ;i++){ System.out.println(Thread.currentThread().getName()+i); if (i== 5 ){ try { threadB.join(); //如果i=5,强制执行线程B } catch (InterruptedException e) { e.printStackTrace(); } } } }); new Thread(runnable, "线程A" ).start(); threadB.start(); } } class MyThreadH implements Runnable{ @Override public void run() { for ( int i= 1 ;i<= 5 ;i++){ System.out.println(Thread.currentThread().getName()+i); } } } |
(5)线程礼让
多线程在彼此交替执行时往往需要进行资源轮流占用,如果某些不是很重要的线程抢占到资源但又不着急执行时就可以暂时礼让出去,让其他线程先执行。
public static void yield( ) 线程礼让
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class JavaDemoH { public static void main(String[] args) { Runnable runnable=(()->{ //Lambda实例化线程类对象,方便演示 for ( int j= 1 ;j<= 15 ;j++){ System.out.println(Thread.currentThread().getName()+j); } }); new Thread( new MyThreadI(), "礼让线程A" ).start(); new Thread(runnable, "非礼让线程B:" ).start(); new Thread(runnable, "非礼让线程C:" ).start(); new Thread(runnable, "非礼让线程D:" ).start(); } } class MyThreadI implements Runnable{ @Override public void run() { for ( int i= 1 ;i<= 10 ;i++){ System.out.println(Thread.currentThread().getName()+i); if (i% 3 == 0 ){ Thread.yield(); //线程礼让 } } } } |
(6)线程优先级
在java线程操作时,所有线程运行前都保持就绪状态,此时那个线程的优先级高就有可能优先被执行
所有创建的线程都是子线程,启动时都是同样的优先级。
主线程是中等优先级 5
public final void setPriority ( int newPriority ) 设置线程优先级
MAX_PRIORITY 最高优先级 10
NORM_PRIORITY 中等优先级 5
MIN_PRIORITY 最低优先级 1
public final int getPriority ( ) 获取线程优先级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class JavaDemoI { public static void main(String[] args) { Runnable runnable=(()->{ for ( int i= 1 ;i<= 5 ;i++){ System.out.println(Thread.currentThread().getName()+i); } }); Thread threadA= new Thread(runnable, "线程A" ); Thread threadB= new Thread(runnable, "线程B" ); Thread threadC= new Thread(runnable, "线程C" ); threadA.setPriority(Thread.MIN_PRIORITY); threadB.setPriority(Thread.NORM_PRIORITY); threadC.setPriority(Thread.MAX_PRIORITY); threadA.start(); threadB.start(); threadC.start(); } } |
(7)如何停止线程
(1)停止线程的3个方法(了解)
- 停止多线:public void stop ()
- 挂起多线程::public final void suspend()
- 恢复挂起的多线程:public final void resume()
(2)对多线程中 stop (), suspend(), resume() 方法在jdk1.2开始不建议使用,主要是因为这三个方法在操作时会产生死锁的问题。
(3)优雅的停止一个线程
代码演示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package ThreadTest; public class JavaDemoO { private static boolean flag= true ; public static void main(String[] args) throws InterruptedException { new Thread(()->{ long num= 0 ; while (flag){ try { Thread.sleep( 10 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "正在运行" +num++); } }, "执行线程" ).start(); Thread.sleep( 200 ); flag= false ; } } |
(8)后台守护线程
(1)java中的线程分为两类:
- 用户线程和守护线程,守护线程:是一种运行在后台的线程服务线程,当用户线程存在时,守护线程也可以用时存在;当用户线程消失时,守护线程也会消失。
- public final void setDaemon(boolean on) 设置为守护线程
- public final boolean idDaemon() 判断是否是守护线程
代码:
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
|
package ThreadTest; public class JavaDemoP { public static void main(String[] args) { Thread threadA= new Thread(()->{ for ( int i= 1 ;i<= 100 ;i++){ try { Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+i); } }, "用户线程" ); Thread threadB= new Thread(()->{ for ( int i= 1 ;i<= 200 ;i++){ try { Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+i); } }, "守护线程" ); threadB.setDaemon( true ); //设置为守护线程 threadA.start(); threadB.start(); } } |
结果:用户线程结束时,守护线程也结束
(9)volatile 关键字
在多线程编程中,若干个线程为了可以实现公共资源的操作,往往是复制相应变量的副本,待完成操作后再将副本变量的数据与原始数据进行同步处理,如果开发者不希望通过副本数据进行操作,而是希望可以直接通过原始变量的操作(节省了复制变量副本与同步的时间),则可以在变量声明时使用volatile关键字.
使用volatile关键字
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package ThreadTest; public class JavaDemoQ { public static void main(String[] args) { new Thread( new MyThreadQ(), "线程A售票成功_剩余票数:" ).start(); new Thread( new MyThreadQ(), "线程B售票成功_剩余票数:" ).start(); new Thread( new MyThreadQ(), "线程C售票成功_剩余票数:" ).start(); new Thread( new MyThreadQ(), "线程D售票成功_剩余票数:" ).start(); } } class MyThreadQ implements Runnable{ private volatile int ticket= 50 ; //直接内存操作 @Override public void run() { while (ticket> 0 ){ try { Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } ticket--; System.out.println(Thread.currentThread().getName()+ticket); } } } |
结果:
volatile与synchronized的区别:
- volatile无法进行同步处理操作,它只是一种直接内存的处理,避免副本操作
- volatile主要是在属性上使用,而synchronize是在方法和代码块上使用。
线程的同步和死锁
(1)线程同步问题
(1) 同步问题的引出
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
|
public class JavaDemoJ { private static int ticket= 5 ; //总票数5张 public static void main(String[] args) { Runnable runnable=(()->{ while ( true ){ //持续卖票 if (ticket> 0 ){ try { Thread.sleep( 100 ); //模拟网络延迟 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+ "卖票成功\t剩余票数:" +ticket--); } if (ticket<= 0 ){ System.out.println(Thread.currentThread().getName()+ "卖票失败\t剩余票数:" +ticket); break ; } } }) ; new Thread(runnable, "售票员A:" ).start(); new Thread(runnable, "售票员B:" ).start(); new Thread(runnable, "售票员C:" ).start(); //假设此时还有一张票,当第一个线程满足售票条件时(还未减少票数),其他线程也可能满足售票条件, //就有可能造成同一张票卖出去两次。 } } |
问题所在:
假设此时还有一张票,当第一个线程满足售票条件时(还未减少票数),其他线程也可能满足售票条件就有可能造成同一张票卖出去两次。
(2) 解决同步问题
Java使用Synchronized关键字实现同步操作,同步的关键是要给代码上“锁”,而对于锁的操作有两种:同步代码块,同步方法。
(1)同步代码块
同步代码块是指使用synchronized关键字定义的代码块,在代码执行时,往往需要设置一个同步对象,由于线程操作的不确定性所以这时候的同步对象可以选择 this。
将票数判断和票数自减放在同一代码块中,当多线程并发执行时,只允许一个线程执行该部分代码块,就实现啦同步处理操作、
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
|
public class JavaDemoK { public static void main(String[] args) { MyThreadK myThreadK= new MyThreadK(); new Thread(myThreadK, "线程A售票成功,剩余票数:" ).start(); new Thread(myThreadK, "线程B售票成功,剩余票数:" ).start(); new Thread(myThreadK, "线程C售票成功,剩余票数:" ).start(); new Thread(myThreadK, "线程D售票成功,剩余票数:" ).start(); } } class MyThreadK implements Runnable{ private static int ticket= 10 ; //总票数 @Override public void run() { while ( true ){ synchronized ( this ){ //同步代码块 if (ticket> 0 ){ try { Thread.sleep( 100 ); //模拟网络延迟 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+(--ticket)); } else { System.out.println( "全部售空" ); break ; } } } } } |
结果:
(2)同步方法解决
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
|
public class JavaDemoL { public static void main(String[] args) { MyThreadL myThreadL= new MyThreadL(); new Thread(myThreadL, "售票员A售票成功,剩余票数:" ).start(); new Thread(myThreadL, "售票员B售票成功,剩余票数:" ).start(); new Thread(myThreadL, "售票员C售票成功,剩余票数:" ).start(); new Thread(myThreadL, "售票员D售票成功,剩余票数:" ).start(); } } class MyThreadL implements Runnable{ private static int ticket= 10 ; @Override public void run() { sale(); } public synchronized void sale() { //同步方法 while ( true ){ if ( this .ticket> 0 ){ try { Thread.sleep( 100 ); //网络延迟 } catch (InterruptedException e) { e.printStackTrace(); } this .ticket--; System.out.println(Thread.currentThread().getName()+ this .ticket); } else { System.out.println( "票已售空" ); break ; } } } } |
结果:
(2)线程死锁问题
(1)死锁问题的引出
代码:
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
|
package ThreadTest; public class JavaDemoJ implements Runnable{ private book book= new book(); private money money= new money(); public JavaDemoJ(){ new Thread( this ).start(); book.tell(money); } public void run(){ money.tell(book); } public static void main(String[] args) { new JavaDemoJ(); } } class book{ public synchronized void tell(money money){ System.out.println( "你先给我钱,我才给你书!" ); money.get(); } public synchronized void get(){ System.out.println( "收到钱,把书给买家!" ); } } class money{ public synchronized void tell(book book){ System.out.println( "你先给我书,我再给你钱!" ); book.get(); } public synchronized void get(){ System.out.println( "收到书,把钱给卖家!" ); } } |
结果:
结论:本程序中采用大量同步处理操作,而死锁一旦出现线程将进入等待操作,并且不会向下继续执行。
(3)生产者消费者问题
(1)生产者生产,消费者取出,生产者生产一个,消费者取出一个
未同步代码:
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
package ThreadTest; public class JavaDemoM { public static void main(String[] args) { Message message= new Message(); new Thread( new Producer(message)).start(); new Thread( new Consumer(message)).start(); } } class Message{ //消息类 private String title; //标题 private String content; //内容 public String getTitle() { return title; } public void setTitle(String title) { this .title = title; } public String getContent() { return content; } public void setContent(String content) { this .content = content; } } class Producer implements Runnable{ //定义生产者 private Message msg= null ; public Producer(Message msg){ this .msg=msg; } @Override public void run() { for ( int i= 1 ;i<= 50 ;i++){ try { Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } if (i% 2 == 0 ){ msg.setTitle(i+ "英雄" ); msg.setContent( "名垂千古" ); } else { msg.setTitle(i+ "小人" ); msg.setContent( "遗臭万年" ); } } } } class Consumer implements Runnable{ private Message msg; public Consumer(Message msg){ this .msg=msg; } @Override public void run() { for ( int i= 1 ;i<= 50 ;i++){ try { Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "【" +msg.getTitle()+ "," +msg.getContent()+ "】" ); } } } |
错误产生结果:
问题分析:
数据错位:假设生产者线程还刚在存储空间添加一个数据的标题,还未添加内容,程序就切换到消费者线程,消费者就会把没有生产者没有添加的内容和上一组生产的内容联系在一起导致数据错位。重复操作:生产者线程放入多组数据,消费者线程才开始取出,或者是消费者还没生产数据,消费者就已经重复取出数据。
同步代码解决:
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
package ThreadTest; public class JavaDemoN { public static void main(String[] args) { Message2 msg= new Message2(); new Thread( new Producer2(msg)).start(); new Thread( new Consumer2(msg)).start(); } } class Message2 { private String title; private String content; private boolean key= true ; //true 允许生产不允许消费 //false 允许消费不允许生产 public synchronized void set(String title,String content) { //同步方法 if ( this .key== false ){ //允许消费不允许生产 try { super .wait(); //线程等待 } catch (InterruptedException e) { e.printStackTrace(); } } this .title = title; this .content = content; this .key= false ; //生产完毕 super .notify(); //唤醒线程 } public synchronized String get() { if ( this .key== true ){ //允许生产不允许消费 try { super .wait(); //线程等待 } catch (InterruptedException e) { e.printStackTrace(); } } try { return title + "," + content; } finally { this .key= true ; super .notify(); } } } class Producer2 implements Runnable{ //定义生产者 private Message2 msg= null ; public Producer2(Message2 msg){ this .msg=msg; } @Override public void run() { for ( int i= 1 ;i<= 50 ;i++){ try { Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } if (i% 2 == 0 ){ this .msg.set(i+ "英雄" , "万古流芳" ); } else { this .msg.set(i+ "小人" , "遗臭万年" ); } } } } class Consumer2 implements Runnable{ private Message2 msg; public Consumer2(Message2 msg){ this .msg=msg; } @Override public void run() { for ( int i= 1 ;i<= 50 ;i++){ try { Thread.sleep( 100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "【" +msg.get()+ "】" ); } } } |
正确执行结果:
原理解释:
将同步操作设置在Message类中,使用synchronized关键字可以使消息内容同步,不会造成消息错位。
在生产者和消费者线程中根据key判断,是否可以生产或者取出,如果不能生产或者取出,可使用 wait( ) 线程等待方法 等待另一方线程取出或者生产后,使用 notify( ) 线程唤醒,继续执行操作。notifyAll( ) 是唤醒全部等待的线程
以上为个人经验,希望能给大家一个参考,也希望大家多多支持服务器之家。
原文链接:https://blog.csdn.net/qq_44287198/article/details/116668489