Java并发编程(一)——并发的基本概念

基本概念

Linux Torvalds认为,并行计算只能在图像处理和服务端编程两个领域使用。

并行计算的相关概念

同步(Synchronous)和异步(Asynchronous)

同步和异步通常用来形容一次方法的调用,同步方法一旦调用,调用者必须等到方法调用返回后,才能继续后续的行为。异步方法更像一个消息传递,一旦开始,方法调用就会立即返回,调用者可以继续后续的操作。
在这里插入图片描述

并发(Concurrent)和并行(Parallelism)

并发和并行都可以表示多个任务一起执行,但侧重点不同。并发侧重于多个任务交替执行,而多个任务之间有可能还是串行的。并行是真正意义上的“同时进行”,出现在拥有多个CPU的系统(例如多核CPU)。
在这里插入图片描述

临界区

用来表示一种公共资源或者说共享数据,可以被多个线程使用。但是每一次只能有一个线程使用他,一旦临界资源被占用。其他线程想使用这个资源就必须等待。

阻塞(Blocking)和非阻塞(Non-Blocking)

阻塞和非阻塞通常用来形容多线程间的相互影响。阻塞,一个线程占用了临界区资源,其他线程必须在临界区中等待。非阻塞,强调没有一个线程可以妨碍其他线程的执行。

死锁(Deadlock)、饥饿(Starvation)、活锁(Livelock)

死锁、饥饿和活锁都属于线程的活跃性问题。
死锁:两个或两个以上的线程在执行过程中,由于竞争资源或彼此通信而造成的一种阻塞现象,若无外力作用,他们都无法推进下去。
饥饿:指一个线程尽管能继续执行,但被调度器无限期地忽视,而不能被调用的情况。
活锁:任务执行没有被阻塞,由于某些条件没有满足,导致一直重复着尝试-失败-尝试-失败的过程。例如行人迎面来回“谦让”。

并发级别

阻塞的:阻塞、无饥饿。
非阻塞的:无障碍,无锁、无等待。

阻塞(Blocking)

如synchronized或重入锁,会产生阻塞的线程。

无饥饿(Starvation-Free)

如果线程之间是由优先级的,低优先级的线程可能会产生饥饿。也就是说对于同一个资源的调配是不公平的。下图表示了公平锁和非公平锁的两种情况。
在这里插入图片描述

无障碍(Obstruction-Free)

不加锁,都进入临界区,若数据不一致则进行回滚。

可使用“一致性标记”,操作之前与操作之后分别读取,若不一致则回滚。

无锁(Lock-Free)

无障碍的基础上,保证有一个线程能在有限步内完成任务离开临界区。通常包含了一个无穷循环。

while(!atomicVar.compareAndSet(LocalVar, localVar + 1)){
	localVar = atomicVar.get();
}

无等待(Wait-Free)

要求所有的线程必须在有限步之内完成。

例如:RCU(Read-Copy
Update,读-复制更新)。读,无限制。写,先复制成副本,在副本上做修改,最后选择时机进行替换。关键在于怎么判断所有读者已经完成访问。

关于并行的两个重要定律

Amdahl定律

定义了串行系统并行化之后的加速比的计算公式和理论上限。

  加 速 比 = 优 化 前 的 系 统 耗 时 优 化 后 的 系 统 耗 时 \ 加速比 = \frac{优化前的系统耗时}{优化后的系统耗时}  =

  T n = T 1 ( F + 1 n ( 1 − F ) ) \ T_{n} = T_{1}(F + \frac{1}{n}(1 - F))  Tn=T1(F+n1(1F))

  加 速 比 = T 1 T n = T 1 T 1 ( F + 1 n ( 1 − F ) ) = 1 F + 1 n ( 1 − F ) \ 加速比=\frac{T_{1}}{T_{n}}=\frac{T_{1}}{T_{1}(F + \frac{1}{n}(1 - F))}=\frac{1}{F + \frac{1}{n}(1 - F)}  =TnT1=T1(F+n1(1F))T1=F+n1(1F)1
其中,n表示处理器个数,F表示串行比例,1-F表示并行比例。优化后的效果取决于cpu的数量和串行化比例。

Gustafson定律

执行时间:a + b(a:串行时间,b:并行时间)
总执行时间:a + nb(n:处理器个数)
  加 速 比 = a + n b a + b \ 加速比=\frac{a+nb}{a+b}  =a+ba+nb
定义:   F = a a + b \ F=\frac{a}{a+b}  F=a+ba为串行比例
  加 速 比 = a + n b a + b = a a + b + n b a + b = F + n ( a + b − a a + b ) = F + n ( 1 − F ) = n − F ( n − 1 ) \ 加速比=\frac{a+nb}{a+b}=\frac{a}{a+b}+\frac{nb}{a+b}=F+n(\frac{a+b-a}{a+b})=F+n(1-F)=n-F(n-1)  =a+ba+nb=a+ba+a+bnb=F+n(a+ba+ba)=F+n(1F)=nF(n1)

JMM(Java内存模型)

JMM的关键技术点都是围绕着多线程的原子性可见性有序性来建立的。

原子性(Atomicity)

原子性指一个操作时不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
例如两个线程对int i进行赋值,线程A对它赋值为1,线程B对它赋值为-1。那么i的值只可能是1或者-1。但如果是32位虚拟机对long类型做赋值,则不是一个原子性操作。

可见性(Visibility)

可见性指的是当前一个线程修改了某一个共享变量的值时,其他线程能否立刻知道这个修改。缓存优化硬件优化指令重排编译器优化都可能导致可见性问题。
在这里插入图片描述

有序性(Ordering)

指令重排使指令顺序与原指令顺序不一致,目的是减少流水线的停顿,提高性能。

例子

class OrderExample{
	int a = 0;
	boolean flag = false;
	public void write(){
		a = 1;
		flag = true;
	}
	public void reader(){
		if (flag){
			int i = a + 1;
			......
		}
	}
}	

在这里插入图片描述

一条指令可以简单分为以下步骤:

  1. 取指IF。
  2. 译码和取寄存器操作数ID。
  3. 执行或者有效地址计算EX。
  4. 存储器访问MEM。
  5. 写回WB。

在这里插入图片描述

指令重排的例子

在这里插入图片描述
处理 a=b+c;d=e+f;

在这里插入图片描述
可以进行指令重排
在这里插入图片描述
指令重排之后
在这里插入图片描述

Happen-Before原则

哪些指令不能重排:

  1. 程序顺序原则:一个线程内保证语义的串行性。
  2. volatile规则:volatile变量的写先于读发生,保证了volatile的可见性。
  3. 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前。
  4. 传递性:A先于B,B先于C,则A必然先于C。
  5. 线程的start()方法先于每一个操作。
  6. 线程的所有操作先于线程的终结(Thread.join())。
  7. 线程的中断(interrupt())先于被中断线程的代码。
  8. 对象的构造函数的执行,先于finalize()方法

Java并行程序基础

线程的生命周期

线程是轻量级的进程,是程序执行的最小单位,进程是线程的容器。

在这里插入图片描述

线程的基本操作

新建

Thread t1 = new Thread();
t1.start(); // 调用Thread.run()

注意:不要使用 t1.run() 启动线程,只会在当前线程中串行执行。

  1. 重写run() 方法
Thread t1 = new Thread(){
	@Override
	public void run(){
		...
	}
};
t1.start();
  1. 可通过extends Thread,再重写run() 方法。但Java是单继承,继承本身也是一种宝贵的资源。
  2. 实现Runnable接口
public interface Runnable{
	public abstract void run();
}	
// Thread中的构造方法
public Thread(Runnable target)
public void run(){
	if (target != null){
		target.run();
	}
}		
public class CreateThread implements Runnable{
	public static void main(String[] args){
		Thread t1 = new Thread(new CreateThread());
		t1.start();
	}
	@Override
	public void run(){
		逻辑代码...
	}
}			

终止

一般情况下线程执行完后会自行结束,但服务端线程的执行本体可能就是个大循环while(true)。
Thread.stop()方法会直接终止线程,立即释放线程所持有的锁,可能会导致数据不一致的问题。
例如:修改User中的ID和Name。

用户1 ID=1 Name=小明
用户2 ID=2 Name=小王

在这里插入图片描述
正确停止的做法:

public class ChangeObjectThread implements Runnable{
	volatile boolean stopme = false;
	public void stopMe(){
		stopme = true;
	}
	@Override
	public void run(){
		while(true){
			if(stopme){
				break; // 自行选择合适的时机退出
			}
			synchronized(锁对象){
				逻辑代码...
			}
			Thread.yield();
		}
	}				
}	

中断

在Java中,停止一个线程的主要方法就是中断。线程中断并不会导致线程立即退出,而是给线程发送一个通知,告知目标线程。接到通知后如何处理,完全由目标线程决定。

public void Thread.interrupt() // 中断,设置中断标志位
public boolean Thread.isInterrupted() // 判断是否中断
public static boolean Thread.interrupted() // 判断是否中断,并清除中断标志位
Thread t1 = new Thread(){
	@Override
	public void run(){
		while(true){
			if(Thread.currentThread.isInterrupted()){ // 读取标志位
				break;
			}
			Thread.yield();
		}
	}
};
t1.start();
Thread.sleep(2000);
t1.interrupt(); // 中断,修改标志位				

上述代码中,与stopme() 标记的手法非常相似,但中断的功能更强。例如循环体中出现了wait() 方法或者sleep() 方法这样的操作。

Thread t1 = new Thread(){
	@Override
	public void run(){
		while(true){
			if(Thread.currentThread().isInterrupted()){
				System.out.println("Interrupted!");
				break;
			}
			try{
				/*
				Thread.sleep() 方法会让当前线程休眠若干时间,他会抛出一个InterruptedException中断异常。
				当线程在sleep时,若被中断,这个异常就会产生。
				此时,他会清除中断标志位。
				*/
				Thread.sleep(2000);
			}catch(InterruptedException e){
				System.out.println("Interrupted when sleep");
				// 设置中断标志位
				Theread.currentThread().interrupt();
			}
			Thread.yield();
		}
	}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();		

等待(wait)和通知(notify)

等待和通知是为了支持多线程之间的协作。
wait() 和notify() 方法不在Thread中,而是在Object中,这意味着任何对象都可以调用这个方法。

public final void wait() throws InterruptedException
public final native void notify() 

工作过程:当一个object调用wait() 方法后,当前线程会进入这个object对象的等待队列。这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。当object调用notify() 方法后,会从这个等待队列上随机挑选一个线程唤醒。

在这里插入图片描述
注意:wait() 方法不能随便调用,无论是wait() 还是notify() 都需要首先获得目标对象的一个监视器。
在这里插入图片描述

public class SimpleWN{
	final static Object object = new Object();
	public static class T1 extends Thread{
		public void run(){
			synchronized(object){
				T1 Start!
				try{
					T1 Wait For Object...
					object.wait();
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				T1 End!
			}
		}
	}
	
	public static class T2 extends Thread{
		public void run(){
			T2 Start! Notify One Thread
			object.notify();
			T2 End!
			try{
				Thread.sleep(2000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args){
		Thread t1 = new T1();
		Thread t2 = new T2();
		t1.start();
		t2.start(); 	
	}
}	
					

注意:Object.wait() 方法和Thread.sleep() 方法都会让线程等待若干时间。除wait() 方法可以被唤醒之外,另一个主要的区别就是wait() 方法会释放对象的锁,而Thread.sleep() 方法不会释放任何资源。

挂起(suspend)和继续执行(resume)

这两个方法已经废弃,原因是suspend() 方法在暂停线程的同时,不会释放任何资源。

在这里插入图片描述
坏例子:

public class BadSuspend{
	public static Object u = new Object(); 
	static ChangeObjectThread tl = new ChangeobjectThread("T1");
	static ChangeObjectThread t2 = new ChangeObjectThread("T2");
	public static class ChangeObjectThread extends Thread{
		public ChangeObjectThread(String natne){
			super.setName(name);
		}
		@Override 
		public void run() {	
			synchronized (u) {
				System.out.println( ” in ” +getName()); 
				Thread.currentThread().suspend(); 
			}
		}
	}
	public static void main(String[] args) throws InterruptedException {
		tl.start(); 
		Thread.sleep(lOO); 
		t2.start(); 
		tl.resume(); 
		t2.resume(); 
		tl.join(); 
		t2.join();
	}
}		 	

利用wait() 和notify() 方法改造

public class GoodSuspend{
	public static Object u = new Object();
	public static class ChangeObjectThread extends Thread {
		volatile boolean susperidme = false;
		public void suspendMe() {
			suspendme = true;
		}
		public void resumeMe() {
			suspendme=false; 
			synchronized (this} {
				notify(};
			}
		}
		@Override 
		public void run() {	
			while (true) {
				synchronized (this) {
					while (suspendme){
						try{
							wait();
						}catch(InterruptedException e){
							e.printStackTrace();
						}
					}
					synchronized(u){
						System.out.println("in ChangeObjectThread");
					}
					Thread.yield();
				}
			}
		}
		public static class ReadObjectThread extends Thread{
			@Override
			public void run(){
				while(true){
					synchronized(u){
						System.out.println("in ReadObjectThread");
					}
					Thread.yield();
				}
			}
		}
		public static void main(String[] args) throws InterruptedException{
			ChangeObjectThread t1 = new ChangeObjectThread();
			ReadObjectThread t2 = new ReadObjectThread();
			t1.start();
			t2.start();
			Thread.sleep(1000);
			t1.suspemdMe();
			System.out.println("suspend t1 2 sec");
			Thread.sleep(2000);
			System.out.println("resume t1");
			t1.resumeMe();
		}
	}
}												
						

等待线程结束(join)和谦让(yield)

join
public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
public class JoinMain{
	public volatile static int i = 0;
	public static class AddThread implements Runnable{
		@Override
		public void run(){
			for(int i = 0;i < 10000000;i++);
		}
	}
	public static void main(String[] args)throws InterruptedException{
		AddThread at = new AddThread();
		at.start();
		at.join();
		System.out,println(i);	
	}
}			

join() 方法的本质是让调用线程wait() 方法在当前线程对象实力上

while(isAlive()){
	wait(0);
}	

不要在应用程序中,在Thread对象实例上使用类似wait() 方法或者notify() 方法,可能会影响系统API的工作。

yield
public static native void yield()

当前线程让出CPU

volatile

释义:易变的、不稳定的
提醒虚拟机这个变量可能被某些线程改动。确保本条指令不会因为编译器的优化而省略。
特性:

  1. 保证可见性。
  2. 禁止指令重排。
  3. 只能保证单次读/写的原子性。不能保证i++这种操作的原子性。

线程组

public class ThreadGroupName implements Runnable {
    public static void main(String[] args) {
        ThreadGroup tg = new ThreadGroup("PrintGroup");
        Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");
        Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");
        t1.start();
        t2.start();
        System.out.println(tg.activeCount());
        tg.list();
    }

    @Override
    public void run() {
        String groupAndName = Thread.currentThread().getThreadGroup().getName() + "-" + Thread.currentThread().getName();
        while (true){
            System.out.println("I am "+groupAndName);
            try{
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

守护线程

public class DaemonDemo {
    public static class DaemonT extends Thread{
        @Override
        public void run() {
            while (true){
                System.out.println("I am alive");
                try{
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException{
        Thread t = new DaemonT();
        t.setDaemon(true);
        t.start();
        Thread.sleep(2000);
    }
}

线程优先级

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

Thread high = new HighPriority();
Thread low = new LowPriority();
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
low.start();
high.start();

线程安全与synchronized

synchronized可完全替换volatile,开销大于volatile,用法如下:

  1. 指定加锁对象:对给定对象加锁,进入同步代码前需要获得给定对象的锁。
public class AccountingSync implements Runnable {
    static AccountingSync instance = new AccountingSync();
    static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            synchronized (instance) {
                i++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}
  1. 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前需要获得当前实例的锁。
public class AccountingSync2 implements Runnable {
    static AccountingSync2 instance = new AccountingSync2();
    static int i = 0;


    public synchronized void increase(){
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

错误的写法:

public class AccountingSync implements Runnable {
    static AccountingSync instance = new AccountingSync();
    static int i = 0;


    public synchronized void increase(){
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new AccountingSync());
        Thread t2 = new Thread(new AccountingSync());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

t1,t2 所包含的Runnable实例不是同一个。

  1. 对类加锁
    把上例中的increase() 方法修改为静态方法。
public class AccountingSync implements Runnable {
    static AccountingSync instance = new AccountingSync();
    static int i = 0;


    public static synchronized void increase(){
        i++;
    }

    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new AccountingSync());
        Thread t2 = new Thread(new AccountingSync());
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

Reference

参考书籍:
《实战Java高并发程序设计》
参考博客:
https://www.cnblogs.com/kukri/p/9595263.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值