栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java多线程实战精讲-带你一次搞明白Java多线程高并发 学习笔记(自用)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java多线程实战精讲-带你一次搞明白Java多线程高并发 学习笔记(自用)

目录
      • 第 1 章 线程概述
      • 第 2 章 线程安全问题
      • 第 3 章 线程同步
      • 第 4 章 线程间的通信

第 1 章 线程概述

1.1 线程相关概念

进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行资源分配与调度的基本单位。

线程:线程(thread)是进程的一个执行单元。

一个线程就是进程中一个单一顺序的控制流, 进程的一个执行分支。
进程是线程的容器,一个进程至少有一个线程.一个进程中也可以有多个线程。
在操作系统中是以进程为单位分配资源,如虚拟存储空间,文件描述符等. 每个线程都有各自的线程栈,自己的寄存器环境,自己的线程本地存储。


并发可以提高以事物的处理效率, 即一段时间内可以处理或者完成更多的事情.
并行是一种更为严格,理想的并发
从硬件角度来说, 如果单核 CPU,一个处理器一次只能执行一个线程的情况下,处理器可以使用时间片轮转技术 ,可以让 CPU 快速的在各个线程之间进行切换, 对于用来来说,感觉是三个线程在同时执行. 如果是多核心CPU,可以为不同的线程分配不同的CPU 内核.

1.2 线程的创建与启动

在Java 中,创建一个线程就是创建一个 Thread 类(子类)的对象(实例).
Thread 类有两个常用 的构造方法:Thread()与 Thread(Runnable).对应的创建线程的两种方式:
(1)定义Thread 类的子类
(2)定义一个Runnable 接口的实现类
这两种创建线程的方式没有本质的区别.

调用线程的start()方法来启动线程, 启动线程的实质就是请求JVM 运行相应的线程,这个线程具体在什么时候运行由线程调度器(Scheduler)决定。

注意:
start()方法调用结束并不意味着子线程开始运行 新开启的线程会执行 run()方法
如果开启了多个线程,start()调用的顺序并不一定就是线程启动的顺序
多线程运行结果与代码执行顺序或调用顺序无关

当线程类已经有父类了,就不能用继承 Thread 类的形式创建线程,可以使用实现 Runnable

定义类实现 Runnable 接口

public class MyRunnable implements Runnable {
//2)重写 Runnable 接口中的抽象方法 run(), run()方法就是子线程要执行的代码@Override
	public void run() {
		for(int i = 1;	i<=1000; i++){
			System.out.println( "sub thread --> " + i);
		}
	}
}

测试实现 Runnable 接口的形式创建线程

public class Test {
	public static void main(String[] args) {
		//3)创建 Runnable 接口的实现类对象
		MyRunnable	runnable = new MyRunnable();
		//4)创建线程对象
		Thread thread = new Thread(runnable);
		//5)开启线程thread.start();
		//当前是 main 线程
		for(int i = 1;	i<=1000; i++){
			System.out.println( "main==> " + i);
		}
	}
}

1.3 线程的常用方法

1.3.1 currentThread()方法

Thread.currentThread()方法可以获得当前线程
Java 中的任何一段代码都是执行在某个线程当中的. 执行当前代码的线程就是当前线程.
同一段代码可能被不同的线程执行, 因此当前线程是相对的,Thread.currentThread()方法的返回值是在代码实际运行时候的线程对象

1.3.2 setName() / getName()

thread.setName(线程名称), 设置线程名称
thread.getName()返回线程名称
通过设置线程名称,有助于程序调试,提高程序的可读性, 建议为每个线程都设置一个能够体现线程功能的名称

1.3.3 isAlive()

thread.isAlive()判断当前线程是否处于活动状态
活动状态就是线程已启动并且尚未终止

1.3.4 sleep()

Thread.sleep(millis); 让当前线程休眠指定的毫秒数

1.3.5 getId()

thread.getId() 可以获得线程的唯一标识
注意:某个编号的线程运行结束后,该编号可能被后续创建的线程使用

1.3.6 yield()
Thread.yield()方法的作用是放弃当前的CPU 资源

1.3.7 setPriority()
thread.setPriority( num ); 设置线程的优先级
java 线程的优先级取值范围是 1 ~ 10 , 如果超出这个范围会抛出异常IllegalArgumentException.
在操作系统中,优先级较高的线程获得CPU 的资源越多
线程优先级本质上是只是给线程调度器一个提示信息,以便于调度器决定先调度哪些线程. 注意不能保证优先级高的线程先运行.
Java 优先级设置不当或者滥用可能会导致某些线程永远无法得到运行,即产生了线程饥饿.
线程的优先级并不是设置的越高越好,一般情况下使用普通的优先级即可,在开发时不必设置线程的优先级。默认优先级为5
线程的优先级具有继承性, 在A 线程中创建了B 线程,则B 线程的优先级与A 线程是一样的.

1.3.8 interrupt()
中断线程.
注意调用 interrupt()方法仅仅是在当前线程打一个停止标志,并不是真正的停止线程


1.3.9 setDaemon()
Java 中的线程分为用户线程与守护线程
守护线程是为其他线程提供服务的线程,如垃圾回收器(GC)就是一个典型的守护线程
守护线程不能单独运行, 当JVM 中没有其他用户线程,只有守护线程时,守护线程会自动销毁, JVM 会退出
设置守护线程的代码应该在线程启动前,不然设置无效

1.4 线程的生命周期

线程状态图:

1.5 多线程编程的优势与存在的风险

第 2 章 线程安全问题


2.1 原子性

2.2 可见性

2.3 有序性

2.3.1 重排序


2.3.2 指令重排序

2.3.3 存储子系统重排序


2.3.4 貌似串行语义


2.3.4 保证内存访问的顺序性
可以使用volatile 关键字, synchronized 关键字实现有序性

2.4 Java 内存模型

刷新处理器缓存是将主内存的数据读取到缓存区中;冲刷处理器缓存指的是将缓存的数据刷新到主内存中。

第 3 章 线程同步

3.1 线程同步机制简介

3.2 锁概述



3.2.1 锁的作用

3.2.2 锁相关的概念


3.3 内部锁:synchronized 关键字

3.3.1 synchronized 同步代码块

this 锁对象:

使用一个常量对象作为锁对象:

使用一个常量对象作为锁对象,不同方法中(例如普通方法和静态方法)的同步代码块也可以同步。

3.3.2 同步方法

package com.wkcto.intrinsiclock;

public class Test05 {
	public static void main(String[] args) {
		//先创建 Test01 对象,通过对象名调用 mm()方法
		Test05 obj = new Test05();
		//一个线程调用 mm()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.mm(); //使用的锁对象this就是obj对象
			}
		}).start();
		//另一个线程调用 mm22()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.mm22(); //使用的锁对象 this 也是 obj 对象, 可以同步
				// new Test05().mm22(); //使用的锁对象 this 是刚刚 new 创建的一个新对象,不是同一个锁对象不能同步
			}
		}).start();
	}
	//定义方法,打印 100 行字符串
	public void mm(){
		synchronized ( this ) { //经常使用this当前对象作为锁对 象
			for (int i = 1; i <= 100; i++) {
				System.out.println(Thread.currentThread().getName() + " --> " + i);
			}
		}
	}
	//使用 synchronized 修饰实例方法,同步实例方法, 默认 this 作为 锁对象
	public synchronized void mm22(){
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + " --> " + i);
		}
	}
}
package com.wkcto.intrinsiclock;

public class Test06 {
	public static void main(String[] args) {
		//先创建 Test01 对象,通过对象名调用 mm()方法
		Test06 obj = new Test06();
		//一个线程调用 mm()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.m1(); //使用的锁对象是 Test06.class
			}
		}).start();
		//另一个线程调用 sm2()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.sm2(); ///使用的锁对象是 Test06.class
			}
		}).start();
	}
	//定义方法,打印 100 行字符串
	public void m1(){
		//使用当前类的运行时类对象作为锁对象,可以简单的理解 为把 Test06 类的字节码文件作为锁对象
		synchronized ( Test06.class ) {
			for (int i = 1; i <= 100; i++) {
				System.out.println(Thread.currentThread().getName() + " --> " + i);
			}
		}
	}
	//使用 synchronized 修饰静态方法,同步静态方法, 默认运行时类 Test06.class 作为锁对象
	public synchronized void sm2(){
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + " --> " + i);
		}
	}
}
package com.wkcto.intrinsiclock;

public class Test07 {
	public static void main(String[] args) {
		//先创建 Test01 对象,通过对象名调用 mm()方法
		Test07 obj = new Test07();
		//一个线程调用 mm()方法
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.doLongTimetask();
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				obj.doLongTimetask();
			}
		}).start();
	}
	//同步方法, 执行效率低
	public synchronized void doLongTimetask(){
		try {
			System.out.println("Task Begin");
			Thread.sleep(3000); //模拟任务需要准备 3 秒 钟
			System.out.println("开始同步");
			for(int i = 1; i <= 100; i++){
				System.out.println(Thread.currentThread().getName() + "-->" + i);
			}
			System.out.println("Task end");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	//同步代码块,锁的粒度细, 执行效率高
	public void doLongTimetask2(){
		try {
			System.out.println("Task Begin");
			Thread.sleep(3000); //模拟任务需要准备3秒钟
			synchronized (this){
				System.out.println("开始同步");
				for(int i = 1; i <= 100; i++){
					System.out.println(Thread.currentThread().getName() + "-->" + i);
				}
			}
			System.out.println("Task end");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

3.3.3 脏读
脏读:出现读取属性值出现了一些意外, 读取的是中间值,而不是修改之后的值。
出现脏读的原因是 对共享数据的修改 与对共享数据的读取不同步。
解决方法:不仅对修改数据的代码块进行同步,还要对读取数据的代码块同步

3.3.4 线程出现异常会自动释放锁
同步过程中线程出现异常, 会自动释放锁对象,其它等待线程可以获得锁继续执行。

3.3.5 死锁
在多线程程序中,同步时可能需要使用多个锁,如果获得锁的顺序不一致,可能会导致死锁。
如何避免死锁?当需要获得多个锁时,所有线程获得锁的顺序保持一致即可。

3.4 轻量级同步机制:volatile 关键字

3.4.1 volatile 的作用
volatile 关键的作用使变量在多个线程之间可见.
volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取

volatile 与synchronized 比较

volatile就是只有对内存进行冲刷的作用,而synchronized不仅有内存冲刷,还加上了锁来进行串行。

3.4.2 volatile 非原子特性
volatile 关键字增加了实例变量在多个 线程之间的可见性,但是不具备原子性。

package com.wkcto.volatilekw;

public class Test03 {
	public static void main(String[] args) {
		//在 main 线程中创建 10 个子线程
		for (int i = 0; i < 100; i++) {
			new MyThread().start();
		}
	}
	static class MyThread extends Thread{
		//volatile 关键仅仅是表示所有线程从主内存读取 count 变量的值
		public static int count;	//加了static表示count是类锁
		
		public synchronized static void addCount(){
			for (int i = 0; i < 1000; i++) {
				//count++不是原子操作
				count++;
			}
			System.out.println(Thread.currentThread().getName() + " count=" + count);
		}
		@Override
		public void run() {
			addCount();
		}
	}
}

3.4.3 常用原子类进行自增自减操作
我们知道i++操作不是原子操作, 除了使用Synchronized 进行同步外,也可以使用 AtomicInteger/AtomicLong 原子类进行实现

package com.wkcto.volatilekw;
import java.util.concurrent.atomic.AtomicInteger;

public class Test04 {
	public static void main(String[] args) throws InterruptedException {
		//在 main 线程中创建 10 个子线程
		for (int i = 0; i < 1000; i++){
			new MyThread().start();
		}
		Thread.sleep(1000);
		System.out.println( MyThread.count.get());
	}
	static class MyThread extends Thread {
		//使用 AtomicInteger 对象
		private static AtomicInteger count = new AtomicInteger();
		public	static void addCount(){
			for (int i = 0; i < 10000; i++) {
				//自增的后缀形式
				count.getAndIncrement();
			}
			System.out.println(Thread.currentThread().getName() + " count=" + count.get());
		}
		@Override
		public void run() {
			addCount();
		}
	}
}

3.5 CAS


使用CAS 实现线程安全的计数器

package com.wkcto.cas;

public class CASTest {
	public static void main(String[] args) {
		CASCounter casCounter = new CASCounter();
		for (int i = 0; i < 100000; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					System.out.println(casCounter.incrementAndGet());
				}
			}).start();
		}
	}
}
class CASCounter{
	//使用 volatile 修饰 value 值,使线程可见
	volatile private long value;
	public long getValue() {
		return value;
	}
	//定义 comare and swap 方法
	private boolean compareAndSwap(long expectedValue, long newValue){
		//如果当前value 的值与期望的expectedVAlue 值一样,就把当前的Value 字段替换为newValue 值
		synchronized (this){
			if ( value == expectedValue){
				value = newValue; return true;
			}else {
				return false;
			}
		}
	}
	//定义自增的方法
	public long incrementAndGet(){
		long oldvalue;
		long newValue;
		do {
			oldvalue = value; newValue = oldvalue+1;
		}while (!compareAndSwap(oldvalue, newValue) );
		return newValue;
	}
}


3.6 原子变量类


3.6.2 AtomicIntegerArray
原子更新数组

package com.wkcto.atomics.atomicarray;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class Test {
	public static void main(String[] args) {
		//1)创建一个指定长度的原子数组
		AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
		System.out.println( atomicIntegerArray ); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
		//2)返回指定位置的元素
		System.out.println( atomicIntegerArray.get(0)); //0
		System.out.println( atomicIntegerArray.get(1)); //0
		//3)设置指定位置的元素
		atomicIntegerArray.set(0, 10); //在设置数组元素的新值时, 同时返回数组元素的旧值
		System.out.println( atomicIntegerArray.getAndSet(1, 11) ); //0
		System.out.println( atomicIntegerArray ); //[10, 11, 0, 0, 0, 0, 0, 0, 0, 0]
		//4)修改数组元素的值,把数组元素加上某个值
		System.out.println( atomicIntegerArray.addAndGet(0, 22) ); //32
		System.out.println( atomicIntegerArray.getAndAdd(1, 33)); //11
		System.out.println( atomicIntegerArray ); //[32, 44, 0, 0, 0, 0, 0, 0, 0, 0]
		//5)CAS 操作 //如果数组中索引值为 0 的元素的值是 32 , 就修改为 222
		System.out.println( atomicIntegerArray.compareAndSet(0, 32, 222)); //true
		System.out.println( atomicIntegerArray ); //[222, 44, 0, 0, 0, 0, 0, 0, 0, 0]
		System.out.println( atomicIntegerArray.compareAndSet(1, 11, 333)); //false
		System.out.println(atomicIntegerArray);
		//6)自增/自减
		System.out.println( atomicIntegerArray.incrementAndGet(0) ); //223, 相当于前缀
		System.out.println( atomicIntegerArray.getAndIncrement(1)); //44, 相当于后缀
		System.out.println( atomicIntegerArray ); //[223, 45, 0, 0, 0, 0, 0, 0, 0, 0]
		System.out.println( atomicIntegerArray.decrementAndGet(2)); //-1
		System.out.println( atomicIntegerArray); //[223, 45, -1, 0, 0, 0, 0, 0, 0, 0]
		System.out.println( atomicIntegerArray.getAndDecrement(3)); //0
		System.out.println( atomicIntegerArray ); //[223, 45, -1, -1, 0, 0, 0, 0, 0, 0] } }

3.6.3 AtomicIntegerFieldUpdater

User.java

package com.wkcto.atomics.atominintegerfiled;

public class User {
	int id;
	volatile int age;
	public User(int id, int age) {
		this.id = id;
		this.age = age;
	}
	@Override
	public String toString() {
		return "User{" + "id=" + id +", age=" + age + '}';
	}
}

SubThread.java

package com.wkcto.atomics.atominintegerfiled;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
//线程类
public class SubThread extends Thread {
	private User user; //要更新的 User 对象
	//创建 AtomicIntegerFieldUpdater 更新器
	private AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
	public SubThread(User user) {
		this.user = user;
	}
	@Override public void run() {
		//在子线程中对 user 对象的 age 字段自增 10 次
		for (int i = 0; i < 10; i++) {
			System.out.println( updater.getAndIncrement(user));
		}
	}
}

Test.java

package com.wkcto.atomics.atominintegerfiled;
public class Test {
	public static void main(String[] args) {
		User user = new User(1234, 10);
		//开启 10 个线程
		for (int i = 0; i < 10; i++) {
			new SubThread(user).start();
		}
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println( user );
	}
}

3.6.4 AtomicReference
可以原子读写一个对象

package com.wkcto.atomics.atomicreference;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;

public class Test01 {
	//创建一个 AtomicReference 对象
	static AtomicReference atomicReference = new AtomicReference<>("abc");
	public static void main(String[] args) throws InterruptedException {
		//创建 100 个线程修改字符串
		for (int i = 0; i < 100; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(new Random().nextInt(20));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (atomicReference.compareAndSet("abc","def")){
						System.out.println(Thread.currentThread().getName() + "把字符串 abc 更改为 def");
					}
				}
			}).start();
		}
		//再创建 100 个线程
		for (int i = 0; i < 100; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						Thread.sleep(new Random().nextInt(20));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					if (atomicReference.compareAndSet("def","abc")){
						System.out.println(Thread.currentThread().getName() + "把字符串 还原为 abc");
					}
				}
			}).start();
		}
		Thread.sleep(1000);
		System.out.println(atomicReference.get());
	}
}

AtomicReference 可能会出现 CAS 的 ABA 问题。

AtomicStampedReference 原子类解决 CAS 中的 ABA 问题:
AtomicStampedReference类包含了一个有用的 compareAndSet()方法, compareAndSet()方法将一个期望的引用与存储在AtomicStampedReference实例中的值比较,如果引用与数据戳都相等(equals()或者 ==),那么 AtomicStampedReference实例将被设置为新的引用,compareAndSet()如果设置成功将返回ture否则返回false。下面是AtomicStampedReference 的compareAndSet()例子:

String initialRef   = "initial value referenced";
int    initialStamp = 0;
 
AtomicStampedReference atomicStringReference =
    new AtomicStampedReference(
        initialRef, initialStamp
    );
 
String newRef   = "new value referenced";
int    newStamp = initialStamp + 1;
 
boolean exchanged = atomicStringReference
    .compareAndSet(initialRef, newRef, initialStamp, newStamp);
System.out.println("exchanged: " + exchanged);  //true
 
exchanged = atomicStringReference
    .compareAndSet(initialRef, "new string", newStamp, newStamp + 1);
System.out.println("exchanged: " + exchanged);  //false
 
exchanged = atomicStringReference
    .compareAndSet(newRef, "new string", initialStamp, newStamp + 1);
System.out.println("exchanged: " + exchanged);  //false
 
exchanged = atomicStringReference
    .compareAndSet(newRef, "new string", newStamp, newStamp + 1);
System.out.println("exchanged: " + exchanged);  //true

例子首先创建了 AtomicStampedReference,然后通过compareAndSet()设置引用和数据戳,第二次在调用 compareAndSet()没有成功,因为第一次的 initialRef 与存储值一样,这时内部存储的是newRef,所以在调用compareAndSet()失败。

第二段,首先initialStamp 与期望值newStamp 不一样更新失败,此时存储的为newStamp ,再次调用compareAndSet()则成功。

package com.wkcto.atomics.atomicreference;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

public class Test03 {
	//定义 AtomicStampedReference 引用操作"abc"字符串,指定初始化版本号为 0
	private static AtomicStampedReference stampedReference = new AtomicStampedReference<>("abc", 0);
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Runnable() {
			@Override public void run() {
				stampedReference.compareAndSet("abc", "def", stampedReference.getStamp(), stampedReference.getStamp()+1); 
				System.out.println(Thread.currentThread().getName() + "--" +stampedReference.getReference()); 
				stampedReference.compareAndSet("def", "abc", stampedReference.getStamp(), stampedReference.getStamp()+1);
			}
		});
		Thread t2 = new Thread(new Runnable() {
			@Override public void run() {
				int stamp = stampedReference.getStamp(); //获得版本号 如果这条语句在这里 对应结果2
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//int stamp = stampedReference.getStamp(); //获得版本号 如果这条语句在这里 对应结果1
				System.out.println( stampedReference.compareAndSet("abc", "ggg", stamp, stamp+1));
			}
		});
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println( stampedReference.getReference() );
	}
}

结果1:

结果2:

第 4 章 线程间的通信
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/644881.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号