多线程总结

并发并行

并行:同一时刻同时处理多个任务。简单理解:一边......一边......

并发:同一时间段内,多个任务交替执行。简单理解:一会......一会......

进程和线程

进程是指正在运行的程序。

特点
  • 进程是系统运行的基本单位
  • 每个进程都有自己的独立空间,一组资源
  • 每一个进程内部数据和状态都是完全独立的
  • 每一个应用程序运行时都会产生一个进程

线程

线程是进程中执行运算的最小单位,一个进程执行过程中可以产生多个线程,线程必须在某个进程内执行。

一个进程中至少有一个线程,也可以有多个线程,这样的程序就称为多线程程序。

进程和线程的区别

进程:有独立的内存空间,进程中的数据存放空间(堆和栈)是独立的,至少有一个进程。

线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小得多。

资源分配给进程,同一进程的所有线程共享该进程的所有资源(如内存)。

处理器分配给线程,即真正在处理器上运行的是线程。

Java程序中至少包含两个线程:一个main()线程,另外一个是垃圾回收机制线程。

多线程和多进程的区别

本质区别在于,每个进程拥有一套自己的变量,而线程则是共享数据。

线程调度

分时调度:所有线程轮流拥有CPU的使用权,平均分配每个线程占用CPU的时间。

抢占式调度:优先让优先级高的线程使用CPU,如果优先级相同,会随机选择一个线程。

创建线程

每个程序至少自动拥有一个线程,称为主线程,当程序加载到内存中时启动主线程,main()就是主线程的入口。

创建线程的三种方法

继承Thread类,重写run(),通过start()启动线程。

public class MyThread extends Thread{
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        //run方法里面写线程具体的任务
        for (int i = 0; i < 10; i++){
            System.out.println(Thread.currentThread().getName());
        }
    }
}
public class MyThreadTest {
    public static void main(String[] args) {
        //创建MyThread对象
        MyThread myThread = new MyThread("我的线程");
        //开启一个新的线程 实际上是去执行重写的run方法
        myThread.start();
        for (int i = 0; i < 100; i++){
            System.out.println("主线程运行了" + i + "次");
        }
    }
}

实现Runnable接口的方式创建

实现Runnale接口,重写run()方法

public class RunnableDemo {
    public static void main(String[] args) {
        Thread.currentThread().setName("主线程");
        Thread t = new Thread(new MyRunnaable());
        t.setName("子线程");
        t.start();
        //子线程运行后,主线程继续运行
        for (int i = 0; i < 10; i ++){
            System.out.println(Thread.currentThread().getName() + "运行了" + i + "次");
        }
    }
}
class MyRunnaable implements Runnable{
    @Override
    public void run() {
        //写子线程要执行的任务
        //输出1-100
        for (int i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + "运行了" + i + "次");
        }
    }
}

Runnnable对象仅仅作为Thread对象的target,Runnale实现类里包含的run()仅仅作为线程的执行体,实际的线程对象依然是Thread实例,只是该实例负责执行该其target的run()。

Thread和Runnable的区别

实现Runnable接口比继承Thread的优势
  1. 适合多个相同的程序代码的线程去共享一个资源
  2. 避免单继承的局限性
  3. 增加程序的健壮性,实现解耦,代码可以被多个线程共享
  4. 实现Runnale接口很容易实现资源共享
  5. 线程池也只能放入实现Runnable或Callable类线程,不能直接放入继承Thread的类

实现Callable接口的方式创建

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class CallableDemo implements Callable {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 10; i++){
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable callable = new CallableDemo();
        FutureTask futureTask = new FutureTask<>(callable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(futureTask.get());
    }
}

Callable和Runnale比较相似,Runnable不返回结果,也不能抛出检查异常,Callable没有返回结果。

Callable和Runnable的区别

  • Callable的call()有返回值,Runnale的run()没有返回值。
  • call()可以抛出检查异常,run()不能。
  • Callable接口的结果可以通过Future对象来获取。

FutureTask类

FutureTask是一个实现了Future和Runnable的类,它可以表示一个异步计算的结果,FutureTask可以包装一个Callable和Runnale对象,通过get()获取异步计算的结果。

Callable call = new Myacallable();
FutureTask task = new FutureTask(call);
Thread thread = new Thread(task);
thread.start();
构造方法

public FutureTask(Runnable runnable, V result) :创建一个FutureTask在运行时执行给定的Runnable,并且让get在完成时返回结果。

public FutureTask ( Callable < V > callable ):创建一个FutureTask在运行时执行给定的callable。

Thread

构造方法

public  Thread():创建一个新线程

public Thread(String name):分配一个指定名字的线程

public Thread(Runnable runnable):分配一个带有指定目标的线程

public Thread(Runnable runnable):分配一个带有指定目标的线程,并指定名字

常用方法
public static Thread currentThread(): 返回对当前正在执行的线程
public void run() : 表示线程的任务,如果线程是使用Runnable对象构造的,则执行Runnale的run(),否则此方法不执行任何操作并返回,所有Thread子类都应该覆盖此方法。
public synchronized void start():线程开始执行。
public static native void sleep(long millis) :让线程睡眠指定的毫秒数,也就是暂停执行,在此期间,线程不会丢失任何的CPU使用权。
public final String getName():返回此线程的名称。
public final synchronized void setName(String name):修改线程名称。
public final int getPriority():返回线程的优先级。
public final void setPriority(int newPriority):更改线程优先级,1-10之间,优先级高的只是比优先级低的获得CPU使用权的概率更高,并不意味着一定会得到CPU使用权。
public final native boolean isAlive() :线程是否活着,就是一个线程已经启动并且尚未结束。
public final void join() :等待线程死亡,等于join(0)
public void interrupt() :中断这个线程
public static boolean interrupted() :测试当前线程是否中断。该方法可以清除线程的中断状态(设置中断状态为false),如果这个方法被调用两次,那么第二次调用返回false,
public static boolean interrupted() :测试当前线程是否中断。线程的中断状态不受此方法影响。
public static native void yield():导致当前线程处于让步状态。但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程如果有其他可运行线程拥有与此线程相等的优先级,那么这些线程会被调度,虽然是让步,但是此线程也会和其他线程抢夺CPU使用权,并不一定让的出去。与sleep方法类似,但是不能又用户指定暂停时间。
public State getState() :返回此线程的状态,返回值是 Thread 的⼀个内部类,枚举类 State 。线程状态可以是:
new:已经创建好线程了,但是尚未启动。
Runnable:可以运行的线程处于此状态。就是调用start()方法后的状态,具备执行资格,等待CPU调用。
Blocked:被阻塞的线程处于此状态。
Waiting:正在等待其他线程执行指定动作的线程处于此状态。
TIMED_WAITING :正在等待另⼀个线程执⾏动作达到指定等待时间的线程于此状态 ( sleep(1000) , join(1000) ),只等待规定的时间,超过规定时间就会和其他线程抢占资源。
TERMINATED 已退出的线程处于此状态。
Thread.sleep(long millis) :当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
obj.wait() , 当前线程调用对象的wait()方法 ,当前线程释放对象锁 ,进入等待队列 。依靠
notify()/notifyAll() 唤醒或者 wait(long timeout) timeout时间到自动唤醒。
obj.notify() 唤醒在此对象监视器上等待的单个线程,选择是任意性的。 notifyAll() 唤醒在此对象监视器上等待的所有线程。

守护线程

守护线程类似于一个默默无闻的小跟班,依赖于它所守护的那个线程,它守护的那个线程结束了,它也就结束了。如果所有线程都执行完毕了,没有要执行的了,它也会结束。

守护线程的终止自身无法控制,因此不能把IO、File等重要操作逻辑分配给它。

守护线程的作用

GC垃圾回收线程:

就是⼀个经典的守护线程,当我们的程序中不再有任何运⾏的Thread,程序就不会再产⽣垃圾,垃圾回收器也就⽆事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会⾃动离开。它始终在低级别的状态中运⾏,⽤于实时监控和管理系统中的可回收资源。
守护线程应用场景:为其他线程提供服务支持;任何情况下,程序结束时这个线程必须正常且要立刻关闭,就可以作为守护线程来使用。
守护线程实例:
public class WatchThread extends Thread{
    //创建守护线程,检查C盘容量,少于1G发出预警
    File file = new File("C:");
    @Override
    public void run() {
        while (true){
            long space = file.getFreeSpace();
            System.out.println("C盘剩余空间:" + space / 1024 / 1024 / 1024 + "G");
            if(space < 1024 * 1024 * 1024){
                System.out.println("C盘空间不足");
            }
            try {
                //暂停5秒后继续监视
                sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        WatchThread thread = new WatchThread();
        //设置为守护线程,在start之前设置
        thread.setDaemon(true);
        thread.start();
    }
}

线程同步

为了解决线程安全问题,可以使用线程同步的思想,最常见的方案就是加锁。意思还是每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他先才能加锁再进来。

使用多个线程访问同一资源的时候,就会出现线程安全问题,java提供了同步机制 synchronized来解决。
java提供的三种同步机制
  1. 同步代码块
  2. 同步方法
  3. 锁机制
同步代码块

就是把访问共享数据的代码锁起来

synchronized(锁对象){
// ...访问共享数据的代码...
}

锁对象:对象的同步锁只是一个概念,可以想象成在任意对象上标记了一个锁。

如何选择锁对象:

  • 建议把共享资源作为锁对象,不要将随便无关的对象作为锁对象。
  • 对于实例方法,建议把this作为锁对象。
  • 对于静态方法,建议把类的字节码(类名.class)作为锁对象。

CountDownLatch:一种同步辅助工具,允许一个或多个线程等待,直到其他线程中执行的一组操作完成。

CountDownLatch latch = new CountDownLatch(10);
latch.countDown(); // 每一个线程运行结束 数量减一
latch.await(); // 等待这个计数器中数字清空
同步方法

使用synchronized修饰的方法就是同步方法,保证线程A执行的时候其他线程只能在方法外等待。就是把一个方法锁住,一个线程调用这个方法,另一个线程就执行不了,只有等上一个线程执行结束,下一个线程调用才能继续执行。

同步方法的锁对象:实例方法的锁对象是类的字节码对象(类名.class),静态方法的锁对象是this,也就是方法的调用者。

同步方法和同步代码块的区别

同步方法是将方法中的所有代码锁住,同步代码块只是把方法中的部分代码锁住。

创建一个 FutureTask 在运行时执行给定的厂家
Runnable ,并安排 get 在成功完成时返回给定的结果
创建一个 FutureTask 在运行时执行给定的
Runnable ,并安排 get 在成功完成时返回给定的结果。

你可能感兴趣的:(java,开发语言)