java -- 线程
Posted _泡泡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java -- 线程相关的知识,希望对你有一定的参考价值。
死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争同步锁而产生的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁。
死锁的案例 : 同步代码块的嵌套
创建锁对象:
public class Lock
public static final Lock lockA = new Lock();
public static final Lock lockB = new Lock();
测试类:
public class DeadLockTest
public static void main(String[] args)
while(true)
new Thread(new Runnable()
@Override
public void run()
synchronized (Lock.lockA)
System.out.println("getlockA...");
synchronized (Lock.lockB)
System.out.println("getlockB...");
).start();
new Thread(new Runnable()
@Override
public void run()
synchronized (Lock.lockB)
System.out.println("getlockB...");
synchronized (Lock.lockA)
System.out.println("getlockA...");
).start();
生产者与消费者
创建2个线程,一个线程表示生产者,另一个线程表示消费者
import java.util.ArrayList;
import java.util.List;
public class Demo
public static void main(String[] args)
List<String> list = new ArrayList<String>();
Object o = new Object();
// 生产者
new Thread(new Runnable()
@Override
public void run()
while (true)
synchronized (o)
if (list.size() > 0)
try
o.wait();
catch (InterruptedException e)
e.printStackTrace();
list.add("aaaa");
System.out.println(list);
// 唤醒消费线程
o.notify();
).start();
// 消费者
new Thread(new Runnable()
@Override
public void run()
while (true)
synchronized (o)
if (list.size() == 0)
try
o.wait();
catch (InterruptedException e)
e.printStackTrace();
list.remove(0);
System.out.println(list);
o.notify();
).start();
线程方法sleep和wait的区别
- sleep()是Thread类静态方法,不需要对象锁。
- wait()方法是Object类的方法,被锁对象调用,而且只能出现在同步中。
- 执行sleep()方法的线程不会释放同步锁。
- 执行wait()方法的线程要释放同步锁,被唤醒后还需获取锁才能执行。
案例性能问题
wait()方法和notify()方法, 本地方法调用OS的功能,和操作系统交互,JVM找OS,把线程停止. 频繁等待与唤醒,导致JVM和OS交互的次数过多.
Condition接口
java.util.concurrent.locks.Condition
是一个接口类, 因此要使用其实现类创建对象
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用
// Condition常用方法:
public void await() // 线程等待
public void signal() // 唤醒一个等待的线程
public void singalAll() // 唤醒所有等待的线程
// 使用其实现类 ReentrantLock 的 newCondition方法获取 Condition
public Condition newCondition()
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo
public static void main(String[] args) throws InterruptedException
// 创建Lock
Lock l = new ReentrantLock();
// 获取 Condition 对象
Condition con = l.newCondition();
new Thread(new Runnable()
@Override
public void run()
l.lock();
System.out.println("开始等待");
try
con.await();
catch (InterruptedException e)
e.printStackTrace();
finally
l.unlock();
).start();
Thread.sleep(2000);
System.out.println("准备唤醒");
l.lock();
con.signal();
l.unlock();
Condition接口方法和Object类方法比较
- Condition可以和任意的Lock组合,也就是实现了线程的分组管理。
- 一个线程的案例中,可以使用多个Lock锁,每个Lock锁上可以结合Condition对象
- synchronized同步中做不到线程分组管理
- Object类wait()和notify()都要和操作系统交互,并通知CPU挂起线程,唤醒线程,效率低。
- Condition接口方法await()不和操作系统交互,而是让线程释放锁,并存放到线程队列容器中,当被signal()唤醒后,从队列中出来,从新获取锁后在执行。
- 因此使用Lock和Condition的效率比Object要快很多
生产者和消费者案例改进
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class OverWriteWakeUpWaiting
public static void main(String[] args)
List<String> list = new ArrayList<>();
Lock l = new ReentrantLock();
// 对线程进行分组管理
Condition con1 = l.newCondition(); // 生产线程, 对象监视器
Condition con2 = l.newCondition(); // 消费线程, 对象监视器
new Thread(new Runnable()
@Override
public void run()
while (true)
l.lock();
if (list.size() > 0)
try
con1.await();
catch (InterruptedException e)
e.printStackTrace();
list.add("abc");
System.out.println(list);
con2.signal();
l.unlock();
).start();
new Thread(new Runnable()
@Override
public void run()
while (true)
l.lock();
if (list.size() == 0)
try
con2.await();
catch (InterruptedException e)
e.printStackTrace();
list.remove(0);
System.out.println(list);
con1.signal();
l.unlock();
).start();
java并发编程的三大特性
原子性
原子性,即一个操作或多个操作,要么全部执行并且在执行的过程中不被打断,要么全部不执行
下面具有原子性的操作有?
x = 1;
// x = 1,是一个单纯的赋值操作,满足原子性。
y=x;
// 实际是两个操作,分别是 读取x变量 ,将x赋值给y,这两个操作分别来看都是原子性的,但是合起来就不是了
x++;
// 实际是三个操作 ,先读取变量 ,在进行+1操作 ,再赋值给x,不满足原子性
x=x+1;
// 同上,不满足原子性
JAVA提供了原子性的技术保障有如下:
1、synchronized (互斥锁)
2、Lock(互斥锁)
3、原子类(CAS)
synchronized 和 Lock 都是通过互斥锁实现,即同一时刻只允许一个线程操作该变量,保障了原子性
原子类AtomicInteger
/*
java.util.concurrent.atomic.AtomicInteger
构造方法
public AtomicInteger()创建具有初始值 0 的新 AtomicInteger。
public AtomicInteger(int initialValue) 创建具有给定初始值的新 AtomicInteger。
方法
int incrementAndGet() 以原子方式将当前值加 1。
int getAndIncrement() 以原子方式将当前值加 1。
int decrementAndGet() 以原子方式将当前值减 1。
int getAndIncrement() 以原子方式将当前值减 1。
int getAndAdd(int delta) 以原子方式将给定值与当前值相加。
int addAndGet(int delta) 以原子方式将给定值与当前值相加。
int get() 获取当前值。
*/
public class Test02
public static void main(String[] args)
AtomicInteger ai = new AtomicInteger(1);
ai.incrementAndGet(); //++ai 2
ai.getAndIncrement(); //ai++ 3
System.out.println(ai.get());// 3
System.out.println(ai.getAndIncrement()); // 3
System.out.println(ai.get()); // 4
System.out.println(ai.incrementAndGet()); // 5
System.out.println(ai.get()); //5
CAS无锁机制
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。当多条线程尝试使用CAS同时更新同一个变量时,只有其中一条线程能更新变量的值,而其他线程都失败,失败的线程并不会被挂起,而是告知这次竞争失败,并可以再次尝试.
CAS的缺点:
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
-
循环时间长开销很大。
CAS 通常是配合无限循环一起使用的,如果 CAS 失败,会一直进行尝试。如果 CAS 长时间一直不成功,可能会给 CPU 带来很大的开销。
-
只能保证一个变量的原子操作。
当对一个变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个变量操作时,CAS 目前无法直接保证操作的原子性。
-
ABA问题。
第一条线程获取到V位置的值 假设是 1 第二条线程获取到V位置的值 也是1 第一条线程cas成功 将值改为 0 第一条线程又cas成功 将值改回 1 这时第二条线程cas 发现值没变 还是1 cas成功 实际上当第二条线程cas时 V位置的值已经从 1-0-1 这就是ABA问题 如何解决 每次获取V位置的值时,带上一个版本号.这样就可以避免ABA问题 java中AtomicStampedReference这个类在cas时就是通过版本号来解决的
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程应该能够立即看得到修改的值
public class Test
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException
new Thread(new Runnable()
@Override
public void run()
System.out.println("1号线程启动....执行while循环");
long num = 0;
while(flag)
num++;
System.out.println(num);
).start();
Thread.sleep(2000);
new Thread(new Runnable()
@Override
public void run()
System.out.println("2号线程启动....修改flag的值为false,停止循环");
flag = false;
).start();
通过如上案例 发现修改flag 的值并没有使循环结束
1.加锁,比如使用synchronized.
JMM关于synchronized的两条规定:
1)线程解锁前,必须把共享变量的最新值刷新到主内存中
2)线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值
public class Test
//使用同步方法获取flag的值
public static synchronized boolean getFlag()
return flag;
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException
new Thread(new Runnable()
@Override
public void run()
System.out.println("1号线程启动....执行while循环");
long num = 0;
/*
线程调用getFlag方法时 先获取锁 也就是加锁
这时会先清空本地内存中共享副本的值,那么在使用值就需要从
主内存中重新获取 ,线程释放锁时,也就是解锁,会把共享变量flag
的值重新更新到主内存中
*/
while(getFlag())
num++;
System.out.println(num);
).start();
Thread.sleep(2000);
new Thread(new Runnable()
@Override
public void run()
System.out.println("2号线程启动....修改flag的值为false,停止循环");
flag = false;
).start();
2.使用volatile关键字保证可见性
public class Test
public static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException
new Thread(new Runnable()
@Override
public void run()
System.out.println("1号线程启动....执行while循环");
long num = 0;
while(flag)
num++;
System.out.println(num);
).start();
Thread.sleep(2000);
new Thread(new Runnable()
@Override
public void run()
System.out.println("2号线程启动....修改flag的值为false,停止循环");
flag = false;
).start();
volatile缓存可见性实现原理
底层实现主要是通过汇编lock前缀指令,会锁住这块区域的缓存,并写回主内存.
1.会将当前处理器缓存的行数据立即写回系统内存
2.这个写回内存的操作导致CPU的缓存该内存地址的数值失效(MESI协议)
volatile只能保证可见性,但是不能保证原子性,如果要保证原子性,请使用锁
有序性
一般来说,程序的执行顺序按照代码的先后顺序执行.但是处理器为了提高程序的效率,可能会对代码的执行顺序进行优化,它不保证程序中各个语句的执行先后顺序一致,但是保证程序的最终结果和代码顺序执行的结果一致.
int a = 10; //语句1
int b = 20; //语句2
int c = 20; //语句3
c= a + b; //语句4
CPU可能会对没有依赖关系的语句进行重排,比如 2134,3124 但是不会对有依赖关系的数据进行重排比如 3和4 改为4和3 这样就会对结果造成影响.这种重排对单线程是没有任何影响的,但是如果是多线程就可能会出现问题.
验证CPU是否会进行指令重排:
public class Test
public static void main(String[] args) throws InterruptedException
for (int i = 0; i < 500000; i++)
Test.State state = new Test.State();
ThreadA t1 = new ThreadA(state);
ThreadB t2 = new ThreadB(state);
t1.start();
t2.start();
static class ThreadA extends Thread
private final Test.State state;
ThreadA(Test.State state)
this.state =state;
public void run()
state.a=1;
state.b=1;
state.c=1;
state.d=1;
static class ThreadB extends Thread
private final Test.State state;
ThreadB(Test.State state)
this.state =state;
public void run()
if( state.b== 1 && state.a ==0)
System.out.println("b= " + state.b);
if(state.c == 1 &&(state.b==0|| state.a ==0))
System.out.println("c = " + state.c);
if(state.d==1 &&(state.a==0||state.b==0||state.c==0))
System.out.println("d " + state.d);
static class State
int a = 0;
int b = 0;
int c = 0;
int d = 0;
/*
c = 1
说明,CPU进行了重排,让c在b或者a前面进行了赋值.
改变顺序可能导致执行结果不同,因此需要禁止重排序。
*/
使用volatile关键字后 就不会出现刚才的情况
static class State
volatile int a = 0;
volatile int b = 0;
volatile int c = 0;
volatile int d = 0;
由此可见:volatile关键字有两个作用1.保证可见性.2禁止重排序
单例设计模式
设计模式 : 不是技术,是以前的人开发人员,为了解决某些问题实现的写代码的经验.
Java的设计模式有23种,分为3个类别,创建型,行为型,功能型
单例代表单个实例,保证一个类的对象永远只有一个!
饿汉式
优点: 简单 多线程下没有任何问题
缺点:
- 当类加载时 对象就会被直接创建
- 若不被使用 对象就白创建了
public class danliDemo1
public static void main(String[] args)
for (int i = 0; i < 100; i++)
new Thread(new Runnable()
@Override
public void run()
System.out.println(Single1.getInstance());
).start();
class Single1
private static Single1 s = new Single1();
public Single1()
public static Single1 getInstance()
return s;
懒汉式
优点: 延迟加载 什么时候调用方法 什么时候创建对象
缺点: 多线程时 代码有问题
public class danliDemo2
public static void main(String[] args)
for (int i = 0; i < 100; i++)
new Thread(new Runnable()
@Override
public void run()
System.out.println(Single2.getInstance());
).start();
class Single2
private static Single2 s;
public Single2()
public static Single2 getInstance()
if (s == null)
s = new Single2();
return s;
安全问题
一个线程判断完变量 s=null,还没有执行new对象,被另一个线程抢到CPU资源,同时有2个线程都进行判断变量,对象创建多次
性能问题
第一个线程获取锁,创建对象,返回对象. 第二个线程调用方法的时候,变量s已经有对象了,根本就不需要在进同步,不要在判断空,直接return才是最高效的.
双重的if判断,提高效率 Double Check Lock(DCL)
DCL双检查锁机制单例,效率高,线程安全,多线程操作原子性。
class Single2
private static Single2 s;
public Single2()
public static Single2 getInstance()
if (s == null)
synchronized (Single2DCL.class)
if (s == null)
s = new Single2DCL();
return s;
面试题
DCL单例是否需要使用volatile关键字?
需要,单例的模式, 不使用volatile关键字,可能线程会拿到一个尚未初始化完成的对象(半初始化)
Java多线程 5.栅栏
5.1 ReadMe
此文线程和任务可以理解为一个意思;
Java中一般通过CountDownLantch和CyclicBarrier来解决线程(任务)之间依赖的问题,栅栏特指CyclicBarrier类,因为CountDownLatch可以实现类似功能,所以在此放到一块讲解;
在任务A依赖任务B的这种场景可以使用Object的wait和notify来实现,但是如果任务A依赖任务B、C、D多个任务的场景,使用Object的wait和notify就难以实现,例如运动会10个人长跑(看作10个长跑任务),公布总排名这个任务就依赖至少9个长跑任务结束,这种场景适合使用CountDownLatch;
结合实际开发过程,更多的场景是A、B、C多个任务同时执行,但是A、B、C任务在执行过程中的某一个点相互依赖,例如一个需求分为前段开发和后端开发,前后端开始联调的时间点就是2个任务相互依赖的点,这种场景适合使用CyclicBarrier;
5.2 CountDownLatch
Latch是门闩的意思,要打开门需要打开门上的所有门闩,CountDownLatch可以理解为一个有多个门闩的门,每个门闩需要一个的线程打开;
代码实现举例:
1 import java.security.SecureRandom; 2 import java.util.concurrent.CountDownLatch; 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 import java.util.concurrent.TimeUnit; 6 7 /** 8 * CountDownLatch实例 9 */ 10 public class DemoCountDownLatch { 11 12 //参赛人数 / 门闩个数 13 private static final int COUNT_RUNNER = 3; 14 15 public static void main(String[] args) throws Exception { 16 17 // 3人参加比赛 / 创建一个有3个门闩的门 18 CountDownLatch countDownLatch = new CountDownLatch(COUNT_RUNNER); 19 20 // 创建3人跑道 / 装上门 21 ExecutorService executorService = Executors.newFixedThreadPool(COUNT_RUNNER); 22 23 for (int i = 0; i < 3; i++) { 24 executorService.submit(new Runner("runner" + i, countDownLatch)); 25 } 26 27 // 等待3人跑到终点 / 把3个门闩都锁上 28 countDownLatch.await(); 29 30 // 公布3人成绩 / 打开门了 31 System.out.println("all runner game over."); 32 } 33 34 static class Runner implements Runnable { 35 36 private String name; 37 38 private CountDownLatch countDownLatch; 39 40 public Runner(String name, CountDownLatch countDownLatch) { 41 this.name = name; 42 this.countDownLatch = countDownLatch; 43 } 44 45 @Override 46 public void run() { 47 try { 48 SecureRandom secureRandom = SecureRandom.getInstanceStrong(); 49 int runTime = Math.abs(secureRandom.nextInt()) % 10; 50 TimeUnit.SECONDS.sleep(runTime); 51 System.out.println(this.name + " game over cost " + runTime + " second."); 52 } catch (Exception e) { 53 e.printStackTrace(); 54 } 55 56 // 跑到终点 / 打开门闩 57 this.countDownLatch.countDown(); 58 } 59 } 60 }
说明:
1. 一个CountDownLatch对象后只能使用一次,也就是说不能工作同一个CountDownLatch对象来重复控制线程的依赖问题;
2.上面的例子中如果有长跑运动员中途放弃比赛,是否永远不能公布总的比赛成绩? CountDownLatch的await可以有入参(timeout, TimeUnit)表示最长等待时间;
5.3 CyclicBarrier
CyclicBarrier是循环栅栏的的意思,循环表示同一个CyclicBarrier可以重复使用(区别于CountDownLatch),Barrier栅栏可以理解为线程相互依赖的那个点(例如前后端联调时间点),各个线程在那个点相互等待,等所有线程到达点后才继续执行;
代码实现举例:
1 import java.time.Instant; 2 import java.util.concurrent.*; 3 4 /** 5 * 前端开发倩倩和后端开发厚厚开发一个需求 6 * 两人先独自开发需求,等都开发完再一块联调功能 7 */ 8 public class DemoCyclicBarrier 9 { 10 public static void main(String[] args) throws InterruptedException 11 { 12 13 // 创建栅栏,参数一为相互依赖的任务数;参数二为各任务到达依赖点后先执行的任务,等任务执行结束相互依赖的任务继续执行 14 CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> { 15 System.out.println("准备开始联调吧..."); 16 lastSecond(3L); 17 }); 18 19 // 创建线程池执行2个任务 20 ExecutorService executorService = Executors.newFixedThreadPool(2); 21 executorService.execute(new Coder(10L, "后端", cyclicBarrier)); 22 executorService.execute(new Coder(3L, "前端", cyclicBarrier)); 23 } 24 25 /** 26 * 线程持续second秒 27 */ 28 private static void lastSecond(long second) 29 { 30 Instant instant = Instant.now(); 31 while (Instant.now().minusSeconds(second).isBefore(instant)) 32 { 33 } 34 } 35 36 static class Coder implements Runnable 37 { 38 // 完成工作需要的时间 39 private long workTime; 40 41 private String name; 42 43 private CyclicBarrier cyclicBarrier; 44 45 public Coder(long workTime, String name, CyclicBarrier cyclicBarrier) 46 { 47 this.workTime = workTime; 48 this.name = name; 49 this.cyclicBarrier = cyclicBarrier; 50 } 51 52 @Override 53 public void run() 54 { 55 try 56 { 57 System.out.println(this.name + " are coding..."); 58 lastSecond(this.workTime); 59 System.out.println(this.name + " code end wait debugging.."); 60 // 完成工作/到达依赖的点/我这边可以开始联调了 61 this.cyclicBarrier.await(); 62 System.out.println("we are debugging.."); 63 64 } 65 catch (InterruptedException e) 66 { 67 // 当前线程被中断 68 e.printStackTrace(); 69 } 70 catch (BrokenBarrierException e) 71 { 72 // 1.其他线程中断;2.其他线程await方法超时;3.cyclicBarrier重置 73 e.printStackTrace(); 74 } 75 // catch (TimeoutException e) 76 // { 77 // //当前线程的await方法超时(await方法设置超时参数) 78 // e.printStackTrace(); 79 // } 80 } 81 } 82 }
说明:
1.CyclicBarrier作为循环栅栏,同一个对象可以循环使用;
2.上面例子中前端开发人员很短时间开发结束,通过await()一直在等待后端开发结束,可以通过await(timeout, TimeUnit)来设置最长等待时间;
3. 可以通过CyclicBarrier的getNumberWaiting()查看到达依赖点的任务;
4.CyclicBarrier构造方法的第二个参数指定的任务A,在其他相互依赖的任务到达依赖点后,任务A优先执行,并且是执行结束,其他任务才继续执行;
5.4 CyclicBarrier&CountDownLatch举例
1 import java.time.Instant; 2 import java.util.concurrent.*; 3 4 public class Demo_CyclicBarrier_CountDownLatch 5 { 6 private static final int COUNT_WORKER = 2; 7 8 public static void main(String[] args) throws InterruptedException 9 { 10 CountDownLatch countDownLatch = new CountDownLatch(COUNT_WORKER); 11 CyclicBarrier cyclicBarrier = new CyclicBarrier(COUNT_WORKER, () -> { 12 System.out.println("准备开始联调吧..."); 13 lastSecond(3L); 14 }); 15 16 ExecutorService executorService = Executors.newFixedThreadPool(2); 17 executorService.execute(new Coder(10L, "后端", countDownLatch, cyclicBarrier)); 18 executorService.execute(new Coder(3L, "前端", countDownLatch, cyclicBarrier)); 19 20 countDownLatch.await(); 21 System.out.println("开发联调结束,需求交付..."); 22 } 23 24 /** 25 * 线程持续second秒 26 */ 27 private static void lastSecond(long second) 28 { 29 Instant instant = Instant.now(); 30 while (Instant.now().minusSeconds(second).isBefore(instant)) 31 { 32 } 33 } 34 35 static class Coder implements Runnable 36 { 37 // 开发联调时间 38 private long workTime; 39 40 private String name; 41 42 private CountDownLatch countDownLatch; 43 44 private CyclicBarrier cyclicBarrier; 45 46 public Coder(long workTime, String name, CountDownLatch countDownLatch, CyclicBarrier cyclicBarrier) 47 { 48 this.workTime = workTime; 49 this.name = name; 50 this.countDownLatch = countDownLatch; 51 this.cyclicBarrier = cyclicBarrier; 52 } 53 54 @Override 55 public void run() 56 { 57 try 58 { 59 System.out.println(this.name + " are coding..."); 60 lastSecond(this.workTime); 61 System.out.println(this.name + " code end wait debugging.."); 62 63 this.cyclicBarrier.await(); 64 65 System.out.println(this.name + " are debugging.."); 66 lastSecond(this.workTime); 67 68 System.out.println(this.name + " debug end.."); 69 this.countDownLatch.countDown(); 70 } 71 catch (Exception e) 72 { 73 e.printStackTrace(); 74 } 75 } 76 } 77 }
以上是关于java -- 线程的主要内容,如果未能解决你的问题,请参考以下文章