线程

线程

1.概念

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

并行:指两个或多个事件在同一时间发生

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程是进程的执行单位,一个进程至少含一个线程。

一个进程可以含多个线程,这种称为多线程。

一个进程可以类比于腾讯电脑管家,多个线程可以当作病毒查杀,清理垃圾,电脑加速等。

2.创建线程

主线程:执行(main)方法的线程

单线程程序:java程序中只有一个线程

2.1 Thread

  • 创建一个新的执行线程有两种方法。 一个是将一个类声明为Thread的子类。 这个子类应该重写run类的方法Thread 。 然后可以分配并启动子类的实例。

  • 实现步骤

    • 1.创建一个Thread类的子类
    • 2.在Thread类的子类中重写Thread类的run方法,设置线程任务(即开启线程做什么)
    • 3.创建Thread类的子类对象
    • 4.调用Thread类的start方法开启新的线程。
  •  class PrimeThread extends Thread {
             long minPrime;
             PrimeThread(long minPrime) {
                 this.minPrime = minPrime;
             }
    
             public void run() {
                 // compute primes larger than minPrime
                  . . .
             }
         }
    
  • 然后,以下代码将创建一个线程并启动它运行:

         PrimeThread p = new PrimeThread(143);
         p.start();
    

Thread常用方法

public final String getName()//获取线程名称
  • Thread.currentThread().getname()也可以直接获取(main必须使用这个方式)
public static void sleep(long millis)//暂停millis时间后在执行

2.2 Runnable

  • 另一种方法来创建一个线程是声明实现类Runnable接口。 那个类然后实现了run方法。 然后可以分配类的实例,在创建Thread时作为参数传递,并启动。

  • 实现步骤:

    • 1.创建Runnable接口的实现类
    • 2.在实现类中重写Runnable接口的run方法
    • 3.创建Runnable接口的实现类对象
    • 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    • 5.调用Thread类的run()方法,开始新的线程
  • class PrimeRun implements Runnable {
             long minPrime;
             PrimeRun(long minPrime) {
                 this.minPrime = minPrime;
             }
    
             public void run() {
                 // compute primes larger than minPrime
                  . . .
             }
         }
    
  • 然后,以下代码将创建一个线程并启动它运行:

         PrimeRun p = new PrimeRun(143);
         new Thread(p).start();
    
  • 实现Runnable接口创建多线程程序的好处
    • 避免了单继承的局限性
      • 一个类继承了Thread就不能继承其他类了
    • 增强了程序的扩展性,降低了程序的耦合性
      • 实现Runnable接口的方式,把设置线程任务和开启新线程进行了解耦
      • 实现类中,重写了run()方法:用来设置线程任务
      • 创建Thread类对象,调用start方法:用来开启新线程
public class MyThread {

    public static void main(String[] args) {

        //Lamda将Runnable实现
        new Thread(() ->  {for (int i = 0; i < 5; i ++)
            System.out.println(Thread.currentThread().getName() + " " + i);
        }).start();

        //匿名内部类继承Thread
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i ++){
                    System.out.println(Thread.currentThread().getName() + " " + i);
                }
            }
        }.start();


    }
}

3.线程的安全问题

卖票案例
在这里插入图片描述

线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权,让其他的线程只能等待。

package it;

public class RunnableImpl implements Runnable {

    private static int ticket = 100;

    @Override
    public void run() {

        while (ticket > 0){
            System.out.println(Thread.currentThread().getName() + " 正在卖第"+ (101 - ticket) + "张票");
            ticket --;
        }
    }
}
package it;

public class MyThread {

    public static void main(String[] args) {
		RunnableImpl runnable = new RunnableImpl();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();

    }
}

以上代码有安全问题,会有重复以及不存在的票

  • 解决安全问题的第一种方式:同步代码块

    格式:

    synchronized(锁对象){
    	可能会出现线程安全问题的代码(访问共享数据的代码)
    }
    

    注意:

      	1. 通过代码块中的锁对象,可以使用任意的对象
      	2. 但是必须保证多个线程使用的锁对象是同一个
      	3. 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行
    
    package it;
    
    public class RunnableImpl implements Runnable {
        private int ticket = 100;
    
        @Override
        public void run() {
            while (true) {
                synchronized (this) {
                    if (ticket > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "->>正在卖第" + (101 - ticket) + "张票");
                        ticket--;
                    } else {
                        break;
                    }
                }
            }
        }
    }
    
  • 解决线程安全问题的第三种方案:使用Lock锁

  • Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition
  • Lock的常用方法
void lock(){//获得锁

}

void unlock(){//释放锁

}
  • 使用步骤:
    1. 在成员位置创建一个ReentrantLock对象
    2. 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
    3. 在可能会出现安全问题的代码后调用Lock接口的方法unlock释放锁
package it;

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

public class RunnableImpl implements Runnable {

    private int ticket = 100;

    Lock l = new ReentrantLock();

    @Override
    public void run() {

        while (true) {
            try {
                l.lock();
                if (ticket > 0) {//判断语句不要放在锁对象外
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + " 正在卖第" + (101 - ticket) + "张票");
                    ticket--;
                }else{
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                l.unlock();
            }
        }
    }
}
   
  1. synchronized是自动释放锁(显示),lock需要手动释放和关闭锁(隐式)。
  2. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。
  3. Lock只有代码块锁,synchronized有代码块锁和方法锁。

4.线程的等待唤醒操作

即为操作系统所讲的pv操作

经典案例:生产者消费者

案例:线程之间的通信(买包子)

  • 创建一个顾客线程(消费者):告知老板要得包子的种类和数量,调用wait方法,放弃CPU的执行,进入到waiting状态(无限等待)
  • 创建一个老板线程(生产者):花了5秒做包子,做好之后调用notify方法,唤醒顾客吃包子

注:

  • 老板和顾客是同步代码,保证唤醒和等待只有一个在执行
  • 同步使用的锁对象唯一
  • 只有锁对象才能使用wait和notify方法

Object类的方法

  • public final void wait()
    

导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll(),换句话说,这个方法的行为就好像简单地执行呼叫wait(0)

  • public final void notify()
    

唤醒正在等待对象监视器的单个线程。 如果任何线程正在等待这个对象,其中一个被选择被唤醒。 选择是任意的,并且由实施的判断发生。 线程通过调用wait方法之一等待对象的监视器。

package it;

public class Baozi {

    public static void main(String[] args) {
        Object obj = new Object();

        new Thread(() -> {//消费者
            System.out.println("tell the onwer that how many and how much (and then wait 5s)");
            synchronized (obj){
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("eat");
            }

        }).start();

        new Thread(() -> {//生产者
            synchronized (obj){
                System.out.println("spend 5s to do it");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                obj.notify();

                System.out.println("it has been finished,u can eat");
            }

        }).start();
    }
}
  • 如果wait(long m),即为m毫秒内没被唤醒,自动醒来
  • notifyAll(),唤醒所有等待的线程

包子升级版案例:

在这里插入图片描述

package ita;

public class Baozi {

    public String pi;
    public String xian;
    public boolean flag = false;
}
package ita;

public class Baozipu extends Thread {

    private Baozi bz;

    public Baozipu(Baozi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        while (true) {
            synchronized (bz) {
                if (bz.flag == true) {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (count % 2 == 0) {
                    bz.pi = "薄皮";
                    bz.xian = "韭菜";
                } else if (count % 2 == 1) {
                    bz.pi = "厚皮";
                    bz.xian = "大葱";
                }
                count++;
                System.out.println("正在做" + bz.pi + bz.xian + "馅包子");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                bz.flag = true;
                bz.notify();
                System.out.println("已经做完" + bz.pi + bz.xian + "馅包子");
            }
        }
    }
}
package ita;

public class Chihuo extends Thread {

    private Baozi bz;

    public Chihuo(Baozi bz) {
        this.bz = bz;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (bz) {
                if (bz.flag == false) {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println("吃货正在吃" + bz.pi + bz.xian + "馅包子");
                bz.flag = false;
                bz.notify();
                System.out.println("吃货已经吃完" + bz.pi + bz.xian + "馅包子");
                System.out.println("+++++++++++++++++++++++++++++++++++");
            }
        }
    }
}
package ita;

public class Main {
    public static void main(String[] args) {
        Baozi bz = new Baozi();

        new Baozipu(bz).start();

        new Chihuo(bz).start();
    }
}

5.线程池

  • 如果并发的线程数量很多,并且每个线程都是执行一个时间执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

  • java.util.concurrent.Executors//线程池的工厂类,用来生产线程池
    
  • public static ExecutorService newFixedThreadPool(int nThreads)
    

    创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。

  • java.util.concurrent.ExecutorService、、线程池接口
    

    用来从线程池中获取线程,调用start方法,执行线程任务

    ​ sumbit(Runnable task)提交一个Runnable任务用于执行

    关闭/销毁线程池的方法 void shutdown()

  • 线程池的使用步骤

    • 使用Executors的newFixedThreadPool生产指定线程数量的线程池
    • 创建一个类,实现Runnable接口,设置线程任务
    • 调用ExecutorService中的方法submit,传递线程任务(实现类)
    • 调用shutdown销毁线程池(不建议执行)
package itb;

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

public class Main2 {
    public static void main(String[] args) {

        ExecutorService es = Executors.newFixedThreadPool(2);
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.shutdown();
    }
}
package itb;

public class RunnableImpl implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() +"创建了一个新的线程");
    }
}

观黑马java所写

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值