文章目录
1. 多线程的概述(并发编程)
1.1 进程
程序是静止的,运行中的程序就是进程。
进程的三个特征:
- 动态性:进程是运行中的程序,要动态的占用内存,CPU和网络等资源。
- 独立性:进程与进程之间是相互独立的,彼此有自己的独立内存区域。
- 并发性:假如CPU是单核,同一个时刻其实内存中只有一个进程在被执行。CPU会分时轮询切换依次为每个进程服务,因为切换的速度非常快,给我们的感觉这些进程在同时执行,这就是并发性。
ps:并行是同一时刻有多个在执行。
1.2 线程
线程是属于进程的。一个进程可以包含多个线程,这就是多线程。
线程创建开销相对于进程来说比较小。
线程也支持并发性。
1.3 线程的作用
- 可以提高程序的效率,线程也支持并发性,可以有更多机会得到CPU。
- 多线程可以解决很多业务模型。
- 大型高并发技术的核心技术。
- 设计到多线程的开发可能都比较难理解。
2. 线程的创建
多线程是很有用的,我们在进程中创建线程的方式有三种:
- 直接定义一个类继承线程类 Thread,重写run()方法,创建线程对象,调用线程对象的 start()方法启动线程。
- 定义一个线程任务类实现 Runnable接口,重写run()方法,创建线程任务对象,把线程任务对象包装成线程对象,调用线程对象的 start()方法启动线程。
- 实现Callable接口(拓展)。
2.1 方式一:继承Thread类
2.1.1 具体实现
- 定义一个线程类继承 Thread类。
- 重写run()方法。
- 创建一个新的线程对象。
- 调用线程对象的start()方法启动线程。
//1.定义一个线程类继承Thread类。
class MyThread extends Thread {
//2.重写run方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(" 子线程:"+i);
}
}
}
public class Demo {
//启动后的Demo当成一个进程
//main()方法由主线程执行,也就是说,main方法就是一个主线程
public static void main(String[] args) {
//3.创建一个线程对象
Thread t = new MyThread();
//4.调用start()方法启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程:"+i);
}
}
}
2.1.2 优缺点
优点:编码简单
缺点:线程类继承Thread类后无法继承其他类,功能不能通过继承拓展(单继承的局限性)。
2.1.3 注意事项
-
线程的启动必须调用 start()方法,否则当成普通类处理:
——如果线程直接调用run()方法,相当于变成了普通类的执行,此时将只有主线程在执行他们。
——start()方法底层其实是给CPU注册当前线程,并且触发run()方法执行。 -
建议线程先创建子线程,主线程的任务放在之后,否则主线程永远是先执行完。
2.1.4 Thread类的常用API
-
public void setName(string name):给当前线程取名字
-
public vold getName():获取当前线程的名字
——线程存在默认名称,子线程的默认名称是:Thread-索引
——主线程的默认名称就是main
- public static Thread currentThread():
——获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
public class Demo {
public static void main(String[] args) {
Thread t1 = new MyThread();
t1.setName("线程1:");//修改第1个线程的名字
t1.start();
Thread t2 = new MyThread();
t2.setName(" 线程2:");//修改第2个线程的名字
t2.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
2.1.5 线程休眠的方法
public static void sleep(long time):让当前线程休息多少毫秒再继续执行。
public class Demo {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(i);
try {
Thread.sleep(1000);//休眠1s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.1.6 通过带参构造器为线程取名字
——public Thread()
——public Thread(String threadName):创建线程对象并取名字。
class MyThread extends Thread {
public MyThread(String threadName) {
super(threadName);//调用父类的有参数构造器,用来初始化当前线程的名称
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"=>"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
Thread t1 = new MyThread("线程1");
t1.start();
Thread t2 = new MyThread("线程2");
t2.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"=>"+i);
}
}
}
2.2 方式二:实现Runnable接口
2.2.1 具体实现
- 创建一个线程任务类实现Runnable接口。
- 重写run()方法
- 创建一个线程任务对象
- 把线程任务对象包装成线程对象
- 调用线程对象的 start()方法启动线程。
2.2.2 Thread构造器
- public Thread() { }
- public Thread(String threadName) { }
- public Thread(Runnable target)
- public Thread(Runnable target,String threadName)
2.2.3 代码示例
//1.创建一个线程任务类实现Runnable接口。
class MyRunnable implements Runnable {
//2.重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"=>"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
//3.创建一个线程任务对象
Runnable target = new MyRunnable();
//4.把线程任务对象包装成线程对象
Thread t1 = new Thread(target,"线程1");
//5.调用线程对象的 start()方法启动线程。
t1.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"=>"+i);
}
}
}
2.2.4 优缺点
缺点:
- 代码复杂一点。
- 无法得到线程执行的结果。
优点:
-
线程任务类只是实现了 Runnable接口,可以继续继承其他类,而且可以继续实现其他接口(避免单继承)
-
同一个线程任务对以被包装成多个线程对象适合多个多个线程去共享同一个资源
-
实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
-
线程池可以放入实现 Runnable或Callable线程任务对象。
注意:其实 Thread类本身也是实现了 Runnable接口的。
2.2.5 匿名内部类的代码示例
public class Demo {
public static void main(String[] args) {
Runnable target = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"=>"+i);
}
}
};
Thread t1 = new Thread(target);
t1.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"=>"+i);
}
}
}).start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"=>"+i);
}
}
}
2.3 方式三:实现Callable接口
2.3.1 具体实现
- 定义一个线程任务类实现Callable接口,申明线程执行的结果类型。
- 重写线程任务类的call()方法,这个方法可以直接返回执行的结果。
- 创建一个Callable的线程任务对象。
- 把Callable的线程任务对象包装成一个未来任务对象。
- 把未来任务对象包装成线程对象.
- 调用线程的 start()方法启动线程。
2.3.2 优缺点
优点:
- 线程任务类只是实现了Callable接口,可以继续继承其他类,而且可以继续实现其他接口的功能。
- 同一个线程任务对象可以被包装成多个线程对象。
- 适合多个线程去共享同一个资源。
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立。
- 线程池可以放入实现 Runnable或Callable线程任务对象。
- 能直接得到线程执行的结果!
缺点:编码复杂。
2.3.3 代码示例
// 1.定义一个线程任务类实现Callable接口,申明线程执行的结果类型。
class MyCallable implements Callable<String> {
// 2.重写线程任务类的call()方法,这个方法可以直接返回执行的结果。
@Override
public String call() throws Exception {
//需求:计算1-100的和
int sum = 0;
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"=>"+i);
sum += i;
}
return Thread.currentThread().getName()+"执行结果是:"+sum;
}
}
public class Demo {
public static void main(String[] args) {
// 3.创建一个Callable线程任务对象
Callable<String> call = new MyCallable();
// 4.把Callable的线程任务对象包装成一个未来任务对象。
// --public FutureTask(Callable<String> callable)
// 未来任务对象是一个Runnable对象,这样就可以被包装成线程对象
// 未来任务对象可以在线程执行完毕后得到线程执行的结果
FutureTask<String> task = new FutureTask<>(call);
// 5.把未来任务对象包装成线程对象
Thread t = new Thread(task);
// 6.启动线程对象
t.start();
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"=>"+i);
}
// 在最后区获取线程执行的结果,线程如果没有结果,让出CPU等线程执行完后再来取结果
try {
String result = task.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
3. 线程的安全问题
多个线程同时操作同一个共享资源的时候可能会出现线程安全问题。
3.1 解决方法:加锁
把共享资源上锁,每次只能一个线程进入访问,访问完毕后其他线程才能进来。
3.1.1 同步代码块
public class Demo {
public static void main(String[] args) {
Runnable a = new DrawMoney();
Thread t1 = new Thread(a,"A");
Thread t2 = new Thread(a,"B");
t1.start();
t2.start();
}
}
class DrawMoney implements Runnable {
private double balance = 10000;
@Override
public void run() {
while (true) {
synchronized (this) {//同步代码块
if (balance>=100) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= 100;
System.out.println(Thread.currentThread().getName()+"取出100,剩 余:"+balance);
}
}
}
}
}
3.1.2 同步方法
public class Demo {
public static void main(String[] args) {
Runnable a = new DrawMoney();
Thread t1 = new Thread(a,"A");
Thread t2 = new Thread(a,"B");
t1.start();
t2.start();
}
}
class DrawMoney implements Runnable {
private double balance = 10000;
@Override
public void run() {
func();
}
public synchronized void func() {
while (true) {
if (balance>=100) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= 100;
System.out.println(Thread.currentThread().getName()+"取出100,剩 余:"+balance);
}
}
}
}
3.1.3 lock显示锁
public class Demo {
public static void main(String[] args) {
Runnable a = new DrawMoney();
Thread t1 = new Thread(a,"A");
Thread t2 = new Thread(a,"B");
t1.start();
t2.start();
}
}
class DrawMoney implements Runnable {
private double balance = 10000;
private final Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();//上锁
if (balance>=100) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -= 100;
System.out.println(Thread.currentThread().getName()+"取出100,剩 余:"+balance);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();//解锁
}
}
}
}
3.2 等待唤醒机制
- wait():让线程处于冻结状态,存在线程池中
- notify():唤醒线程池中任意一个线程
- notifyAll():唤醒线程池中的所有线程
ps:sleep()和wait()的区别?
sleep()之后,线程释放执行权,但是不释放锁;时间到了继续执行。
wait()之后,线程释放执行权,释放锁。有无参和带参
4. 线程通信
class Account {
private String cardID;
private double balance;
public Account(String cardID, double balance) {
this.cardID = cardID;
this.balance = balance;
}
public String getCardID() {
return cardID;
}
public void setCardID(String cardID) {
this.cardID = cardID;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public synchronized void drawMoney() {
try {
if (balance == 100) {
balance -= 100;
System.out.println(Thread.currentThread().getName()+"取出"+100+",账户剩 余:"+balance);
this.notifyAll();
this.wait();
}else{
System.out.println(Thread.currentThread().getName()+"来取钱,余额不足");
this.notifyAll();
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void saveMoney() {
try {
if (balance==0) {
balance += 100;
System.out.println(Thread.currentThread().getName()+"存了100,账户余 额:"+balance);
this.notifyAll();
this.wait();
}else{
System.out.println(Thread.currentThread().getName()+"不需要存钱");
this.notifyAll();
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class DrawThread extends Thread {
private Account account;
DrawThread(Account account,String threadName) {
super(threadName);
this.account = account;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.drawMoney();
}
}
}
class SaveThread extends Thread {
private Account account;
SaveThread(Account account,String threadName) {
super(threadName);
this.account = account;
}
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.saveMoney();
}
}
}
public class Demo {
public static void main(String[] args) {
Account account = new Account("ID_001",0);
Thread t1 = new DrawThread(account,"小明");
Thread t2 = new DrawThread(account,"小红");
Thread t3 = new SaveThread(account,"爸爸");
Thread t4 = new SaveThread(account,"妈妈");
Thread t5 = new SaveThread(account,"爷爷");
Thread t6 = new SaveThread(account,"奶奶");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
5. 线程状态
6. 线程池
6.1 概念
一个可以容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,节省资源。
6.2 好处
- 降低资源消耗:减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
- 提高响应速度:不需要频繁的创建线程,如果有线程可以直接用,不会出现系统僵死!
- 提高线程的可管理性(线程池可以约束系统最多只能有固定个线程,不会因为线程过多而死机)
6.3 线程池的核心思想
线程复用,同一个线程可以被重复使用,来处理多个任务。
6.4 线程池的创建和原理
线程池在Java中的代表类:ExecutorService(接口)
Java在Executors类下提供了一个静态方法得到一个线程池的对象:
- public static ExecutorService newFixedThreadPool(int nThreads):创建一个线程池返回。
public class Demo {
public static void main(String[] args) {
//创建一个线程池,指定线程数量为2
ExecutorService pool = Executors.newFixedThreadPool(3);
//添加线程任务让线程池处理
Runnable target = new MyThread();
pool.submit(target);//第1次提交任务,线程池创建线程,自动触发执行
pool.submit(target);//第2次提交任务,线程池创建线程,自动触发执行
pool.submit(target);//第3次提交任务,线程池创建线程,自动触发执行
pool.submit(target);//第4次提交任务,复用之前的线程
//pool.shutdown();//等任务执行完毕后再关闭线程池
pool.shutdownNow();//立即关闭线程池,不论任务是否执行完毕
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
7. 死锁
7.1 概念
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
7.2 死锁产生的4个必要条件
- 互斥使用:当资源被一个线程使用(占有)时,别的线程不能使用。
- 不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
- 请求和保持:当资源请求者在请求其他的资源的同时保持对原有资源的占有。
- 循环等待:存在一个等待循环队列:A要B的资源,B要A的资源。这样就形成了一个等待环路。
7.3 代码示例
public class Demo {
public static Object resource_01 = new Object();
public static Object resource_02 = new Object();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (resource_01) {
System.out.println("线程1占用资源1,请求资源2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource_02) {
System.out.println("线程1占用资源2");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (resource_02) {
System.out.println("线程2占用资源2,请求资源1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource_01) {
System.out.println("线程2占用资源1");
}
}
}
}).start();
}
}
8. volatile关键字
多个线程访问共享变量,会出现其中一个线程修改变量的值后,其他线程看不到最新值的情况。
8.1 代码示例
如下代码:在子线程中将flag的值改为true,但是在主线程中flag依旧为false
class VolatileThread extends Thread {
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
}
public boolean isFlag() {
return flag;
}
}
public class VisibilityDemo {
public static void main(String[] args) {
VolatileThread v = new VolatileThread();//创建子线程
v.start();//在子线程修改了flag的值
while (true) { // 此时flag还是false
if (v.isFlag()) {
System.out.println("111");
}
}
}
}
8.2 问题分析
共享变量flag==false存放在主内存中,接着子线程和主线程加载变量副本,读进自己的工作内存,此时flag都为false。之后子线程将变量flag改为true,并把变量送到主内存,此时主内存的flag为true。
但是,主线程的flag的值不会去更新,所以主线程的flag一直为false。
8.3 解决方式
解决线程间变量不可见性有2种常见方法
- 加锁(synchronize)
- 对共享的变量进行volatile关键字修饰
8.3.1 加锁(synchronize)
- 线程获得锁
- 清空线程的工作内存
- 从主内存拷贝最新的共享变量的值到工作内存
- 执行代码
- 将修改后的共享变量的值刷新回到主内存
- 线程释放锁
class VolatileThread extends Thread {
private boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
}
public boolean isFlag() {
return flag;
}
}
public class VisibilityDemo {
public static void main(String[] args) {
VolatileThread v = new VolatileThread();//创建子线程
v.start();//在子线程修改了flag的值
while (true) {
synchronized (VisibilityDemo.class) {
if (v.isFlag()) {//加了锁之后flag的值变为true
System.out.println("111");
}
}
}
}
}
8.3.2 volatile关键字修饰
一旦一个线程修改了共享变量的值,其他线程会立即取到最新值
class VolatileThread extends Thread {
private volatile boolean flag = false;//将共享变量用volatile修饰
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
}
public boolean isFlag() {
return flag;
}
}
public class VisibilityDemo {
public static void main(String[] args) {
VolatileThread v = new VolatileThread();
v.start();
while (true) {
if (v.isFlag()) {
System.out.println("111");
}
}
}
}
9. 原子性
9.1 概念
一批操作是一个整体,要么同时成功,要么同时失败。
9.2 问题代码示例
9.2.1 问题描述
下述代码,程序运行结束后正常情况下count为10000,但实际情况是:多次运行,有许多次没有到10000.
class AtomThread implements Runnable {
private int count = 0;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
count++;
}
System.out.println(count);
}
}
public class VolatileAtomicDemo {
public static void main(String[] args) {
Runnable e = new AtomThread();
for (int i = 1; i <= 100; i++) {
new Thread(e).start();
}
}
}
9.2.2 产生问题原因
run()方法的count在自加时,假设第一个线程读到的count是10,那么第二个第三个线程读到的count也有可能是10,那么自加后count为11,但是实际情况下count应该为13。
9.2.3 注意
volatile关键字修饰的变量只能保证其可见行,不能保证原子性。
9.3 问题解决
9.3.1 加锁(synchronized)
9.3.2 AtomicInteger
因为加锁的性能较差,所以使用原子类,性能高效,线程安全。
9.3.2.1 构造方法:
-
public AtomicInteger():初始化一个默认值为0的原子型 Integer
-
public AtomicInteger(int initialValue):初始化一个指定值的原子型 Integer
9.3.2.2 其他方法:
-
int get():获取值
-
int getAndIncrement ():以原子方式将当前值加1,注意,这里返回的是自增前的值
-
int incrementAndGet():以原子方式将当前值加1,注意,这里返回的是自增后的值
-
int addAndGet (int data):以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
-
int getAndSet (int value):以原子方式设置为 newValue的值,并返回旧值
9.3.3.3代码示例
class AtomThread implements Runnable {
private AtomicInteger count = new AtomicInteger();//创建一个原子类,初始值为0
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
count.incrementAndGet();
}
System.out.println(count);
}
}
public class VolatileAtomicDemo {
public static void main(String[] args) {
Runnable e = new AtomThread();
for (int i = 1; i <= 100; i++) {
new Thread(e).start();
}
}
}
9.3.3.4 原理
比较再交换(CAS):每次修改数据不会加锁,等到修改完时会判断原值有没有被修改。如果有,重新取值自加;没有,修改值。
比如:两个线程获得的count值都为7。1号线程自加后,count值为8,在把值变为8之前,首先会把取时的count值7和原来的count值7作比较,发现原来count的值还是7没有变过,那么把count的值变为8;等到线程2自加后count的值为8,它会把自己取时的count7和原来的count作比较,发现count值已经是8了,和原来取时的count值7不一样,那么重新取count==8,再自加,去比较,符合,把count改为9。
9.4 CAS和synchronized(乐观锁和悲观锁)
CAS和 Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?
Synchronized是从悲观的角度出发(悲观锁):总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此 Synchronized我们也将其称之为悲观锁。jdk中的 ReentrantLock也是一种悲观锁。性能较差!
CAS是从乐观的角度出发:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,CAS这种机制我们也可以将其称之为乐观锁。综合性能较好!
10. 并发包(重点)
10.1 ConcurrentHashMap
Map集合中的经典集合:HashMap性能好,但它线程不安全。
如果在要求线程安全的业务情况下就不能用这个集合做Map集合,否则业务会崩溃
验证HashMap线程不安全的代码:
public class Demo {
public static Map<String,String> maps = new HashMap<>();
public static void main(String[] args) {
myRunnable target = new myRunnable();
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
t1.start();
t2.start();
try {
t1.join();//让t1跑完
t2.join(); //让t2跑完
//join()保证了主线程不抢占资源,但是t1和t2之间会互相抢占资源
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(maps.size());//结果小于10000,得证
//HashMap线程不安全原因:假如t1和t2都是在x这个位置插入元素,那么其中一个元素会覆盖另一个元素
}
}
class myRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i < 5000; i++) {
Demo.maps.put(Thread.currentThread().getName()+i,i+"");
}
}
}
为了保证线程安全,可以使用 Hashtable。
Hashtable是线程安全的Map集合,但是性能较差(每个过程都加锁),已经被淘汰了。
为了保证线程安全,再看 ConcurrentHashMap(不止线程安全,而且效率高,性能好,最新最好用)
ConcurrentHashMap保证了线程安全,综合性能较好!(局部锁定,只锁定当前元素,其他元素不锁定)
10.2 CountDownLatch
CountDownlatch允许一个或多个线程等待其他线程完成操作,再执行自己。
例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行
需求:
提供A线程,打印A,C,提供B线程,打印B.
构造器public CountDownLatch( int count)//初始化唤醒需要的down几步。
方法:
public void await() throws InterruptedException//让当前线程等待
public vold countDown() //计数器进行减1(down1)
代码示例:
class ThreadA extends Thread {
private CountDownLatch c;
ThreadA(CountDownLatch c) {
this.c = c;
}
@Override
public void run() {
System.out.println("A");
try {
c.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C");
}
}
class ThreadB extends Thread {
private CountDownLatch c;
ThreadB(CountDownLatch c) {
this.c = c;
}
@Override
public void run() {
System.out.println("B");
c.countDown();//让监督者中的计数器减1,等待的线程被唤醒
}
}
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch c = new CountDownLatch(1);
new ThreadA(c).start();
new ThreadB(c).start();
}
}
未完待续…