在后续的学习中,发现多线程的水还是相当深的,之前只掌握了线程最基本的操作。因此,最近抽出时间将线程相关的知识好好复习一遍并补充一些线程常用的知识点。

基础知识

  1. 进程
    不同的应用程序运行的过程中都需要在内存中分配自己独立的运行空间,彼此之间不会相互的影响。我们把每个独立应用程序在内存的独立空间称为当前应用程序运行的一个进程。
    进程:它是内存中的一段独立的空间,可以负责当前应用程序的运行。当前这个进程负责调度当前程序中的所有运行细节。
  2. 线程
    在一个进程中,每个独立的功能都需要独立的去运行,这时又需要把当前这个进程划分成多个运行区域,每个独立的小区域(小单元)称为一个线程。
    线程:它是位于进程中,负责当前进程中的某个具备独立运行资格的空间。
  3. 进程和线程的关联
    进程是负责整个程序的运行,而线程是程序中具体的某个独立功能的运行。一个进程中至少应该有一个线程。
  4. 多线程
    • 现在的操作系统基本都是多用户,多任务的操作系统。每个任务就是一个进程。而在这个进程中就会有线程。
    • 真正可以完成程序运行和功能的实现靠的是进程中的线程。
    • 多线程:在一个进程中,我们同时开启多个线程,让多个线程同时去完成某些任务(功能)。实质上是在线程之间进行高速切换。
    • 多线程的目的:提高程序的运行效率。

创建线程

  1. 继承Thread的方式
  2. 声明实现Runnable接口的方式
  3. t.run()和t.start()的区别
    t.run()只是主线程单纯的方法调用,t.start()表示开启了一个新的线程。
  4. 代码实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    public class ThreadTest1 {
    public static void main(String[] args) {
    //1.继承Thread的方式
    Thread t1 = new MyThread();
    Thread t2 = new MyThread();
    t1.start();
    t2.start();
    //2.实现 Runnable 接口的方式
    // Runnable r1 = new MyThread2();
    // Runnable r2 = new MyThread2();
    // Thread t3 = new Thread(r1);
    // Thread t4 = new Thread(r2);
    // t3.start();
    // t4.start();
    }
    }
    class MyThread extends Thread{
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    System.out.println(
    //获得当前线程对象的名字
    Thread.currentThread().getName()+":"+i);
    }
    }
    }
    class MyThread2 implements Runnable{
    @Override
    public void run() {
    for (int i = 0; i < 10; i++) {
    System.out.println(Thread.currentThread().getName()+":"+i); //获得当前线程对象的名字
    }
    }
    }

Java同步关键词

synchronized

  1. 格式
    在重写的run方法内
    synchronized(需要一个任意的对象(锁)){
    代码块中放操作共享数据的代码
    }
  2. 具体功能
    synchronized是java中的一个关键字,也就是说是Java语言内置的特性。如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
    • 获取锁的线程执行完了该代码块,然后线程释放对锁的占有。
    • 线程执行发生异常,此时JVM会让线程自动释放锁。
  3. 缺点
    • 如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能一直无期限地等待下去。
    • 当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象,采用synchronized关键字来实现同步就会导致一个问题:如果多个线程都只是进行读操作,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
  4. 代码实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public class Test1 {
    public static void main(String[] args) {
    MyThread1 t1 = new MyThread1();
    MyThread2 t2 = new MyThread2();
    t1.start();
    t2.start();
    }
    }
    class MyThread1 extends Thread{
    @Override
    public void run() {
    //设置同步锁
    synchronized ("999"){
    System.out.println(getName()+":启动");
    try {
    //线程执行发生异常,此时JVM会让线程自动释放锁
    //int i =1/0;
    Thread.sleep(5000);
    System.out.println(getName()+":5秒后醒了");
    System.out.println(getName()+":结束");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    class MyThread2 extends Thread{
    @Override
    public void run() {
    //设置同步锁
    synchronized ("999"){
    System.out.println(getName()+":启动");
    try {
    Thread.sleep(5000);
    System.out.println(getName()+":5秒后醒了");
    System.out.println(getName()+":结束");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }

有同步锁的运行结果:
Thread-0:启动
Thread-0:5秒后醒了
Thread-0:结束
Thread-1:启动
Thread-1:5秒后醒了
Thread-1:结束
无同步锁运行的结果:
Thread-0:启动
Thread-1:启动
Thread-0:5秒后醒了
Thread-0:结束
Thread-1:5秒后醒了
Thread-1:结束

lock

  1. lock和synchronized的区别
    • Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问。
    • Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
  2. Lock接口

    1
    2
    3
    4
    5
    6
    7
    public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    }
  3. ReentrantLock类
    直接使用lock接口的话,我们需要实现很多方法,不太方便ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法,ReentrantLock,意思是“可重入锁”。

  4. lock()
    lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    public class LockTest {
    static Lock lock1 = new ReentrantLock(); //创建锁
    static Lock lock2 = new ReentrantLock(); //创建锁
    public static void main(String[] args) {
    MyThread3 t1 = new MyThread3();
    MyThread4 t2 = new MyThread4();
    t1.start();
    t2.start();
    }
    }
    class MyThread3 extends Thread{
    @Override
    public void run() {
    LockTest.lock1.lock(); //上锁
    try {
    System.out.println(getName()+":得到锁");
    System.out.println(getName()+":开始休眠3秒");
    Thread.sleep(3000);
    System.out.println(getName()+":结束休眠");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }finally {
    LockTest.lock1.unlock(); //释放锁
    }
    }
    }
    class MyThread4 extends Thread{
    @Override
    public void run() {
    LockTest.lock1.lock(); //上锁
    try {
    System.out.println(getName()+":得到锁");
    System.out.println(getName()+":开始休眠3秒");
    Thread.sleep(3000);
    System.out.println(getName()+":结束休眠");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }finally {
    LockTest.lock1.unlock(); //释放锁
    }
    }
    }

同一把锁的运行结果:
Thread-0:得到锁
Thread-0:开始休眠3秒
Thread-0:结束休眠
Thread-1:得到锁
Thread-1:开始休眠3秒
Thread-1:结束休眠
不是同一把锁的运行结果(相当于没加锁):
Thread-1:得到锁
Thread-1:开始休眠3秒
Thread-0:得到锁
Thread-0:开始休眠3秒
Thread-1:结束休眠
Thread-0:结束休眠
没有释放锁的结果:
Thread-0:得到锁
Thread-0:开始休眠3秒
Thread-0:结束休眠
(无限期等待)…

  1. tryLock()
    tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    public class TryLockTest {
    static Lock lock = new ReentrantLock(); //创建锁
    public static void main(String[] args) {
    MyThread5 t1 = new MyThread5();
    MyThread6 t2 = new MyThread6();
    t1.start();
    t2.start();
    }
    }
    class MyThread5 extends Thread{
    @Override
    public void run() {
    boolean tryLock = TryLockTest.lock.tryLock();
    System.out.println(getName()+" "+tryLock);
    if(tryLock){
    try {
    System.out.println(getName()+":得到锁");
    // System.out.println(getName()+":开始休眠3秒");
    // Thread.sleep(3000);
    // System.out.println(getName()+":结束休眠");
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
    System.out.println(getName()+":释放锁");
    TryLockTest.lock.unlock();
    }
    }
    }
    }
    class MyThread6 extends Thread{
    @Override
    public void run() {
    boolean tryLock = TryLockTest.lock.tryLock();
    System.out.println(getName()+" "+tryLock);
    if(tryLock){
    try {
    System.out.println(getName()+":得到锁");
    // System.out.println(getName()+":开始休眠3秒");
    // Thread.sleep(3000);
    // System.out.println(getName()+":结束休眠");
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
    System.out.println(getName()+":释放锁");
    TryLockTest.lock.unlock();
    }
    }
    }
    }

直接运行结果:
Thread-1 true
Thread-1:得到锁
Thread-1:释放锁
Thread-0 true
Thread-0:得到锁
Thread-0:释放锁
先得到所锁的占用锁一段时间的运行结果:
Thread-0 true
Thread-0:得到锁
Thread-0:开始休眠3秒
Thread-1 false
Thread-0:结束休眠
Thread-0:释放锁

  1. tryLock(long time, TimeUnit unit)
    方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
  2. lockInterruptibly()
    • 当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就是说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
      注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。
    • 因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
    • 而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class MyInterruptibly {
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
MyThread7 t1 = new MyThread7();
MyThread7 t2 = new MyThread7();
t1.start();
t2.start();
try {
Thread.sleep(2000); //主线程休眠2秒,此时锁还被第一个运行的线程占用
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();
}
}
class MyThread7 extends Thread{
@Override
public void run() {
try {
MyInterruptibly.lock.lockInterruptibly(); //上锁
System.out.println(getName()+":得到锁");
System.out.println(getName()+":开始休眠3秒");
Thread.sleep(3000);
System.out.println(getName()+":结束休眠");
} catch (InterruptedException e) {
System.out.println(getName()+":被打断");
e.printStackTrace();
}finally {
MyInterruptibly.lock.unlock(); //释放锁
}
}
}

运行结果:
Thread-0:得到锁
Thread-0:开始休眠3秒
Thread-1:被打断
java.lang.InterruptedException
at java.util.concurrent.locks…
Exception in thread “Thread-1” java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)…
Thread-0:结束休眠

  1. ReadWriteLock接口
    一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。

    1
    2
    3
    4
    5
    6
    public interface ReadWriteLock {
    Lock readLock(); //读锁
    Lock writeLock(); //写锁
    }
  2. ReentrantReadWriteLock
    ReentrantReadWriteLock实现了ReadWriteLock接口。最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。
    使用读写锁,可以实现读写分离锁定,读操作并发进行,写操作锁定单个线程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    public class MyReadWriteLock {
    public static void main(String[] args) {
    MyThread8 t1 = new MyThread8();
    MyThread8 t2 = new MyThread8();
    t1.start();
    t2.start();
    }
    }
    class MyThread8 extends Thread{
    private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    @Override
    public void run() {
    //读操作
    rwl.readLock().lock();
    try {
    long start = System.currentTimeMillis();
    while(System.currentTimeMillis()-start<=1){
    System.out.println(getName()+":正在进行读操作");
    }
    System.out.println( getName()+":读操作完毕");
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
    rwl.readLock().unlock();
    }
    //写操作
    rwl.writeLock().lock();
    try {
    long start = System.currentTimeMillis();
    while(System.currentTimeMillis()-start<=1){
    System.out.println(getName()+":正在进行写操作");
    }
    System.out.println( getName()+":写操作完毕");
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
    rwl.writeLock().unlock();
    }
    }
    }

最后更新: 2020年07月27日 03:51

原始链接: https://www.lousenjay.top/2018/08/19/多线程详解/