JAVA的多线程与高并发(一)、线程与同步锁

多线程与高并发

写下本系列文章,用以记录学习多线程与高并发的过程。

一些比较基础的知识会直接带过,不做详细说明。

线程

下面会从介绍线程到实现java多线程来讲。

线程的概念

线程的概念在我之前写的《Java开发者需要了解的硬件知识 (二)、操作系统篇》中已经讲述过了。搬过来用一用。

进程是OS分配资源的基本单位,线程是执行调度的基本单位。

进程最重要的分配资源是:独立的内存空间。

线程调度执行(线程共享进程的内存空间,没有自己独立的内存空间)

img

线程在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种

  1. 新生状态(NEW):线程刚被创建出来,还未调用start()方法
  2. 可运行状态(RUNNABLE):处于该状态的线程随时可以被操作系统调度执行,它还可以根据线程工作情况细分为①就绪状态Ready②正在执行状态Running。
  3. 阻塞状态(BLOCKED):线程进入同步代码块前,需要获取锁,而在等待获取锁时,线程就会进入阻塞状态,获得锁后则进入就绪状态。
  4. 等待状态(WAITING):等待状态的线程都在等待另一个线程的特定操作(以解除它的等待状态),等待状态结束回到就绪状态。
  5. 超时等待状态(TIMED_WAITING):线程在等待另一个线程的特定操作后的指定时间内,会处于超时等待状态,直至操作完成并等待足够的时间后线程会被自动唤醒,并回到就绪状态
  6. 终结状态(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,**优先级高并不一定会优先执行,只是优先执行的概率更高。**

image-20200913170857907

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具体实现牵扯到了许多方面的知识,新开一篇文章讲,会写在下一篇里。。。。。。

posted @ 2020-09-18 00:20  Rooooy7  阅读(518)  评论(0编辑  收藏  举报