单机高并发应该掌握的线程基础:线程状态,异常与锁等
1.进程、线程、协程的概念:
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();
//new T1().start();
for(int i=0; i<10; i++) {
try {
TimeUnit.MICROSECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main");
}
}
}
执行new T1().run()方法时先输出10个T1,再输出10个main,这是因为此时run就是一个普通的方法,在主线程main里面,先执行run()方法,再执行循环。
执行new T1().start()时,结果如下:
T1和main交替出现是因为,调用start方法的时候,新创建了一个线程,主线程和新建的线程交替执行,执行的顺序由CPU决定的,所以导致打印的结果不是唯一的,多执行几次代码,得到的结果是不一样的。
2.创建线程的几种方法以及代码实现:
面试题:启动线程的三种方式?
2.1继承Thread方法,重写run方法:
2.2实现runnable接口,重写run方法:
2.3lambda表达式也可以实现,一般还有线程池但是底层原来还是继承Thread方法:
具体实现代码:
public class T02_HowToCreateThread {
static class MyThread extends Thread{
@Override
public void run(){
System.out.println("MyThread!!");
}
}
static class MyRunnable implements Runnable{
@Override
public void run(){
System.out.println("MyRunnable");
}
}
public static void main(String[] args) {
new MyThread().start();
new Thread(new MyRunnable()).start();
new Thread(()->{
System.out.println("Lambda");
}).start();
}
}
输出结果:
MyThread!!
MyRunnable
Lambda
阿里编码开发手册中说:
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName()));
singleThreadPool.shutdown();
3.sleep、yield、join方法的使用:
sleep:
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
* 意思是说:当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。 该线程不会失去任何监视器的所有权。
* @param millis
* the length of time to sleep in milliseconds
* 毫秒为单位
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
其实主要的就是他是让其他线程走,自己进行休眠,但是自己却不会释放对象锁,也就是说,如果有同步锁的时候,其他线程不能访问共享数据。
注意该方法要捕获异常 比如有两个线程同时执行(没有Synchronized),一个线程优先级为MAX_PRIORITY,另一 个为MIN_PRIORITY,如果没有Sleep()方法,只有高优先级的线程执行完成后,低优先级 的线程才能执行;但当高优先级的线程sleep(5000)后,低优先级就有机会执行了。 总之,sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的 线程有执行的机会。
yield:
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
* 意思是说 提示当前线程可以让处理器忽略当前线程,去处理其他线程
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
* 它是一种启发式尝试,用于改善线程之间的相对进展,否则会过度利用CPU。 它的使用应与详细的分析和基准测试相结合,以确保它实际上具有所需的效果。
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package. * 使用这种方法很少是合适的。 它可能对调试或测试目的很有用,它可能有助于重现因竞争条件而产生的错误。 在设计并发控制结构(如中的那些)时,它也可能很有用
*/
public static native void yield();
yield() 这个方法从以上注释可以看出,也是一个休眠自身线程的方法,同样不会释放自身锁的标识,区别在于它是没有参数的,即yield()方 法只是使当前线程重新回到可执行状态,
所以执行yield()的线程有可能在进入到可执行状态 后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也 和sleep()方法不同。
join:
/**
* Waits for this thread to die.
* 等待线程死亡
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0); // 调用了有参方法
}
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
* 这个线程最多等多少毫秒,如果超时了,就会进行线程死锁
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final synchronized void join(long millis)
throws InterruptedException
保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。
public class T03_Sleep_Yield_Join {
public static void main(String[] args) {
//testSleep();
// testYield();
testJoin();
}
static void testSleep() {
new Thread(()->{
for(int i=0; i<10; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
static void testYield() {
new Thread(()->{
for(int i=0; i<10; i++) {
System.out.println("A" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
new Thread(()->{
for(int i=0; i<10; i++) {
System.out.println("------------B" + i);
if(i%10 == 0) Thread.yield();
}
}).start();
}
static void testJoin() {
Thread t1 = new Thread(()->{
for(int i=0; i<10; 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(); //当执行t2时,T1必须现在执行完才可以执行T2
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0; i<10; i++) {
System.out.println("B" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
t1.start();
}
}
4.线程状态:
public class T04_ThreadState {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println(this.getState());
for(int i=0; i<10; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
}
public static void main(String[] args) {
Thread t = new MyThread();
System.out.println(t.getState());
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getState());
}
}
5.线程同步:synchronized关键字:
synchronized锁的是对象不是代码,说的是this对象,如果是静态方法说的是xx.class,
注意:锁定对象的时候不能用String常量,Integer,Long
锁定方法和非锁定方法可以同时执行
底层实现:
jdk早期重量级实现:概念:找操作系统申请锁,必须进入内核态,所以是重量级的。
锁升级:jdk1.5以后
改进点:hotSpot实现:markword记录这个线程的id(偏向锁),线程再次进入的时候,看markword是否是自己的线程id,如果是直接拿锁,获取资;如果有线程争用:线程争用的前提是该锁已经被其他线程获取,所以本身升级为自旋锁,默认旋10次,如果10次以后还得不到这把锁的话就升级为重量级锁,向os申请锁。注意锁只能升级不能降级。
在什么情况使用自旋锁比较好:自旋锁虽然占用cpu,但是不进入内核态,只在用户态。加锁代码执行时间短,并且线程的数量不会太多的时候用自旋锁。执行时间长,并且线程资源多的时候用系统锁。
多个线程同时访问一个资源的时候:必须对资源上锁
5.1对某个对象加锁:
/**
* synchronized关键字
* 对某个对象加锁
*/
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
synchronized(o) { //任何线程要执行下面的代码,必须先拿到o的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
5.2对某个static方法加锁:
/**
* synchronized关键字
* 对某个对象加锁
*/
public class T {
private static int count = 10;
public synchronized static void m() { //这里等同于synchronized(FineCoarseLock.class)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void mm() {
synchronized(T.class) { //考虑一下这里写synchronized(this)是否可以? 不可以
count --;
}
}
}