多线程与高并发
写下本系列文章,用以记录学习多线程与高并发的过程。
一些比较基础的知识会直接带过,不做详细说明。
线程
下面会从介绍线程到实现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()。
#zs#*
* Thread state for a thread which has not yet started.
#fzs#
NEW,
#zs#*
* 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.
#fzs#
RUNNABLE,
#zs#*
* 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}.
#fzs#
BLOCKED,
#zs#*
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
*
*
{@link Object#wait() Object.wait} with no timeout*
{@link #join() Thread.join} with no timeout*
{@link LockSupport#park() LockSupport.park}*
*
*
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.
#fzs#
WAITING,
#zs#*
* 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:
*
*
{@link #sleep Thread.sleep}*
{@link Object#wait(long) Object.wait} with timeout*
{@link #join(long) Thread.join} with timeout*
{@link LockSupport#parkNanos LockSupport.parkNanos}*
{@link LockSupport#parkUntil LockSupport.parkUntil}*
#fzs#
TIMED_WAITING,
#zs#*
* Thread state for a terminated thread.
* The thread has completed execution.
#fzs#
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异常后就结束线程。)
图解线程
同步锁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具体实现牵扯到了许多方面的知识,新开一篇文章讲,会写在下一篇里。。。。。。