多线程与高并发
基础概念
创建一个线程的两种方式:
- 继承Thread
class MyThread extends Thread{ } new MyThread().start();
- 实现Runnable接口
class MyThread implements Runnable { } new Thread(new MyRun()).start();
面试时候有时候会问到,启动线程的三种方式:
- Thread、
- Runnable
- 通过Lambda表达式或者Executors.newCachedThread(线程池) 第三种说白了也是用的第一和第二的两种的其中一种
暂停线程的几种方式:
- sleep,当前线程暂停一段时间,让给别的线程
- Yield,非常谦让的退出一下,让出一下cpu,先从cpu上运行先离开,进入到一个等待队列中,至于cpu从队列中拿出哪个,这个就是随机的了
- join,把其他线程加入到我运行的线程中,相当于两个线程t1,t2,当在t1中调用了t2.join的时候,t2执行完毕,t1才能继续执行,这块有个面试题:怎么能够保证三个线程按照顺序执行? 使用join方法就可以保证按照想要的顺序执行。
工作中,我们在多线程中,不建议使用stop()方法,该方法已经被废止,中断线程我们使用interrupt方法,打断,但是中止完方法,必须catch出exception,做相应的操作。一般很少用这几个方法。
获取一个线程的状态:使用getState()方法
synchronized 关键字
是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
synchronized 关键字加了以后,不加锁的效率是加锁的100倍,所以能不加锁尽量不要加锁
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);
}
}
}
锁定当前对象
public class T {
private int count = 10;
public void m() {
synchronized(this) { //任何线程要执行下面的代码,必须先拿到this的锁
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
}
//或者这样写,这两种方法是等值的
public class T {
private int count = 10;
public synchronized void m() { //等同于在方法的代码执行时要synchronized(this)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
当一个方法是静态方法的时候,static修饰的时候,synchronized修饰的话,修饰该方法,相当于给该类加锁即syn(T.class)
public class T {
private static int count = 10;
public synchronized static void a() { //这里等同于synchronized(FineCoarseLock.class)
count--;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void n() {
synchronized(T.class) {
count --;
}
}
}
synchronized是可重入锁,一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁
public class T {
synchronized void m1() {
System.out.println("m1 start");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();
System.out.println("m1 end");
}
synchronized void m2() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m2");
}
public static void main(String[] args) {
new T().m1();
}
}
synchronized的底层实现
JDK早期的时候,实现是重量级 --》 OS操作系统申请锁
后来改进:
锁升级概念:可以查下 我就是厕所所长(一,二)
锁升级文章一
锁升级文章二
当我们使用sync(object) 上来后,先在Object头上面,markword记录这个线程ID,并没有加锁,这个叫偏向锁,如果有线程争用的话,偏向锁会升级为自旋锁,就是相当于另外一个线程一直在while(true)等待该线程结束,不会加入到cpu队列中去,自旋锁默认情况下旋转10次,若10次后,则升级为重量级锁—》OS操作系统锁(原子类)
那么有个问题:什么时候用自旋锁,什么时候用重量级锁?
执行时间比较短(加锁代码),线程数少,用自旋锁,
执行时间比较长,线程数多,用系统锁