ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java基础-多线程基础知识

2021-11-28 17:02:45  阅读:106  来源: 互联网

标签:多线程 java Thread CPU 基础知识 线程 执行 public


文章目录

并行和并发

并行: 多个CPU实例或是多台机器同时执行一段处理逻辑,是真正的同时。
在这里插入图片描述

并发:一个CPU或一台机器,通过CPU调度算法,让用户看上去同时去执行,实际上从CPU操作层面并不是真正的同时。并发往往需要公共的资源,对公共的资源的处理和线程之间的协调是并发的难点。

Process and Thread

在这里插入图片描述

  1. Process

(1)进程就是程序在处理机制中的一次运行

(2)一个进程既包括其所要执行的指令,也包括了执行指令所需的任何系统资源,如CPU,内存空间,I/O端口等,
不同进程所占用的系统资源相对独立。

程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念、而进程则是程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。

进程就是在操作系统中运行的程序,有独立的运行内存空间,比如应用和后台服务,windows是一个支持多进程的操作系统,内存越大能同时运行的程序越多,在java里一个进程指的是一个运行在独立JVM上的程序。

  1. Thread
    (1)线程时进程执行过程中产生的多条执行线索,是比进程单位更小的执行单位。
    (2)它没有入口也没有出口,必须栖身于某一线程,由线程触发执行
    (3)属于同一进程的所有线程共享该进程的系统资源,但是线程之间的切换比进程切换要快得多。
    (4)通常一个进程中包含若干个线程,线程是CPU调度和执行的单位。·
    (5)线程就是独立的执行路径

线程:一个程序离运行的多个任务,每个任务就是一个线程,线程是共享内存的。比如在qq中,可以同时接收发送消息,但是有一个内存占用。

java语言把线程或执行环境当作一个封装对象,包含CPU及自己的代码和数据,由虚拟机提供控制。

注意,很多多线程是模拟出来的,真正的多线程指的是由多个CPU,即多核,如服务器。如果是迷你出来的多线程,在一个CPU的情况下,在同一个时间节点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行错觉。

相关核心概念
(1)线程就是独立执行的路径
(2)在程序运行时即是没有创建自己的线程,后台也会有多个线程,如主线程,gc线程
(3)main称之为主线程,为系统的入口,用于执行整个程序
(4)在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
(5)对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
(6)线程会带来额外的开销,如CPU调度时间,并发控制开销
(7)每个线程在自己的工作内存在交互,内存控制不当会造成数据的不一致
(8)线程不一定立即执行,CPU安排调度
3.使用多线程的好处
同时运行多个任务,提升CPU使用效率

共享内存,占用资源更少,线程之间可以通信

异步调用,避免阻塞。

从微观上讲,一个时间里只能有一个作业被执行,但要实现多线程,就是要在宏观上使多个作业被同时执行。

线程的结构

在java中线程可以被认为是由三部分组成的:
1.虚拟CPU,封装在java.lang.Thread类中,它控制着整个线程的运行
2.执行的代码,传递给Thread类,由Thread类控制顺序执行
3.处理的数据,传递给Thread类,是在代码执行过程中所要处理的数据。

注意:在java中,虚拟CPU体现于Thread类中。当一个线程被构造时,它由构造方法参数,
执行代码,操作数据来初始化。

多线程的优势
在这里插入图片描述

(1)多线程编程简单,效率高。使用多线程可以在线程之间共享数据和资源。
(2)适合于开发服务程序,如Web服务
(3)适合与开发由多种交互接口的程序
(4)适合于有人机交互又有计算量的程序。

使用多线程的好处:
1.同时执行多个任务,提升CPU的使用率
2共享内存,占用资源更少,线程间可以通信
3.异步调用,避免阻塞

线程的状态

(1)当生成一个Thread对象之后,就产生了一个线程。通过该对象实例,可以启动线程,终止线程,或者暂时挂起线程。
(2)Thread类本身只是线程的虚拟CPU,线程所执行的代码是通过方法中的run方法(包含在一个特定的对象中)完成的,方法run()称为线程体。实现线程体的对象是在初始化线程时传递给线程的。
(3)在一个线程被建立并初始化之后,java的运行时系统自动调用run()方法。

1.新建 New 线程对象被创建后,它只会短暂的处于这种状态。此时他已经分配了必须的系统资源,并执行了初始化。

Thread thread = new Thread("test");

2.就绪 Runnable 称为可运行状态
线程对象被创建之后,其他线程调用了该对象的start()方法,从而来启动该线程。例如
thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

3.运行 Running:线程获取CPU权限进行执行。注意:线程只能从就绪状态进入运行状态。

4.死亡(dead)
(1)run()方法中最后一个语句执行完毕
(2)当线程遇到异常退出时

5.阻塞(blocked)
一个正在执行的线程因特殊的原因被暂停执行,就进入阻塞状态。阻塞时线程不进入就绪队列排队。必须等到阻塞的原因消除才能进入就绪队列。
阻塞情况分三种:
(1)等待阻塞:通过调用线程的wait()方法,让线程等待某项工作的完成
(2)同步阻塞:线程在获取synchronised同步锁失败(因为锁被其他线程嗲用),它就会进入同步阻塞状态。
(3)其他阻塞:通过调用线程的sleep()或发出了I/O请求时,线程会进入到阻塞状态。
当sleep()状态超时,join()等待线程终止或是超时。或是I/O处理完毕之后,线程就进入就绪状态。

6.中断线程
当run()方法执行结束返回时,线程自动结束。
(1)在程序中常常调用interrupt()方法来终止线程。该方法不仅可中断正在运行的线程,而且也能终端处于blocked
状态的线程,此时会抛出InterruptedException异常
.
java中测试线程是否被中断的方法
(1)void Interrupt() 像一个线程发送中断请求,同时把这个线程的“Interrupted”设置为true
(2)static boolean interrupted()检测当前线程是否已经被中断,并重置状态“interrupted”值,及如果连续两次调用
该方法,则第二次调用将返回false
boolean isInterrupted()检测当前线程是否已被中断,不改变“interrupted”的值

创建线程

类Thread的一个构造方法如下

public Thread(ThreadGroup group,Runnable target,Strig name)
//group指明了线程所属的线程组;target是线程体run()方法所在的对象
(target必须实现了Runnable接口);name是线程的名称.
//在接口Runnable中只有一个run()方法作为线程体。
任何实现Runnable接口的对象都可以作为一个线程的目标对象

一,创建现线程的方法-继承Thread类

public class ThreadTest{
    /**
     * 创建线程的方法一,继承Thread类*/
    static Lefthand left;
    static Righthand right;

    public static void main(String[] args) {
        left = new Lefthand();
        right = new Righthand();

        left.start();
        right.start();
    }
}

class Lefthand extends Thread{
    /**
     * 方法run()称为线程体,它是整个线程的核心,线程所要完成的任务的代码都定义在线程体中
     * 实际上不同功能之间的区别就在于它们的线程体不同。*/
    public void run(){
        for (int i = 0; i < 6; i++) {
            System.out.println("You are Students!");
            try {
            	//多线程 要休息
                sleep(500);//性能改进
                System.out.println(getState());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Righthand extends Thread{
    public void run()
    {
        for (int i = 0; i < 6; i++) {
            System.out.println("I am a Teacher!");
            try {
                sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

二,实现Runnable()接口
总之线程由Thread对象的实例来引用。线程执行的代码来源于传递给Thread构造方法的参数引用的类,这个类
必须实现了接口Runnable(),线程操作的数据来源于触底给Thread构造方法的Runnable实例。。。。。。。

import javax.swing.plaf.TableHeaderUI;

public class xyz implements Runnable{
    int i=0;
    @Override
    public void run() {
        while (i < 100){
            System.out.println("Hello" + i++);
            try {
                Thread.sleep(500);  //sleep()是Thread类中的静态方法,所以可以直接调用
                //参数指定了线程再次启动前必须休眠的最短时间,保证一段时间后该线程回归到就绪队列。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
/**
 *public Thread(ThreadGroup group,Runnable target,String name
 * group指明了线程所属的线程组;target是线程体run()方法所在的对象,name是线程的名称*/

/**
 * Thread的构造方法中包含有一个Runnable实例的参数,这就是说,必须定义一个实现Runnable接口的类并产生一个该类的实例,
 * 对该实例的引用就是适合于这个构造方法的参数*/
class test{
    public static void main(String[] args) {
        Runnable r = new xyz();
        Thread t = new Thread(r);
        t.start();
    }
}


总结:线程由Thread对象的实例来引用。线程执行的代码来源于传递给Thread构造方法的参数引用的类,这个类必须实现了Runnable()接口,线程操做的数据来源于传递给Thread构造方法的Runnable实例。

关于两种创建线程方法的讨论

1.适用于采用实现Runnable接口方法的情况
因为Java只允许单继承,如果一个类已经继承了Thread,就不再继承其他类,在
一些情况下,就被迫采用实现Runnable接口的方法。

2.适用于采用继承Thread方法的情况
当一个run()方法置于Thread类中时,this实际上引用的是控制当前运行系统的Thread实例

五,线程的启动

一个线程被创建之后,但并没有立即运行。要使线程真正在java环境中运行,必须通过start()方法来启动。
启动之后,线程的虚拟CPU已经就绪。

1.start()用来启动一个线程,当调用start()方法时,系统才会开启一个线程,通过Thead类中start()方法来启动的线程处于就绪状态(可运行状态),此时并没有运行,一旦得到CPU时间片,就自动开始执行run()方法。此时不需要等待run()方法执行完也可以继续执行下面的代码,所以也由此看出run()方法并没有实现多线程。
2.run()用来定义线程对象被调度之后锁执行的操作,用户必须重写run()方法
3.yield()强制终止当前线程的运行
4.isAlive()测试当前线程是否在活动
5.sleep(int millsecond)使线程休息一段时间,时间长短由参数决定
6.void wait()使线程处于等待状态

六,线程的调度

虽然就绪线程已经可以运行,但这不意味着这个线程一定能够立刻运行。
在Java中时间调度通常是抢占式,而不是时间片式。

抢占式调度是指可能有多个线程准备运行,但只有一个在真正运行。一个线程获得执行权,这个线程将持续运行下去,
直到它运行结束或因为某种原因阻塞,再或者有另一个高优先级线程就绪。

java的线程调度采用如下优先级策略
1.优先级高的先执行,优先级低的后执行
2.多线程系统会自动为每一个线程分配一个优先级,默认时,继承其父类的优先级
3.任务救急的线程,其优先级较高
4.同优先级的线程按“先进先出”的原则

java中Thread类几个与线程优先级有关的静态变量
1.MAX_PRIORITY 值为10
2.MIN_PRIORITY
3.NORM_PRIORITY 值为5

java类中几个常用的与优先级有关的方法有
1.void setPrioriy(int newPriority)
2.int getPriority()获得当前线程的优先级
3.static void yield()使当前线程放弃执行权

就绪队列与阻塞队列
1.所有就绪但没有运行的线程则根据其优先级排入一个就绪队列,当CPU空闲时,如果就绪队列不为空,队列中第一个具有最高优先级的线程将运行。
2.当一个线程被抢占而停止运行时,它的运行状态被改变并放到就绪队列的队尾,同样一个因为某种原因被阻塞的线程就绪后
通常也放到就绪队列的队尾。

七 线程的基本控制

1.结束线程
在程序代码中,可以利用Thread类中的静态方法currentThread()来引用正在运行的线程。

2.检查线程
isAlive 获取一个线程是否还在活动状态的信息。活动状态不意味着这个线程正在执行,而只说明这个线程已经被启动,
并且既没有运行方法stop()和方法run()。

3.挂起线程
暂停一个线程也称为挂起。在挂起之后,必须重新唤醒线程进入运行。

sleep()
线程不是休眠期满后就立刻被唤醒,因为此时其他线程可能正在执行,重新调度只在以下几种情况下才会发生:
。。。被唤醒的线程具有更高的优先级
。。。正在执行的线程因为其他原因被阻塞
。。。程序处于支持时间片的系统之中

wait()和notify()/notifyAll()
wait()方法导致当前线程等待,直到其他线程调用此对象的notify()发给发或nitifyAll()发给发,才能唤醒线程

join()
方法join()将引起现行线程等待,直至方法join()方法所调用的线程结束。例如已经生成并运行了一个线程tt,
而在另一个线程中执行方法timeout(),

public void timeout(){
    //暂停该线程,等候其他线程结束
    tt.join;
    //其他线程结束后,继续执行该线程
}

八 同步问题

线程的安全性和原子性

由于线程之间可以共享内存,则某个对象(变量)使可以被多个线程共享的,是可以被多个线程同时访问的。当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步协同,这个类都能表现出正确行为,那么就称这个线程是同步的。

1.线程间通信,写线程往管道流中输入信息,读线程往管道流中读入信息。
2.线程间的资源互斥共享
通常一些同时运行的线程需要共享数据。在这种时候,每个线程就需要考虑与它一起
共享数据的其他线程的状态与行为,否则就不能保证共享数据的一致性。

import javax.swing.plaf.TableHeaderUI;
import java.io.*;
import java.util.Scanner;
import java.util.Stack;

class myWriter extends Thread{
    private PipedOutputStream outStream;
    private  String messages []  = {"Monday","Tuesday","Wednsday",
    "Thursday","Friday","Saturday","Sunday"};
    public myWriter(PipedOutputStream o){
        outStream = o;
    }

    public void run(){
        /**
         * Creates a new print stream, without automatic line flushing, with the
         * specified OutputStream. Characters written to the stream are converted
         * to bytes using the platform's default character encoding.
         *
         * @param  out        The output stream to which values and objects will be
         *                    printed
         *
         * @see java.io.PrintWriter#PrintWriter(java.io.OutputStream)
         *
         *  public PrintStream(OutputStream out) {
         *             this(out, false);
         *         }
         */

        PrintStream p = new PrintStream(outStream);
        for (int i = 0; i < messages.length; i++) {
            p.println(messages[i]);//将数据写入到管道流中,
            p.flush();
            System.out.println("Write:" + messages[i]);
        }
        p.close();
        p = null;
    }
}

class myReader extends Thread{
    private PipedInputStream inputStream;
    public myReader(PipedInputStream i){
        inputStream = i;
    }

    public void run(){
        String line;
        boolean reading = true;
        Scanner scan = new Scanner(new InputStreamReader(inputStream));

        while (scan.hasNextLine()){
            System.out.println("Read:" + scan.nextLine());

            try {
                Thread.currentThread().sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

public class Pipethread{
    public static void main(String[] args) {
        Pipethread thisPipe = new Pipethread();
        thisPipe.process();
    }

    private void process() {
        PipedInputStream inputStream;
        PipedOutputStream outputStream;
        try {
            /**
             * Creates a piped output stream that is not yet connected to a
             * piped input stream. It must be connected to a piped input stream,
             * either by the receiver or the sender, before being used.
             *
             * @see     java.io.PipedInputStream#connect(java.io.PipedOutputStream)
             * @see     java.io.PipedOutputStream#connect(java.io.PipedInputStream)
             *
             * public PipedOutputStream() {
             *             }
             */

            outputStream = new PipedOutputStream();
            /**
             * Creates a {@code PipedInputStream} so
             * that it is connected to the piped output
             * stream {@code src}. Data bytes written
             * to {@code src} will then be  available
             * as input from this stream.
             *
             * @param      src   the stream to connect to.
             * @throws     IOException  if an I/O error occurs.
             *在这里插入代码片
             * public PipedInputStream(PipedOutputStream src) throws IOException {
             *                 this(src, DEFAULT_PIPE_SIZE);
             *             }
             */

            inputStream = new PipedInputStream(outputStream);
            new myWriter(outputStream).start();
            new myReader(inputStream).start();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

对象的锁定标志
在java语言中,引入了“对象互斥锁的概念”来实现不同线程对共享数据操作的同步,“对象互斥锁的概念”阻止多个线程同时访问同一个条件变量。java可以为每一个对象的实例配有一个“对象互斥锁”。

java内存模型中的可见性,原子性和有序性。
可见性:当多个线程访问同一个变量x时,线程1修改了变量x的值,线程1,线程2…线程n能够立即读取到线程1修改后的值。
有序性:即程序执行时按照代码书写的先后顺序执行。在java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单位线程程序的执行,却会影响到多线程并发执行的正确性。
原子性:原子性通常指多个操作不存在只执行一部分的情况,要么全部执行,要么全部不执行。

线程池的概念
线程的创建时比较消耗内存的,所以我们要事先创建若干个可执行的线程放进一个“池”里面,需要的时候就直接从池里面取出来不需要自己创建,使用完毕也不需要销毁而是放进“池”中,从而减少了创建和销毁对象所产生的开销。
ExecutorService:线程池接口
ExecutorService poo(池名称) = Executors.常用线程池名;
常用线程池:
newsingleThreadExecutor :单个线程的线程池,即线程池中每次只有一个线程在工作,单线程串行执行任务
newfixedThreadExecutor(n):固定数量的线程池,每提交个任务就是一个线程, 直到达到线程池的最大数量,然后在后面等待队列前面的线程执行或者销毁
newCacheThreadExecutor:一个可缓存的线程池。当线程池超过了处理任务所需要的线程数,那么就会回收部分闲置线程(一般是闲置60s)。当有任务来时而线程不够时,线程池又会创建新的线程,当线程够时就调用池中线程。适用于大量的耗时较少的线程任务。
newScheduleThreadExecutor:一个大小无限的线程池,该线程池多用于执行延迟任务或者固定周
期的任务。

package ThreadPool;

public class ticket implements Runnable{
    private int ticket = 10;

    @Override
    public void run() {
        for (int i = 1; i <= 15; i++) {
            if (ticket > 0)
                System.out.println(Thread.currentThread().getName()+"买票,剩余"+ticket--);
            else {
                System.out.println(Thread.currentThread().getName()+"票卖完了");
                break;
            }

        }
    }
}

package ThreadPool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TicketPool {
    public static void main(String[] args) {
        /**
         * 利用线程池创建线程
         * */

        //创建放5个线程的线程池  Executors是一个类
        ExecutorService service = Executors.newFixedThreadPool(5);

        for (int i = 1; i < 5; i++) {
            //执行线程
            service.execute(new ticket());
        }
        service.shutdown();//关闭线程池
    }
}

(1)用关键字volatile声明一个共享数据(变量)
volatile具有可见性,有序性,不具备原子性。
可见性:当多个线程访问同一个变量x时,线程1修改了变量x的值,线程1,线程2,…线程n能够立即读取到线程1修改的值。
有序性:即程序执行时按照代码书写的先后顺序执行。在java内存模型中,允许编译器和处理器对指令进行重排序,但时重排序过程不会影响到单线程序的执行,却会影响到多线程并发执行的正确性。
原子性:原子性通常指多个操作不存在只执行一部分的情况,要么全部执行,要么全部不执行。

synchronised时阻塞式同步,称为重量级锁。而volatile时非阻塞式同步,称为轻量级锁。被volatile修饰的变量能够保证每个线程能偶获取该变量的最新值。

(2)用关键字synchronized声明操作共享数据的一个方法或一段代码。synchronised是java的内置锁机制,是在JVM上的。

(3)ReentrantLock可重入锁,在javaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入,互斥,是吸纳了Lock接口的锁,他与使用synchronised方法和块 具有相同的基本行为和语义,并且扩展了其能力。
ReentrantLock():创建一个ReentrantLock实例
lock():获得锁
unlock():释放锁
可重入:甲获得锁后释放锁或锁失效,乙可继续获得这个锁。

为了保证数据操作的完整性
pan.baidu.com/s/1LmovqR4vuN8pxoRsnzCN6A
当线程执行到被同步的语句的时,它将传递的对象参数设置为锁定标志,禁止其他线程对该对象的访问。

在第一个线程拥有锁定标记时,如果另一个线程企图执行synchronised(this)中的语句,它将从对象this中获取锁定标记。
因为这个标记不可得,故该线程不能继续执行。实际上该线程将加入一个等待序列,这个等待序列与对象锁定标志相连,当标被返回该对象时,第一个等待它的线程将得到他并继续执行。当持有锁定标志的线程运行完synchronized()调用包含的程序块之后这个标志会自动返还。

同步方法

class myStack1{
    int idx = 0;
    char data [] = new char[6];

    public void push(char c){
        synchronized (this){
            data[idx] = c;
            idx++;
        }
    }

    public synchronized char pop(){
        synchronized (this){
            idx--;
            return data[idx];
        }
    }
}

死锁

果一个线程持有一个锁并试图获取另一个锁时,就有死锁的危险。
死锁的情况发生在 第一个线程等待第二个线程所持有的所,而第二个线程又在等待第一个线程持有的锁的时候,
每个线程都不能继续执行

线程交互

1.为什么线程之间需要交互?
2.为了解决线程运行速度的问题,java提供了一种建立在对象实例之上的交互方法。java中的每个对象实例都有两个线程队列和它相连。第一个用雷排列等待锁定标志的线程,第二个则用来实现wait()和notify()的交互机制。
(1)wait()方法的作用是让当前线程释放其所持有的“对象互斥锁”,进入wait队列(等待队列)
(2)notify()和notifyAll()方法的作用是唤醒一个或所有正在等待队列中等待的线程,并将它们移入等待同一个对象互斥锁的队列
注意:这三个方法都只能在被声明为synchronised的方法或代码段中调用。

生产消费者模型

标签:多线程,java,Thread,CPU,基础知识,线程,执行,public
来源: https://blog.csdn.net/weixin_45773632/article/details/110182260

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有