「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战」
线程状态及基本操作
1.新建线程
- 通过继承Thread类,重写run方法
- 通过实现Runnable接口
- 通过实现callable接口
//继承自Thread
Thread thread = new Thread(){
@Override
public void run() {
Log.e(TAG, "Thread start");
super.run();
}
};
thread.start();
//实现Runnable接口
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
Log.e(TAG, "Thread1 start");
}
});
thread1.start();
//实现Callable接口
ExecutorService service = Executors.newCachedThreadPool();
Future<String> future = service.submit(new Callable<String>() {
@Override
public String call() throws Exception {
Log.e(TAG, "Callable start");
return "finish";
}
});
try {
String result = future.get();
Log.e(TAG, "Callable result is " + result);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
2.线程状态切换
状态名 | 说明 |
---|---|
NEW | 初始状态,线程被构建,但是还没有调用start方法 |
RUNNABLE | 运行状态,Java线程将操作系统中的就绪和运行两种状态统称运行中 |
BLOCKED | 阻塞状态,表示线程阻塞于锁 |
WAITING | 等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一下特定动作(通知或中断) |
TIME_WAITING | 超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的 |
TERMINATED | 终止状态,表示当前线程已经执行完毕 |
面试中可能会问到:调用两次start之类的,了解线程状态很容易就能解答。
3.线程的基本操作
interrupt
join
如果一个线程实例A执行了threadB.join(),其含义是:当前线程A会等待threadB线程终止后threadA才会继续执行。
sleep
sleep() wait()区别
1.sleep是Thread的静态方法,而wait是Object的实例方法
2.wait方法必须在同步方法或者同步块中调用,也就是必须已经获得对象锁,而sleep没有这个限制。另外,wait方法会释放战友的对象锁,使得该线程进入等待池中,等待下一次获取资源,而sleep方法只是会让出CPU并不会释放掉对象锁
3.sleep方法在休眠时间达到后如果再次获取CPU时间片就会继续执行,而wait方法必须等待Object.notify/Object.notifyAll通知后,才会离开等待池。
yield
当前线程让出CPU,让其他线程优先执行,若没有满足的,则yield不起作用。
4.守护线程Daemon
synchronized
分类 | 具体分类 | 被锁的对象 | 伪代码 |
---|---|---|---|
方法 | 实例方法 | 类的实例对象 | public synchronized void method() {} |
静态方法 | 类对象 | public static synchronized void method() {} | |
代码块 | 实例对象 | 类的实例对象 | synchronized(this){} |
class对象 | 类对象 | synchronized(King.class){} | |
实例对象Object | 实例对象Object | Object lock = new Object();synchronized(lock){} |
注:如果锁的是类对象的话,尽管new多个实例对象,但是他们仍然属于是同一个类,依然会被锁住,即线程之间保证同步关系。
CAS操作(compare and swap)
CAS操作过程,包含3个值分别为:V内存地址存放的实际值,O预期的值(旧值),N更新的值。当V和O相同时,也就是旧值和内存中实际的值相同,表明该值没有修改过,即该旧值O就是目前最新的值,自然可以将新值赋给V,反之,V和O不相同,表明该值已经被其他线程改过了,所以不能将新值N赋给V,返回V即可。当多个线程使用CAS操作一个变量时,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试,当然也可以选择挂起线程。
volatile
1.将当前处理器缓存行的数据写回系统内存
2.这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效
3.当处理器发现本地缓存失效后,会从内存中重新读该变量数据,即可以获取当前最新值。
volatile通过该机制使每个线程都能获取到该变量的最新值。
final
变量
final成员变量
类变量在声明变量的时候直接赋初值或在静态代码块中给类变量赋值。
实例变量可以在声明变量的时候、非静态代码块中和构造器中给实例变量赋初值。
final局部变量
final局部变量由程序员进行显式的初始化,如果已经初始化,后面就不能再次进行修改,如果final变量未进行初始化,可以进行赋值,仅有一次赋值。
注:当final修饰基本类型变量时,不能对基本数据类型进行重新赋值。而对于引用类型变量而言,它仅仅是一个引用,final只保证这个引用类型引用的地址不会发生改变,即一直引用这个对象,但这个对象属性是可以改变的。
方法
父类的方法被final修饰,子类不能重写该方法
被final修饰的方法,可以被重载
类
当一个类被final修饰时,表明该类是不能被子类继承的。
多线程中的final
不懂
三大属性简介
原子性
一个操作是不可中断的,要么全部执行成功,要么全部执行失败,有着同生共死的感觉。
int a = 10; //原子操作
a++; //1.读取a的值 2.对a进行加一操作 3.将计算后的值再赋给a
int b = a;
a = a + 1;
Java内存模型中的原子操作:
lock:作用于主内存中的变量,把一个变量标识为一个线程独占的状态
unlock:作用于主内存的变量,把一个处于锁定状态的变量释放出来,后可被其他线程锁定
read:作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中,方便load操作
load:作用于工作内存中的变量,把read操作从主内存中得到的变量值放入工作内存中的变量副本
use:作用于工作内存中的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时 执行这个操作
assign:作用于工作内存中的变量,把一个从执行引擎接收到的值赋给工作内存的变量
store:作用于工作内存的变量,把工作内存中一个变量的值传送给主内存中以便随后的write操作使用
write:作用于主内存的变量,他把store操作从工作内存中得到的变量的值放入主内存的变量中
有序性
synchronized语义表示锁在同一时刻只能由一个线程进行获取,当锁被占用后,其他线程只能等待。因此,synchronized语义就要求线程在访问读写共享变量时只能“串行”执行,因此synchronized具有有序性。
在java内存模型中,为了性能优化,编译器和处理器会进行指令重排序;总结为:如果在本线程内观察,所有的操作都是有序的,如果在令一个线程观察,所有的操作都是无序的。
volatile包括禁止指令重排的语义,具有有序性
可见性
当一个线程修改了共享变量后,其他线程能立即得到这个修改。
synchronized获取锁是会从主内存中获取共享变量的最新值,释放锁时会将变量同步到主内存中。
volatile具有可见性
原子操作类
在java.util.concurrent下的atomic包中提供了一系列的操作简单、性能高效,并且保证线程安全的类去更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的乐观锁策略去实现原子更新数据,在java中则是使用CAS操作具体实现。
synchronized主要问题是:在存在线程竞争的情况下出现线程的阻塞和唤醒锁带来的性能问题,这是一种互斥同步;
而CAS并不是武断的将线程挂起,当CAS操作失败后会进行一定的尝试,而非进行耗时的挂起唤醒的操作,因此也叫作非阻塞同步,这是两者主要的区别。
原子更新基本类型
atomic提供的原子更新基本类型的工具类:
- AtomicInteger
- AtomicBoolean
- AtomicLong
以AtomicInteger为例总结常用的方法:
1.addAndGet(int data):原子方式将输入值与原值相加,并返回最后结果
2.incrementAndGet():以原子方式将实例中的原值+1,并返回最后结果
3.getAndSet(int newValue):将实例中的值更新为新值
4.getAndIncrement():将原值+1,并返回原值
/**
* Atomically increments by one the current value.
*
* @return the previous value
*/
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
主要通过Unsafe类提供的compareAndSwapInt、compareAndSwapLong等一系列CAS操作的方法进行实现。
原子更新数组类型
- AtomicIntegerArray
- AtomicLongArray
- AtomicReferenceArray
以AtomicIntegerArray来总结常用的方法:
1.addAndGet(int i, int delta)
2.getAndIncrement(int i):将数组中的索引为i的元素自增1
3.compareAndSet(int i, int expect, int update)
原子更新引用类型
AtomicReference:原子更新引用类型
AtomicReferenceFieldUpdater:原子更新引用类型里的字段
AtomicMarkableReference:原子更新带有标记位的引用类型
AtomicReference<User> userAtomicReference = new AtomicReference<>();
userAtomicReference.set(u1);
User user = userAtomicReference.getAndSet(u2);
Log.e(TAG, "age = " + user.getAge());
User user2 = userAtomicReference.get();
Log.e(TAG, "age = " + user2.getAge());
原子更新字段类型
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
- AtomicStampedReference
两个操作步骤:
1.原子更新字段类都是抽象类,智能通过静态方法newUpdater来创建一个更新器,并且设置要更新的类和属性
2.更新的类的属性必须使用public volatile修饰
AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
并发工具
倒计时器CountDownLatch
多线程写作完成业务功能时,有时候需要等待其他多个线程完成后,主线程才能继续往下执行业务功能,这种情况下,可以使用Thread类的join方法,让主线程等待被join的线程执行完成之后,主线程继续往下执行。使用线程间消息通信机制也可以完成。
其实java提供了雷士倒计时这样的工具类,可以实现这种场景。
CountDownLatch构造函数
public CountDownLatch(int count)
构造方法传入一个整数,之后调用CountDownLatch的countDown方法会对N减1,直到N==0的时候,当前调用await方法的线程会继续执行。
方法:
1.await():调用该方法的线程等到构造方法传入的N减到0的时候,才能继续往下执行
2.await(long timeout, TimeUnit unit)
3.countDown():
4.long getCount()
例子:
public class CountDownLatchTest {
private static final String TAG = "CountDownLatchTest";
private CountDownLatch refereeSignal = new CountDownLatch(1); //1裁判
private CountDownLatch playerSignal = new CountDownLatch(6); //6远动员
private CountDownLatch prepareSignal = new CountDownLatch(6); //6远动员
public CountDownLatchTest() {
ExecutorService service = Executors.newFixedThreadPool(6);
for (int i = 0; i < 6; i++) {
final int index = i;
service.submit(new Runnable() {
@Override
public void run() {
try {
Log.e(TAG, "运动员:" + index + "准备就绪");
prepareSignal.countDown();
refereeSignal.await();
Log.e(TAG, "运动员:" + index + "开始冲刺");
playerSignal.countDown();
Log.e(TAG, "运动员:" + index + "完成比赛");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
try {
prepareSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "裁判员发枪");
refereeSignal.countDown();
try {
playerSignal.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "运动员全部到底重点,比赛技术");
service.shutdown();
}
}
循环栅栏:CyclicBarrier
CyclicBarrier也是一种多线程并发控制的使用工具,和CountDownLatch一样具有等待技术的功能。
主要方法
//等到所有的线程都到达指定的临界点
public int await() throws InterruptedException, BrokenBarrierException;
//
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException;
//用于查询阻塞等待的线程是否被中断
public boolean isBroken();
//获取当前有多少个线程阻塞等待在临界点上
public int getNumberWaiting();
//构造函数,可以指定在线程达到临界点时执行什么操作
public CyclicBarrier(int parties, Runnable barrierAction)
例:
public class CyclicBarrierTest {
private static final String TAG = "CyclicBarrierTest";
CyclicBarrier cyclicBarrier = new CyclicBarrier(6, new Runnable() {
@Override
public void run() {
Log.e(TAG, "所有运动员入场了");
}
});
public CyclicBarrierTest() {
ExecutorService service = Executors.newFixedThreadPool(6);
for (int i = 0; i < 6; i++) {
service.submit(new Runnable() {
@Override
public void run() {
Log.e(TAG, "运动员{" + Thread.currentThread().getName() + "}入场了");
try {
cyclicBarrier.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, "运动员{" + Thread.currentThread().getName() + "}准备出发");
}
});
}
}
}
比较:
1.CountDownLatch是一个线程等待多个线程,CyclicBarrier是多个线程相互等待
2.CountDownLatch调用countDown之后,线程不会阻塞;而调用CyclicBarrier的await方法后,线程阻塞
3.方法数不同
4.CyclicBarrier可复用
Semaphore
10个人有5枝笔,使用acquire获取许可证才能继续执行,否则只能在该方法处阻塞。release归还许可后,其他线程可有获得许可证继续执行。
public class SemaphoreTest {
private final static String TAG = "SemaphoreTest";
Semaphore mSemaphore = new Semaphore(5);
public SemaphoreTest() {
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
service.execute(new Runnable() {
@Override
public void run() {
Log.e(TAG, Thread.currentThread().getName() + " 准备获取笔");
try {
mSemaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e(TAG, Thread.currentThread().getName() + " 成功获取笔");
Log.e(TAG, Thread.currentThread().getName() + " 填写表格中");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mSemaphore.release();
Log.e(TAG, Thread.currentThread().getName() + " 放回笔");
}
});
}
}
}
Exchanger
如果一个线程先执行exchange方法,那么他会同步灯带另一个线程也执行exchange方法,这个时候两个线程都达到了同步点,两个线程就可以交换数据
例:一个人走到门口,等待另一个人走到门口同步点,然后打一架
public class ExchangerTest {
private static final String TAG = "ExchangerTest";
Exchanger<String> mExchanger = new Exchanger<>();
public ExchangerTest() {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
try {
String king = mExchanger.exchange("我们去操场打一架吧");
Log.e(TAG, "Long:" + king);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.submit(new Runnable() {
@Override
public void run() {
Log.e(TAG, "Long 走到了操场");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
String loong = mExchanger.exchange("我也想揍你了");
Log.e(TAG, "King:" + loong);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
生产者-消费者
三种方式:
- 1.使用Object的wait和notify的消息通知机制
- 2.使用Lock的Condition的await和signal的消息通知机制
- 3.使用BlockingQueue实现