JUC
线程和进程如何区分
1、进程有独立的内存空间,线程没有,进程是操作系统的基本单位,线程是cpu调度的基本单位
一、线程如何创建
一、继承Thread方法创建
public class T01_WhatIsThread {
private static class T1 extends Thread {
@Override
public void run() {
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1");
}
}
}
public static void main(String[] args) {
//这种方法就是单纯的方法引用,并没有创建线程
//new T1().run();
//通过调用start()方法创建线程进行运行
new T1().start();
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main");
}
}
}
二、继承Runnable接口(3种线程调用方法)
public class T02_HowToCreateThread {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println("Hello MyRun!");
}
}
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRun()).start();
new Thread(()->{
System.out.println("Hello Lambda!");
}).start();
}
}
三、Executors.newCachedThrad,通过线程池,但是我只了解,不会
二、线程的常用方法
一、Sleep()
改方法运行后会释放cpu资源,等时间结束在继续运行,改方法不会释放锁
static void testSleep() {
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
二、yield();
该方法调用时,当前线程重新进入等待队列,该线程重新和其他线程一起等cpu的调度
static void testYield() {
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("------------B" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
}
三、join();
当前线程等待调用的线程执行完毕之后在运行,下列代码中t1会等t2结束在运行
如果没有传参join(),就是得等当前线程运行结束
static void testJoin() {
Thread t1 = new Thread(()->{
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(()->{
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0; i<100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
三、线程状态
一、status();
当前线程的状态信息,3中,Ready(等待运行)Running(运行中)和Teminated(运行结束)
注意,线程没有关闭线程一说,因为关闭线程stop()方法会出问题,所以非必要不运行,等运行结束关闭就可以了
四、synchronized锁
1、synchronized是针对对象头两位锁的,多个线程都是看对象头的两个直接是否上锁,所以锁的时候,不能让对象重新初始化,所以用final修饰。
2、不能用String常量,
3、老的jdk版本中,synchronized直接是重量级锁,但是因为现实过程中,可能出现多个线程抢占锁的时候,同一个线程在释放锁之后又重新获取锁,所以对synchronized进行了优化操作,出现了偏向锁,自旋锁,重量级锁的升级操作,就是当线程1释放了锁,记录下这个线程1的信息,释放完立马在和别人竞争锁时,直接选择线程1,如果线程1没有抢到锁,升级为自旋锁,就是我在这自己绕圈,等我自旋完看你是否释放,释放了我就抢占锁,没有释放我就不抢占锁,如果自旋完原来占有的线程没有释放,那么就升级为重量级锁。
3.1、线程数如果多的话,大量的线程数在自旋占用资源,那么效率可能会更低,线程数如果比较正常,或者少,那么自选锁对资源的占用就比较少。
3.2、synchronized因为锁升级的概念,所以效率未必会比其他锁效率低
synchronized实现i++的思路
五、volatile
1、保证线程可见性,看下面代码,分析加volatile和不加的区别
1.1:在新建线程时,线程中的值会去copy公共地方的变量,靠cpu的缓存一致性MESI才能实现
1.2:没有volatile是线程之间的可见性是不保证的,子线程更改了东西会立马同步到公共区域,但是不会同步到其他子线程中
public class T01_HelloVolatile {
volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
void m() {
System.out.println("m start");
while(running) {
}
System.out.println("m end!");
}
public static void main(String[] args) {
T01_HelloVolatile t = new T01_HelloVolatile();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false;
}
}
2、禁止指令重排序
cpu层面禁止不了,jvm虚拟机可以禁止,这个基于现在的cpu的指令重排序命令来优化的,原本的cpu是单个命令,直接就运行了,现在的cpu为了提高效率,对于一些指令进行优化,有些指令可能会运行时同时运行,这就导致了工作上面的
双重锁在超高的并发中,会出现问题。
jvm的new对象过程,
1、申请内存,给到默认值
2、内存中设置我们的定义值
3、把内存绑定给代码中的对象,
因为CPU的指令重排序,偶尔会出现1 3 2的情况,单例模式下的双重锁,当第一个进去了,在创建对象时,1,3完成赋值了,而2还没有完成,另一个进程拿的这个对象去操作了,会出现值不对的情况,出现问题
volatile 关键字,使一个变量在多个线程间可见
volatile虽然保证了线程可见,但是不能保证原子性,不能替换synchronized
如果只有volatile发现i++不是10000,但是synchronized可以保证是10000条数据
public class T04_VolatileNotSync {
/*volatile */int count = 0;
synchronized void m() {
for(int i=0; i<10000; i++) count++;
}
public static void main(String[] args) {
T04_VolatileNotSync t = new T04_VolatileNotSync();
List<Thread> threads = new ArrayList<Thread>();
for(int i=0; i<10; i++) {
threads.add(new Thread(t::m, "thread-"+i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
AtomicLong多线程i++无锁化操作线程安全
AtomicLong,AtomicInteger,底层用的CAS无锁化
CAS(Compare And Set)无锁化
CAS是CPU原语支持,中间不可以被打断,所以在更改的时候不会出现第二个
cas(更改的对象,期望值,更新值);每个线程都会输入一个原来的值,一个要改变的值,当发现原来的值和输入不一样,那就是有线程改变了这个值,我循环等上一个线程改变,我在输入一个新的原来的值,一个新的改变到的值。
AbA问题:
会出现aba的问题,在上一个cas操作的时候变成b又变成1,第二个线程也不会有问题,不改也没事,不过想要解决这个问题,加版本号,后面在检查是加上版本号处理
aba问题出现,如果是基本数据类型,那没有问题,怎么样都行
如果是处理对象,就需要解决
举例:刚开始执行一个对象a,对象a中有个引用属性b,我cas(a,a,b)期望值改成b,并且改成b的时候,b中的属性变更了一点,然后我又cas(b,b,a)这么着下来,我们去比较a的时候,a是没有问题的,但是a指向的b的属性值有了问题,后续业务逻辑出错
public class T01_AtomicInteger {
/*volatile*/ //int count1 = 0;
AtomicInteger count = new AtomicInteger(0);
/*synchronized*/ void m() {
for (int i = 0; i < 10000; i++)
//if count1.get() < 1000
count.incrementAndGet(); //count1++
}
public static void main(String[] args) {
T01_AtomicInteger t = new T01_AtomicInteger();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread-" + i));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
LongAdder多线程i++分段锁操作
i会变成一个数组分别上锁,每个数组元素都加锁,由全部的线程平均分到数据的各个节点上,然后由最后的结果获取
/**
* 解决同样的问题的更高效的方法,使用AtomXXX类
* AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的
* @author mashibing
*/
package com.mashibing.juc.c_018_00_AtomicXXX;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;
public class T01_AtomicInteger {
/*volatile*/ //int count1 = 0;
// AtomicInteger count = new AtomicInteger(0);
LongAdder count = new LongAdder();
/*synchronized*/ void m() {
for (int i = 0; i < 10000; i++)
//if count1.get() < 1000
count.increment(); //count1++
}
public static void main(String[] args) {
T01_AtomicInteger t = new T01_AtomicInteger();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread-" + i));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
Atomic与synchronized与longAdder性能测试程序
根据下列程序,可以看到AtomicLong的效率要高于sync,但是要低于LongAdder,因为LongAdder在特别多的线程情况下,效率是比较高的,而AtomicLong是因为cas乐观锁,效率是比较高的,sync锁升级,在升级之后效率是很低的。
package com.company.test020;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
/**
* @description: TODO
* @author yang.su
* @version 1.0
*/
public class AtomicVsSyncVsLongAdder {
static Long count3 =0L;
static AtomicLong count1 = new AtomicLong(0);
static LongAdder count2 = new LongAdder();
public static void main(String[] args) throws Exception {
final Object lock = new Object();
Thread[] threads = new Thread[1000];
//AtomicLong测试
for (int i = 0 ; i<threads.length ; i++){
threads[i] = new Thread(()->{
for (int j = 100000 ; j>0 ; j--){
count1.incrementAndGet();
}
});
}
long atomicStart = System.currentTimeMillis();
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
long atomicEnd = System.currentTimeMillis();
System.out.println("atomicLong"+count1+":"+(atomicEnd-atomicStart));
//LongAdder
for (int i = 0 ; i<threads.length ; i++){
threads[i] = new Thread(()->{
for (int j = 100000 ; j>0 ; j--){
count2.increment();
}
});
}
long longAdderStart = System.currentTimeMillis();
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
long longAdderEnd = System.currentTimeMillis();
System.out.println("longAdder"+count2+":"+(longAdderEnd-longAdderStart));
//sync
for (int i = 0 ; i<threads.length ; i++){
threads[i] = new Thread(()->{
for (int j = 100000 ; j>0 ; j--){
synchronized (lock){
count3++;
}
}
});
}
long syncStart = System.currentTimeMillis();
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
long syncEnd = System.currentTimeMillis();
System.out.println("sync"+count2+":"+(syncEnd-syncStart));
}
}
synchronized (lock){
count3++;
}
}
});
}
long syncStart = System.currentTimeMillis();
for (Thread thread : threads) {
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
long syncEnd = System.currentTimeMillis();
System.out.println("sync"+count2+":"+(syncEnd-syncStart));
}
}