avatar

并发编程(一)

一、并发编程入门

1.1 线程

1.1.1进程和线程进程

进程

进程指正在运行的程序,进程拥有一个完整的、私有的基本运行资源集合。通常,每个进程都有

自己的内存空间。

进程往往被看作是程序或应用的代名词,然而,用户看到的一个单独的应用程序实际上可能是一组相互 协作的进程集合。

为了便于进程之间的通信,大多数操作系统都支持进程间通信(IPC),如pipes 和sockets。IPC不仅支持同一系统上的通信,也支持不同的系统。IPC通信方式包括管道(包括无名管道和命名管道)、 消息队列、信号量、共享存储、Socket、Streams等方式,其中 Socket和Streams支持不同主机上的两个进程IPC。

线程

线程有时也被称为轻量级的进程。进程和线程都提供了一个执行环境,但创建一个新的线程比创建一个新的进程需要的资源要少。

线程是在进程中存在的 — 每个进程最少有一个线程。线程共享进程的资源,包括内存和打开的文件。这样提高了效率,但潜在的问题就是线程间的通信。

多线程的执行是Java平台的一个基本特征。每个应用都至少有一个线程 – 或几个,如果算上“系

统”线程的话,比如内存管理和信号处理等。但是从程序员的角度来看,启动的只有一个线程,叫主线 程。

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。

1.1.2 线程实践

1.1.2.1 线程的创建

两种方式:

1
2
3
4
5
6
7
8
public class HelloThread extends Thread {
   public void run() {        
System.out.println("Hello from a thread!");  
}
   public static void main(String args[]) {      
(new HelloThread()).start();  
}
}
1
2
3
4
5
6
7
8
public class HelloRunnable implements Runnable {
   public void run() {        
System.out.println("Hello from a thread!");  
}
   public static void main(String args[]) {      
(new Thread(new HelloRunnable())).start();  
}
}
1.1.2.2 线程启动和停止

启动线程:调用start方法

停止线程:线程自带的stop方法,一方面已经过时,另一方面,不会对停止的线程做状态保存,使得线程中涉及的 对象处于未知状态,如果这些状态,其他线程也会使用,将会使得其他线程出现无法预料的异常,所 以,停止程序的功能,需要自己实现。

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
public class ThreadTest {    
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();
thread.start();
Thread.sleep(1000L);
thread.stop();
while (thread.isAlive()) {}        
thread.print();  
}
   private static class StopThread extends Thread {
       private int x = 0;
private int y = 0;
       @Override        
public void run() {            
synchronized (this) {                
++x;                
try {                    
Thread.sleep(3000L);              
} catch (InterruptedException e) {
e.printStackTrace();              
}
++y;          
}
}
       public void print() {
System.out.println("x=" + x + " y=" + y);
}  
}
}

上述代码中,run方法里是一个同步的原子操作,x和y必须要共同增加,然而这里如果调用 thread.stop()方法强制中断线程,输出如下:

1
x=1 y=0

没有异常,也破坏了我们的预期。如果这种问题出现在我们的程序中,会引发难以预期的异常。因此这 种不安全的方式很早就被废弃了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyRunnable implements Runnable {
   private boolean doStop = false;
   public synchronized void doStop() {
this.doStop = true;  
}
   private synchronized boolean keepRunning() {
return this.doStop == false;  
}
   @Override    
public void run() {        
while(keepRunning()) {            
// keep doing what this thread should do.
System.out.println("Running");
           try {                
Thread.sleep(3L * 1000L);          
} catch (InterruptedException e) {
e.printStackTrace();          
}
      }  
}
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyRunnableMain {
   public static void main(String[] args) {        
MyRunnable myRunnable = new MyRunnable();
       Thread thread = new Thread(myRunnable);
       thread.start();
       try {            
Thread.sleep(10L * 1000L);      
} catch (InterruptedException e) {            
e.printStackTrace();      
}
       myRunnable.doStop();  
}
}
1.1.2.3 线程暂停和中断

暂停
Java中线程的暂停是调用 java.lang.Thread 类的 sleep 方法。该方法会使当前正在执行的线程暂停 指定的一段时间,如果线程持有锁, sleep 方法结束前并不会释放该锁。
中断
java.lang.Thread类有一个 interrupt 方法,该方法直接对线程调用。当被interrupt的线程正在 sleep或wait时,会抛出 InterruptedException 异常。 事实上, interrupt 方法只是改变目标线程的中断状态(interrupt status),而那些会抛出 InterruptedException 异常的方法,如wait、sleep、join等,都是在方法内部不断地检查中断状 态的值。

  • interrupt方法
    Thread实例方法:必须由其它线程获取被调用线程的实例后,进行调用。实际上,只是改变了被调用线 程的内部中断状态;
  • Thread.interrupted方法
    Thread类方法:必须在当前执行线程内调用,该方法返回当前线程的内部中断状态,然后清除中断状态 (置为false) ;
  • isInterrupted方法
    Thread实例方法:用来检查指定线程的中断状态。当线程为中断状态时,会返回true;否则返回 false。
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
public class ThreadTest {    
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();        
thread.start();        
Thread.sleep(1000L);        
thread.interrupt();        
while (thread.isAlive()) { }        
thread.print();  
}
   private static class StopThread extends Thread {
       private int x = 0;
private int y = 0;
       @Override        
public void run() {            
synchronized (this) {                
++x;                
try {                    
Thread.sleep(3000L);              
} catch (InterruptedException e) {
e.printStackTrace();              
}                
++y;          
}      
}
       public void print() {
  System.out.println("x=" + x + " y=" + y);      
}  
}
}

输出结果如下:

1
2
3
4
x=1 y=1 
java.lang.InterruptedException: sleep interrupted    
at java.lang.Thread.sleep(Native Method)    
at ThreadTest$StopThread.run(ThreadTest.java:28)

x=1,y=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
 // 核心 interrupt 方法   
public void interrupt() {        
if (this != Thread.currentThread()) // 非本线程,需要检查权限
checkAccess();
       synchronized (blockerLock) {            
Interruptible b = blocker;            
if (b != null) {                
interrupt0();           // 仅仅设置interrupt标志位
b.interrupt(this);    // 调用如 I/O 操作定义的中断方法
return;          
}      
}        
interrupt0();  
}    
// 静态方法,这个方法有点坑,调用该方法调用后会清除中断状态。    
public static boolean interrupted() {        
return currentThread().isInterrupted(true);  
}    
// 这个方法不会清除中断状态    
public boolean isInterrupted() {        
return isInterrupted(false);  
}  
// 上面两个方法会调用这个本地方法,参数代表是否清除中断状态  
private native boolean isInterrupted(boolean ClearInterrupted);

interrupt() :

  • interrupt 中断操作时,非自身打断需要先检测是否有中断权限,这由jvm的安全机制配置;

  • 如果线程处于sleep, wait, join 等状态,那么线程将立即退出被阻塞状态,并抛出一个 InterruptedException异常;

  • 如果线程处于I/O阻塞状态,将会抛出ClosedByInterruptException(IOException的子类)异常;

  • 如果线程在Selector上被阻塞,select方法将立即返回;

  • 如果非以上情况,将直接标记 interrupt 状态;

注意:interrupt 操作不会打断所有阻塞,只有上述阻塞情况才在jvm的打断范围内,如处于锁阻塞的 线程,不会受 interrupt 中断;
阻塞情况下中断,抛出异常后线程恢复非中断状态,即 interrupted = false

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 ThreadTest {        
public static void main(String[] args) throws InterruptedException {        
Thread t = new Thread(new Task("mytask"));        
t.start();        
t.interrupt();  
}
   static class Task implements Runnable{        
String name;                
public Task(String name) {            
this.name = name;      
}                
@Override        
public void run() {            
try {                
Thread.sleep(1000);          
} catch (InterruptedException e) {
System.out.println("thread has been interrupt!");
}            
System.out.println("isInterrupted: " +
Thread.currentThread().isInterrupted());            
System.out.println("task " + name + " is over");      
}  
}
}

输出:

1
2
3
thread has been interrupt! 
isInterrupted: false
task 1 is over

调用Thread.interrupted() 方法后线程恢复非中断状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class ThreadTest {        
public static void main(String[] args) throws InterruptedException {        
Thread t = new Thread(new Task("mytask"));        
t.start();        
t.interrupt();  
}
   static class Task implements Runnable{        
String name;                
public Task(String name) {            
this.name = name;      
}                
@Override        
public void run() {            
System.out.println("first :" + Thread.interrupted());
System.out.println("second:" + Thread.interrupted());
System.out.println("task " + name + " is over");      
}  
}
}

输出结果:

1
2
3
first :true 
second:false
task 1 is o
1.1.2.4 线程的状态

Java线程可能的状态:

1.2 多线程

线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

1.2.1 并发和并行

  • 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
  • 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
  • 在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群

1.2.2 多线程好处

提高cpu的利用率

单线程:

1
2
3
4
5
6
5 seconds reading file A  
2 seconds processing file A
5 seconds reading file B
2 seconds processing file B
----------------------
14 seconds total

多线程:

1
2
3
4
5
6
5 seconds reading file A  
5 seconds reading file B +
2 seconds processing file A
2 seconds processing file B
----------------------
12 seconds total

一般来说,在等待磁盘IO,网络IO或者等待用户输入时,CPU可以同时去处理其他任务。

更高效的响应

多线程技术使程序的响应速度更快 ,因为用户界面可以在进行其它工作的同时一直处于活动状态,不会 造成无法响应的现象。

公平使用CPU资源

当前没有进行处理的任务,可以将处理器时间让给其它任务;占用大量处理时间的任务,也可以定期将 处理器时间让给其它任务;通过对CPU时间的划分,使得CPU时间片可以在多个线程之间切换,避免需要 长时间处理的线程独占CPU,导致其它线程长时间等待。

1.2.3 多线程的代价

*更复杂的设计 *

共享数据的读取,数据的安全性,线程之间的交互,线程的同步等;

上下文环境切换

线程切换,cpu需要保存本地数据、程序指针等内容;

更多的资源消耗

每个线程都需要内存维护自己的本地栈信息,操作系统也需要资源对线程进行管理维护;

文章作者: Frosro
文章链接: https://frosro.github.io/2021/01/07/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%EF%BC%88%E4%B8%80%EF%BC%89/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 BETTER LATE THAN NEVER
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论