多线程

注:该博客或为自己所写以供个人学习之用

一.线程与进程    

1.几乎所有的操作系统都支持同时多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程当一个程序运行时,内部可能包含多个顺序执行流,每个执行流就是一个线程。也就是说一个进程有1个或者多个线程构成。

      当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位。

      一般而言,进程包含如下三个特征。

①独立性:进程是系统中独立存在的实体,她可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。

②动态性:进程和程序的区别在于,程序知识一个惊天的指令集和,而进程是一个正在系统中活动的指令集合。

③并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

归纳:操作系统可以同时执行多个任务,每个任务就是进程,进程可以同时执行多个任务,每个任务就是线程。

注:并发性和并行性是连个概念,并行指在同一时刻,有多条指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令别迅速轮换执行,使得在宏观上具有多个进程同时执行的效果。并行运行智能在多核CPU中发生。

 

二.线程与进程的资源问题

      线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,她与父进程的其他线程共享该进程所拥有的全部资源。因为多个线程共享父进程里的全部资源,因此编程更加方便;但必须更加小心,因为需要确保线程不会妨碍进程里的其他线程。

      线程共享的环境包括:进程代码段、进程的公有数据等。利用这些共享的数据,线程很容易实现互相之间的通信。

三.线程的创建和启动

java使用Thread类代表线程,所有的线程对象都必须是Thread类或器子类的实例。

1.继承Thread类创建线程类

步骤:

①定义Thread类的子类,并且重写该类的run()方法,该run()方法的方法体就代表线程需要完成的任务。因此把run()方法称为线程执行体。

②创建Thr子类的实例,即创建了线程对象。

③调用线程对象的start()方法来启动该线程。

 

package com.bluemsun.study;

/**
 * Created by mafx on 2018/9/16.
 * @author mafx
 */
public class ExtendsThreadClassCreatThread extends Thread{
    private int i;
    @Override
    public void run(){
        for(;i<100;i++){
            System.out.println(getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20){
                //创建和启动线程
                new ExtendsThreadClassCreatThread().start();
                new ExtendsThreadClassCreatThread().start();
            }
        }
    }
}

注:使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。

   2.实现Runable接口创建线程类 

实现Runnable接口来创建并启动多线程的步骤如下。

①定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

②创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

  注:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅仅作为线程执行体。二十几的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

package com.bluemsun.study;

/**
 * Created by mafx on 2018/9/16.
 */
public class ImplementRunnableCreatThread implements Runnable{
    private int i;
    @Override
    public void run(){
        for(;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
            if(i==20){
                //创建和启动线程
                ImplementRunnableCreatThread st=new ImplementRunnableCreatThread();
                new Thread(st,"线程 1").start();
                new Thread(st,"线程 2").start();
            }
        }
    }
}

3.使用Callable和Future创建线程

Callable接口提供了一个call()方法可以作为线程执行体,但是call()方法比run()方法功能更加强大。

*call()方法可以有返回值

*call()方法可以声明抛出异常

创建并启动有返回值得线程的步骤如下。

①创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,在创建Callable实现类的实例。

②使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

③使用FutureTask对象作为Thread对象的target创建并启动新线程。

④调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

package com.bluemsun.study;

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

/**
 * Created by mafx on 2018/9/16.
 */
public class ImplementCallableCreatThread implements Callable{
    /*FutureTask task=new FutureTask((Callable)()->{
        int i=0;
        for(;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    });*/

    @Override
    public Object call() throws Exception {
        int i=0;
        for(;i<100;i++){
            System.out.println(Thread.currentThread().getName()+" "+i);
        }
        return i;
    }

    public static void main(String[] args) {
        ImplementCallableCreatThread ct=new ImplementCallableCreatThread();
        FutureTask task=new FutureTask(ct);
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 20) {
                new Thread(task, "线程 1").start();
            }
        }
        try {
            System.out.println("子线程的返回值:" + task.get());
        }catch (Exception ex){
            ex.printStackTrace();
        }

    }
}

四.线程的生命周期

多线程_第1张图片

五.方法概览

1.join()方法:该方法是Thread提供的让一个线程等待另一个线程完成的方法。当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止。

重载方法:join():等待被join()的线程执行完成

join(long millis):等待被join的线程的时间最长为millis毫秒。如果在millis毫秒内被join的线程还没有执行结束,则不再等待。

join(long millis,int nanos):等待时间毫秒加毫微妙

2.sleep():使线程睡眠,让线程放弃处理器资源进入阻塞状态

3.yield():和sleep()有点相似,但是它不会让线程进入阻塞状态而是再次进入就绪状态

4.setPriority(int newPriority)设置线程的优先级,getPriority()返回指定线程的优先级

............

六.同步代码

1.同步代码块

为了解决多线程共享同一个资源点来的线程安全问题,java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法是同步代码块。语法如下:

synchronized(obj)

{

//此处的代码就是同步代码块

}

其中obj就是同步监视器,上面代码的含义是:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个线程可以活的对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对该同步监视器的锁定。使用同步监视器的目的是阻止多个线程对同一资源进行并发访问,因此通常使用可能被并发访问的共享资源充当同步监视器。

2.同步方法

与同步代码块对应,java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,则称该方法是同步方法。同步方法的同步监视器是this,也就是调用该方法的对象。也就是通过锁定整个对象来实现线程安全

注:synchronized关键字可以修饰方法,可以修饰代码块,但不能修饰构造器、成员变量等。

 七.释放同步监视器的锁定

线程在以下几种情况下释放对同步监视器的锁定:

1.当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器。

2.当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行,当前线程释放同步监视器

3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致改代码块、该方法异常结束时,当前线程释放同步监视器。

4.当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并且释放同步监视器。

在如下几种情况中,线程不会释放同步监视器:

1.线程执行同步代码块或者同步方法时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。

2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器。

总结:

sleep()和wait()方法的区别:第一个区别就是这两个方法的来源不同,sleep()方法来源于Thread类,而wait()方法来源于Object,另一区别就是,线程调用sleep()方法不会释放同步监视器,而调用wait()方法会释放同步监视器。

sleep()和yield()方法的区别:它们都是Thread类的方法,线程调用sleep()会先使得线程进入阻塞状态,而调用yield()会使得线程进入就绪状态。

八.同步锁(Lock)

Lock是控制多线程对共享资源进行访问的工具。通常,锁提供了堆共享资源的独占访问,每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应先获得lock对象。在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁)。使用该Lock对象可以显示的加锁、释放锁,其语法大致如下:

class X{

   private final ReentrantLock lock=new ReenTranLock;

   //......

   //定义要保证线程安全的方法

   public void m(){

         lock.lock();//加锁

        try(){

             //需要保证线程安全的代码

         }finally{

             lock.unlock();//释放锁

            }

    }

}

 九.volatile关键字

volatile作用:让其他线程能够马上感知到某一线程对某个变量的修改

(1)保证可见性

对共享变量的修改,其他的线程马上能感知到

不能保证原子性  读、写、(i++)

(2)保证有序性

重排序(编译阶段、指令优化阶段)

输入程序的代码顺序并不是实际执行的顺序

重排序后对单线程没有影响,对多线程有影响

volatile规则:

对于volatile修饰的变量:

(1)volatile之前的代码不能调整到他的后面

(2)volatile之后的代码不能调整到他的前面(as if seria)

(3)霸道(位置不变化)

十.volatile与synchronized的区别

(1)使用上的区别

Volatile只能修饰变量,synchronized只能修饰方法和语句块

(2)对原子性的保证

synchronized可以保证原子性,Volatile不能保证原子性

注:Volatile关键字值通知其他线程,当前线程对某变量进行了修改,让其他线程去内存中读取数据,而不是继续操作自己工作空间的数据

(3)对可见性的保证

都可以保证可见性,但实现原理不同

Volatile对变量加了lock,synchronized使用monitorEnter和monitorexit  monitor  JVM

(4)对有序性的保证

Volatile能保证有序,synchronized可以保证有序性,但是代价(重量级)并发退化到串行

(5)其他

synchronized引起阻塞

Volatile不会引起阻塞

*Volatile的使用场景

  1. 状态标志(开关模式)
public class ShutDowsnDemmo extends Thread{

    private volatile boolean started=false;


    @Override

    public void run() {

        while(started){

            dowork();

        }

    }

    public void shutdown(){

        started=false;

    }

}

2.双重检查锁定(double-checked-locking)

DCL(7)

public class Singleton {

    private volatile static Singleton instance;

    public static Singleton getInstance(){

        if(instance==null){

            synchronized (Singleton.class){

                instance=new Singleton();

            }

        }

        return instance;

    }

}

3.需要利用顺序性

你可能感兴趣的:(java的道与术,线程,Thread)