JAVA的多线程与高并发(一)、线程与同步锁
多线程与高并发
写下本系列文章,用以记录学习多线程与高并发的过程。
一些比较基础的知识会直接带过,不做详细说明。
线程
下面会从介绍线程到实现java多线程来讲。
线程的概念
线程的概念在我之前写的《Java开发者需要了解的硬件知识 (二)、操作系统篇》中已经讲述过了。搬过来用一用。
进程是OS分配资源的基本单位,线程是执行调度的基本单位。
进程最重要的分配资源是:独立的内存空间。
线程调度执行(线程共享进程的内存空间,没有自己独立的内存空间)
线程在Linux中的实现
线程在Linux就是一个普通的进程,只不过和其他进程共享资源(内存空间,全局数据等。。。)
在JVM中的线程
创建一个JVM中线程,即申请一个操作系统的线程(重量级线程),1:1关系
JAVA中的线程
创建线程的两种方式
public class createThred {
static class Thread1 extends Thread {
@Override
public void run() {
System.out.println("Thread1");
}
}
static class Runnable1 implements Runnable {
public void run() {
System.out.println("Thread2");
}
}
public static void main(String[] args) {
// 第一种:写一个类继承Thread类,重写它的run方法
Thread1 thread1 = new Thread1();
thread1.start();
// 第二种:写一个类实现Runnable接口,然后使用新类作为参数来创建新线程
Thread thread2 = new Thread(new Runnable1());
thread2.start();
// 第二种的拓展,在Jdk8后使用lambda表达式
Thread thread3 = new Thread(() -> System.out.println("Thread3"));
thread3.start();
}
}
创建线程有两种方式,启动线程上有三种方式。
前两种就是创建线程的那两种,第三种启动线程的方式是在线程池中创建线程(使用Executors.newCachedThread,实际上内部还是前两种来实现的)
线程的状态
JAVA中线程的状态可以主要分成6种
- 新生状态(NEW):线程刚被创建出来,还未调用start()方法
- 可运行状态(RUNNABLE):处于该状态的线程随时可以被操作系统调度执行,它还可以根据线程工作情况细分为①就绪状态Ready②正在执行状态Running。
- 阻塞状态(BLOCKED):线程进入同步代码块前,需要获取锁,而在等待获取锁时,线程就会进入阻塞状态,获得锁后则进入就绪状态。
- 等待状态(WAITING):等待状态的线程都在等待另一个线程的特定操作(以解除它的等待状态),等待状态结束回到就绪状态。
- 超时等待状态(TIMED_WAITING):线程在等待另一个线程的特定操作后的指定时间内,会处于超时等待状态,直至操作完成并等待足够的时间后线程会被自动唤醒,并回到就绪状态
- 终结状态(TERMINATED):线程完成了他的执行方法后即进入终结状态,而且终结状态的线程无法再start()。
/** * Thread state for a thread which has not yet started. */ NEW, /** * Thread state for a runnable thread. A thread in the runnable * state is executing in the Java virtual machine but it may * be waiting for other resources from the operating system * such as processor. */ RUNNABLE, /** * Thread state for a thread blocked waiting for a monitor lock. * A thread in the blocked state is waiting for a monitor lock * to enter a synchronized block/method or * reenter a synchronized block/method after calling * {@link Object#wait() Object.wait}. */ BLOCKED, /** * Thread state for a waiting thread. * A thread is in the waiting state due to calling one of the * following methods: * <ul> * <li>{@link Object#wait() Object.wait} with no timeout</li> * <li>{@link #join() Thread.join} with no timeout</li> * <li>{@link LockSupport#park() LockSupport.park}</li> * </ul> * * <p>A thread in the waiting state is waiting for another thread to * perform a particular action. * * For example, a thread that has called {@code Object.wait()} * on an object is waiting for another thread to call * {@code Object.notify()} or {@code Object.notifyAll()} on * that object. A thread that has called {@code Thread.join()} * is waiting for a specified thread to terminate. */ WAITING, /** * Thread state for a waiting thread with a specified waiting time. * A thread is in the timed waiting state due to calling one of * the following methods with a specified positive waiting time: * <ul> * <li>{@link #sleep Thread.sleep}</li> * <li>{@link Object#wait(long) Object.wait} with timeout</li> * <li>{@link #join(long) Thread.join} with timeout</li> * <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li> * <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li> * </ul> */ TIMED_WAITING, /** * Thread state for a terminated thread. * The thread has completed execution. */ TERMINATED;
线程的基本方法
1、sleep(long millis)
使线程睡眠millis毫秒,线程进入时间等待状态(TIMED_WAITING),睡眠完后线程回到就绪状态。
2、yeild()
使当前线程进入等待队列(就绪状态),等待操作系统下一次调度。
3、join()
在T1线程中调用T2的join()方法时,T1线程则会等待T2线程结束才进入等待队列(就绪状态)。(调用自己线程的join方法是没有意义的)
4、isAlive()
判断线程是否活动,返回boolean,在调用start()后与线程死亡前都是活动状态。
5、getPriority() / setPriority(int newPriority)
获取线程优先级与设置线程优先级,下图为线程优先级参考表,一般为5,**优先级高并不一定会优先执行,只是优先执行的概率更高。**
6、interrupt()
中断线程,interrupt() 并不能真正的中断线程,这点要谨记。需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就需要这样做:
1. 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
2. 在调用阻塞方法时正确处理InterruptedException异常。(例如:catch异常后就结束线程。)
7、wait() -- 来自Object方法
某个对象调用wait方法,会让当前线程进入等待状态,需要注意的是,这个wait()方法会释放线程持有的锁,意味着进入等待状态后,别的线程就有机会获得该线程释放出来的锁了。
8、notify() / notifyAll() -- 来自Object方法
某对象调用notify()方法,随机唤醒一个wait线程,让其进入就绪状态。
某对象调用notifyAll()方法,唤醒全部wait线程,让其进入就绪状态。
这部分牵扯到对象锁池的知识,可以参考 https://blog.csdn.net/djzhao/article/details/79410229
图解线程
同步锁Synchronized
本篇只是简要说明synchronized关键字的一些使用场景和使用细节。
在下一篇中,会详细的分析synchronized锁的实现和它的内部原理。
synchronized的锁定对象
1、指定对象
你可以指定一个对象,使用synchronized关键字来加锁
public class T {
private int count = 10;
private Object o = new Object();
public void m() {
// 你也可以指定this对象
// synchronized (this)
synchronized (o)
{
count--;
}
}
}
2、修饰方法
也可以直接在方法属性中使用synchronized,如此一来,会默认把调用该方法的对象作为锁(也就是this)
public class T {
private int count = 10;
public synchronized void m() {
// 此时与上面第一种锁定this对象的效果是一样的
count--;
}
}
3、修饰静态方法
使用静态方法是不需要对象的,如果synchronized修饰静态方法,那么锁定的对象将是T.class,类的对象
public class T {
private int count = 10;
public synchronized static void m() {
// 此时与上面第一种锁定this对象的效果是一样的
count--;
}
}
可重入性
如果在同一个线程中,对于一个对象,允许同步方法A的代码块中调用同步方法B。(允许同一个线程可以获取多把锁)
比如下面这块代码
public class T {
synchronized void m1 () {
System.out.println("m1");
m2();
}
synchronized void m2 () {
System.out.println("m1");
}
}
synchronized必须是可重入锁,因为java中A继承B,A重写B的方法时的super.m()是可以正常执行的(无论这个方法是不是加了synchronized)。如果不是可重入的,那么这样的默认继承重写就没办法实现。
异常
如果在程序执行中抛出异常,那么锁会被释放。可能会因此产生不可预知的结果。
如果不想释放锁,可以主动catch产生的异常。
锁升级
synchronized在JDK早期,使用时会向操作系统申请重量级锁。在随后的升级版本中,加入了锁升级的概念。
意味着,synchronized不再是直接向操作系统申请重量级锁,而是一步步升级为重量级锁。
其中的过程有:
1、偏向锁
当没有任何竞争时(单线程执行synchronized代码时)
2、自旋锁
少量线程竞争
3、系统锁
大量线程竞争
具体实现
由于synchronized具体实现牵扯到了许多方面的知识,新开一篇文章讲,会写在下一篇里。。。。。。