初学者的多线程理解

02/06 11:19
阅读数 33

(一)线程与进程

(1)概述

进程:

是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间线程。

线程:

是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换并发执行。一个进程最少有一个线程。

实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)由来

在早期的操作系统里,计算机都是单核处理器,只有一个核心,进程是执行程序的最小单位,任务调度采用时间片轮转的抢占式方式进行进程调度。每个进程都有各自的一块独立的内存,保证进程彼此间的内存地址空间的隔离。

计算机病毒的诞生让计算机程序的安全得到重视。在早期在只有一个核心的情况下,计算机一旦中病毒就内存有可能被占满,cpu不会执行其他的操作,也无法进行键盘输入鼠标移动等操作。

随着计算机技术的发展,进程出现了很多弊端
(1)进程的创建、撤销和切换的开销比较大
(2)由于对称多处理机的出现,可以满足多个运行单位,而多进程并行开销过大。

线程的引入:
由于进程出现了很多弊端,这个时候就引入了线程的概念。 线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合 和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。 线程没有自己的系统资源,只拥有在运行时必不可少的资源。但线程可以与同属与同一进程的其他线程共享进程所拥有的其他资源。

(3)线程的调度

分时调度

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

抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高

(4)多进程与多线程的理解

在此引用一下廖雪峰老师的网站

(5)Thread类(线程类)

<1>构造方法:

在这里插入图片描述

<2>静态方法

在这里插入图片描述

<3>实例方法

在这里插入图片描述在这里插入图片描述

<3>具体方法

在这里插入图片描述在这里插入图片描述

<4>常用方法详解(实例演示)

 public static void main(String[] args) {
   
   
        //输出当前进程的名字:main
        System.out.println(Thread.currentThread().getName());
        //必须加.start();不然线程无法执行
        new Thread(new MyRunnable(),"端午").start();
        new Thread(new MyRunnable(),"重阳").start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();
        //由运行结果可以看出线程不保证属性,是抢占式
        Thread t = new Thread(new MyRunnable());
        t.setName("除夕");
        t.start();
}
public  static class MyRunnable implements Runnable{
   
   
    @Override
    public void run() {
   
   
        //输出当前任务进程的名字
        System.out.println(Thread.currentThread().getName());
    }
}

在这里插入图片描述

分析: 此程序运用的常用方法有

变量和类型 方法 描述
void run() 如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法。
static Thread currentThread() 返回对当前正在执行的线程对象的引用。
void setName​(String name) 将此线程的名称更改为等于参数 name 。
构造器 Thread​(Runnable target, String name) 分配新的 Thread对象。
构造器 Thread​(Runnable target) 分配新的 Thread对象。

(6)多线程的创建方式

<1>继承Thread类

public static void main(String[] args) {
   
   
        MyThread myThread = new MyThread();
        //不用调用run,用start
        myThread.start();
        for(int i = 0;i<5;i++) {
   
   
            System.out.println("处处蚊子咬"+i);
        }
}
//继承Thread类
public class MyThread extends  Thread{
   
   
    @Override
    public void run() {
   
   
      for (int i = 0;i<5;i++)
    System.out.println("春眠不觉晓"+i);
    }
}

多次运行结果不一致
在这里插入图片描述
在这里插入图片描述
可以看出Java是抢占式分配
程序运行图示:
在这里插入图片描述
在这里插入图片描述





<2>实现Runnable接口

 public static void main(String[] args) {
   
   
        //1.创建一个任务对象
        MyRunnable r = new MyRunnable();
        //2.创建一个线程,并分配一个任务对象
        Thread k = new Thread(r);
        //3.执行线程
        k.start();
        for(int i =0;i<5;i++){
   
   
            System.out.println("处处蚊子咬"+i);
        }
 }
 //实现Runnable接口
public class MyRunnable implements Runnable{
   
   

    @Override
    public void run() {
   
   
        //线程执行的任务
        for(int i =0;i<5;i++){
   
   
            System.out.println("春眠不觉晓"+i);
        }
    }
}

在这里插入图片描述
在这里插入图片描述

<3>两种创建方式的区别

实现Runnable 与 继承Thread 相比有
如下优势
1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
2.可以避免单继承所带来的局限性。
3.任务与线程本身是分离的,提高了程序的健壮性。
4.线程池技术只接受Runnable类型的任务,不接受Thread类型的任务。
然而不代表继承Thread方法就不能用,下面运用内部类的方法就很方便。





  public static void main(String[] args) {
   
   
        //创建一个匿名内部类,任务只用执行一次不需要调用
        new Thread(){
   
   
            @Override
            public void run() {
   
   
                for(int i =0;i<5;i++){
   
   
                    System.out.println("春眠不觉晓"+i);
                }
            }
        }.start();
        for(int i =0;i<5;i++){
   
   
            System.out.println("处处蚊子咬"+i);
        }
    }

在这里插入图片描述

(七)线程的休眠:

public static void main(String[] args) throws InterruptedException {
   
   
      for(int i =0;i<5;i++){
   
   
        System.out.println(i);
        //休眠1000毫秒(1)秒,sleep是静态方法
        Thread.sleep(1000);
      }
    }

运行结果:
在这里插入图片描述

(八)线程的中断:

  public static void main(String[] args) throws InterruptedException {
   
   
        Thread t = new Thread(new MyRunnable());
        t.start();
        for(int i =0;i<5;i++){
   
   
            System.out.println(Thread.currentThread().getName()+i);
     try{
   
   
        Thread.sleep(1000);
      }catch (InterruptedException e){
   
   
         e.printStackTrace();
     }
        }
        //main线程已经结束,调用t线程中断,t线程进入异常return跳出线程
        t.interrupt();
    }
 public static class MyRunnable implements Runnable{
   
   

        @Override
        public void run() {
   
   
            for(int i =0;i<10;i++){
   
   
                System.out.println(Thread.currentThread().getName()+i);
                try {
   
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
   
                  
                    System.out.println("发现中断标记,线程自杀");
                    return;
                }
            }
        }
 }

运行结果:
在这里插入图片描述

(九)线程守护

线程的分类: 守护线程和用户线程

用户线程:当一个进程不包含任何的存活的用户线程时,进程结束。
守护线程:守护用户线程的,当最后一个用户线程结束时,所以守护线程自动死亡。

    public static void main(String[] args) throws InterruptedException {
   
   
        Thread t = new Thread(new MyRunnable());
        //设置守护线程
        t.setDaemon(true);
        t.start();
        for(int i =0;i<5;i++){
   
   
            System.out.println(Thread.currentThread().getName()+":"+i);
            try{
   
   
              Thread.sleep(1000);
            }catch (InterruptedException e){
   
   
               e.printStackTrace();
            }
        }
        //main方法还需要结束,守护线程还没有死,再输出一次
   }
    public static class MyRunnable implements Runnable{
   
   
        @Override
        public void run() {
   
   
            for(int i =0;i<10;i++){
   
   
             System.out.println(Thread.currentThread().getName()+":"+i);
                try {
   
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
   
                        e.printStackTrace();
                }
            }
        }
    }

运行结果:
在这里插入图片描述

<10>线程安全

【1】线程不安全

 public static void main(String[] args) {
   
   
       Runnable t = new Ticket();
       new Thread(t).start();
       new Thread(t).start();
       new Thread(t).start();
 }
    public static class Ticket implements Runnable{
   
   
        private  int count = 5;
        @Override
        public void run() {
   
   

            while(count>0){
   
   
                System.out.println("正在准备出票");
                try {
   
   
                    Thread.sleep(1000);
                }catch (InterruptedException e){
   
   
                    e.printStackTrace();
                }
                count--;
                System.out.println("出票成功,还剩票数为:"+count);
            }
        }
    }

运行结果:
在这里插入图片描述
可以看出不应该出现负数,三个线程执行同一个任务造成了线程不安全。

【2】线程不安全原因

在判断和使用数据直接间隔了几行代码,在中间执行的时候被其他线程插足,把数据改变了,造成了数据不符合预期。

【3】解决方法

一个线程在间隔区执行时不让其他线程插足,排队执行。

{1}解决方法1:同步代码块

 public static void main(String[] args) {
   
   
 //只创建了一个new Ticket对象,所以是同一个o,同一把锁才能排队
        Runnable t = new Ticket();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }

    public static class Ticket implements Runnable {
   
   
        private int count = 5;
        private  Object o = new Object();
        @Override
        public void run() {
   
   
            while (true) {
   
   
            //开始上锁
                synchronized (o) {
   
   
                    if (count > 0) {
   
   
                        System.out.println("正在准备出票");
                        try {
   
   
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
   
   
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"出票成功,还剩票数为:" + count);
                    }else{
   
   
                        break;
                    }
                }
            }
        }
    }

运行结果:
在这里插入图片描述
线程0第一个抢到锁,在循环执行结束锁标志被清除,此时距离锁位置最近,反手又打上标记。所以抢到的几率最大,程序效率不高。

{2}解决方法2:同步方法

  public static void main(String[] args) {
   
   
        Runnable t = new Ticket();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }
    public static class Ticket implements Runnable {
   
   
        private int count = 10;
        @Override
        public void run() {
   
   
            while (true) {
   
   
            //在循环中调用sale方法
                boolean flag = sale();
                if (!flag) {
   
   
                //退出死循环
                    break;
                }
            }
        }
//给方法上锁
        public synchronized boolean sale() {
   
   
            if (count > 0) {
   
   
                System.out.println("正在准备出票");
                try {
   
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
   
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + "出票成功,还剩票数为:" + count);
                return true;
            } else {
   
   
                return false;
            }
        }
    }

运行结果:
在这里插入图片描述

{3}解决方法3:显示锁LOCK

同步方法块和同步方法属于隐示锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo6 {
   
   
    public static void main(String[] args) {
   
   
       Runnable t = new Ticket();
       new Thread(t).start();
       new Thread(t).start();
       new Thread(t).start();
    }

    public static class Ticket implements Runnable {
   
   
        private int count = 5;
         //创建锁对象
        private Lock l = new ReentrantLock();
        @Override
        public void run() {
   
   
            while (true) {
   
   
            //上锁
                l.lock();
                if (count > 0) {
   
   
                    System.out.println("正在准备出票");
                    try {
   
   
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
   
   
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println("出票成功,还剩票数为:" + count);
                } else {
   
   
                    break;
                }//if语句运行结束,解锁
                l.unlock();
            }
        }
    }
}

运行结果:
在这里插入图片描述

(二)同步与异步

同步: 排队执行 , 效率低但是安全.
异步: 同时执行 , 效率高但是数据不安全.

(三)并发与并行

并发: 指两个或多个事件在同一个时间段内发生。
并行: 指两个或多个事件在同一时刻发生(同时发生)

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部