一、线程的基本概念:
1、进程:一个程序运行起来之后,叫一个进程。进程拥有代码和打开的文件资源、数据资源、独立的内存空间。
2、线程:进程里最小的执行单元就是一个线程,一个进程可以有多个线程。线程具有五种状态:New,Runnable,Running,Blocked,Dead
3、协程/纤程(quasar):比线程更加轻量级的存在,一个线程可以有多个协程,协程在程序内部是可中断的,然后再执行别的执行程序,在适当的时候再返回来接着执行;
二、线程的启动方式:
public class HowToCreateThread {
//1、继承thread
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}
//2、实现runnable
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();
//3、线程池中执行
new Thread(()->{
System.out.println("Hello Lambda!");
}).start();
}
}
三、线程相关关键字
1、sleep:睡眠一定时间,线程自动复活,线程回到就绪状态;
2、yield:执行中线程必须得让出当前线程锁一下,其他线程能抢到锁则抢到,抢不到,当前线程继续持有锁。
3、join:当前线程加入其他线程,等待其他join的线程执行完,再执行自己的线程。
4、线程状态
四、原子性操作
在Java中可以通过锁和循环CAS的方式来实现原子操作。
1、synchronized:保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile,可以确保线程互斥的访问同步代码。
2、CAS:自旋锁,循环操作直到成功为止,保证原子操作
1、AtomicLong:以incrementAndGet()为例,对AtomicLong的原理进行说明
public final long incrementAndGet() {
for (;;) {
// 获取AtomicLong当前对应的long值
long current = get();
// 将current加1
long next = current + 1;
// 通过CAS函数,更新current的值
if (compareAndSet(current, next))
return next;
}
}
2、LongAddr:继JDK1.8之后新增的原子类操作
public void increment() {
add(1L);
}
LongAdder类与AtomicLong类的区别在于高并发时前者将对单一变量的CAS操作分散为对数组中多个元素的CAS操作,取值时进行求和;而在并发较低时仅对base变量进行CAS操作,与AtomicLong类原理相同。
3、synchronized、AtomicLong、LongAddr针对100000000数据执行效率对比
public class AtomicVsSyncVsLongAdder {
static long count2 = 0L;
static AtomicLong count1 = new AtomicLong(0L);
static LongAdder count3 = new LongAdder();
public static void main(String[] args) throws Exception {
Thread[] threads = new Thread[1000];
for(int i=0; i<threads.length; i++) {
threads[i] =
new Thread(()-> {
for(int k=0; k<100000; k++) count1.incrementAndGet();
});
}
long start = System.currentTimeMillis();
for(Thread t : threads ) t.start();
for (Thread t : threads) t.join();
long end = System.currentTimeMillis();
System.out.println("Atomic: " + count1.get() + " time " + (end-start));
//-----------------------------------------------------------
Object lock = new Object();
for(int i=0; i<threads.length; i++) {
threads[i] =
new Thread(new Runnable() {
@Override
public void run() {
for (int k = 0; k < 100000; k++)
synchronized (lock) {
count2++;
}
}
});
}
start = System.currentTimeMillis();
for(Thread t : threads ) t.start();
for (Thread t : threads) t.join();
end = System.currentTimeMillis();
System.out.println("Sync: " + count2 + " time " + (end-start));
//----------------------------------
for(int i=0; i<threads.length; i++) {
threads[i] =
new Thread(()-> {
for(int k=0; k<100000; k++) count3.increment();
});
}
start = System.currentTimeMillis();
for(Thread t : threads ) t.start();
for (Thread t : threads) t.join();
end = System.currentTimeMillis();
System.out.println("LongAdder: " + count1.longValue() + " time " + (end-start));
}
}
执行结果如下:
五、锁升级的概念
sync(Object)
1、线程的初始状态是无锁
2、markword:如果当前是第一个线程访问的时候。没个这个线程枷锁,记录当前线程的ID(偏向锁),默认将来不会有第二个线程来抢这个锁,单个线程执行的状态。
3、如果有线程争用:升级为自旋锁,默认自旋10次。
4、10次之后,升级为重量级锁,自己去操作系统申请资源。
关于锁的选择:
加锁代码执行时间断,线程数少,使用自旋锁(CAS)。
加锁代码执行时间长,线程数比较多,使用系统锁(synchronized)。
您的每一份关注,都是我不懈成长的动力