欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java多线程学习总结

程序员文章站 2022-05-04 17:54:36
...

Java多线程学习总结

  1. 线程的概念
  2. 线程的创建
  3. 线程的操作
  4. 多线程(对象锁、信号、控制)

1.线程的概念

讨论线程我们需要明白四个概念:

线程:CPU进行资源调度的最小单位,与进程中其他线程共享资源;

多线程:指的是这个程序(一个进程)运行时产生了不止一个线程;

并行:同一时刻同时执行;

并发:通过cpu调度算法,表现为一段时间上的一起执行;

1.1.线程的状态

新建状态(new)
就绪状态(Runnable)
运行状态(Running)
阻塞状态(Blocked)
死亡状态(Dead)

Java多线程学习总结

2.线程的创建

Java中线程的创建主要分为三种方式:常见的两种为继承Thread类、实现Runnable接口,除此之外,使用ExecutorService、Callable、Future实现有返回结果的多线程(线程池相关知识);

2.1.继承Thread类

通过继承Thread类,重新实现run函数,完成线程的创建

public class TestThread extends Thread{

    @Override
    public void run(){
        while(true){
            TestUnit.print();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

2.2.实现Runnable接口

任何一个类实现了Runnable接口都可以看成线程的执行单元,可以通过Thread对象或线程池对象来完成对其的控制;

Thread t1 = new Thread(new Runnable(){

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            TestUnit.print();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

});

2.3.使用ExecutorService、Callable、Future实现

实现Callable接口的call方法后,可作为线程池(ExecutorService)的执行单元,注册执行;Future用于获取call方法执行结果;


//TestCallable.java
package com.aaron.concurrent.test;

import java.util.concurrent.Callable;

public class TestCallable implements Callable<Object> {

    private int taskNum = -1;

    public TestCallable(int i) {
        this.taskNum = i;
    }

    public TestCallable() {
        // TODO Auto-generated constructor stub
    }

    @Override
    public Object call() throws Exception {
        if(this.taskNum != -1){
            return "My number is : "+ this.taskNum;
        }
        else{
            return " I have no number!";
        }
    }

}


//MainCallable.java
package com.aaron.concurrent.test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class MainCallable {

    public static final int TASK_NUM = 5;
    public static final int MAX_TIME_OUT = 2;

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService pool = Executors.newFixedThreadPool(TASK_NUM); 
        List<Future> returnList = new ArrayList<Future>();  
        for (int i = 0; i < TASK_NUM; i++) {  
            Callable c = new TestCallable(i);  
            Future f = pool.submit(c);   
            returnList.add(f);  
        } 

        // 关闭线程池 ,等待线程执行完毕
        pool.awaitTermination(MAX_TIME_OUT, TimeUnit.SECONDS);
        pool.shutdown(); 

        for (Future f : returnList) {  
            // 从Future对象上获取任务的返回值,并输出到控制台  
            System.out.println(f.get().toString());  
        }  
    }
}

3.线程的操作

3.1.启动线程:start()

start() 方法来启动线程,线程进入就绪状态,并不是马上执行run函数中的代码;

3.2.等待线程执行结束:join()

在代码中调用ThreadA.join(),会等待ThreadA执行完毕后才会执行join之后的代码

3.2.等待线程执行结束:join()

在代码中调用ThreadA.join(),会等待ThreadA执行完毕后才会执行join之后的代码

Java多线程学习总结

3.3.暂停:休眠(sleep)和等待(wait)

sleep和wait方法都能是线程进入阻塞状态,不过还是有不同之处,sleep方法不会交出线程的同步锁(monitor),依然参加时间片的轮转,只是不执行sleep以后的代码,同时sleep属于静态方法,只对调用它的线程有效,如Thread.sleep(100);而wait方法会释放同步锁,线程进入等待池中,不占用cpu,只用等到执行notify操作,才会唤醒该线程进入就绪状态;

Java多线程学习总结

3.3.让步:yield

yield函数使得线程放弃cpu使用权,从运行状态进入就绪状态,由系统进行重新调度,在实际使用中可能会没有效果,如只有一个线程;

Java多线程学习总结

3.4.唤醒:nitify和notifyAll

nitify函数可以唤醒正在共享对象等待池中处于阻塞状态的线程,重新进入到就绪队列,nitifyAll则是唤醒所有的阻塞线程;如果等待池中线程为空或者依旧无法获取到同步锁,则没有效果;

3.5.伪中断:interrupt

interrupt()函数只会修改线程运行的状态,并不会直接关闭线程;并通过异常的方式,提供程序员处理线程关闭的后续工作;如果线程本身处于阻塞状态(sleep/join/wait、io)会直接抛出InterruptedException 异常;否则,除非主动检查线程运行状态,interrupt()将不会有效果,也不会抛出异常;

4. 多线程(对象锁、信号、控制)

4.1.关键字(synchronized)

synchronized关键字有三个关键的问题:
1.哪里能使用
2.锁住的内容
3.锁住的对象

Java多线程同步Synchronized使用分析

4.2.锁机制(Lock接口)

synchronized关键字锁的获取和释放由硬件底层来完成,简单却不灵活,而Java提供的一些锁机制由程序员来来控制,比synchronized要灵活很多;

可以说锁是各种读写控制策略的抽象,程序员可以了解并使用Java提供的读写锁,但是也能根据具体的场景扩展读写规则;

常用的Java读写锁:重入锁(ReentrantLock)、ReentrantReadWriteLock(读写锁)


//--Lock接口
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

//--ReadWriteLock接口
public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

锁相关的概念(和锁机制有关):可重入锁、可中断锁、公平锁、读写锁

java多线程和并发 by人生设计师

4.3.多线程相关概念

4.3.1.死锁

锁没有释放或者同时请求同一个资源,解决一般的思路是锁的资源尽可能少,对资源的访问尽量是有序的;

4.3.1.线程安全和线程安全的集合类

当多个线程访问一个对象,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步,或者调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的;

Java提供一些常用的线程安全集合类(java.util.concurrent包):ConcurrentHashMap、ConcurrentLinkedDeque、CopyOnWriteArrayList、StringBuffer;