JavaSE:多线程

程序、进程和线程

**程序:**为了完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
**进程:**程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,有它自身的产生、存在和消亡的过程。
**线程:**进程课进一步细化为线程,是一个程序内部的一条执行路径。

说明:

  • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
  • 若一个进程同一时间并行执行多个线程,就是支持多线程的
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
  • 一个进程中的多个线程共享相同的内存单元/内存地址空间,他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。

一个Java应用程序java.exe ,其实至少有三个线程:main()主线程,gc垃圾回收线程,异常处理线程。

并行于并发

并行:多个CPU同时执行多个任务。比如:多个人同时做不同的的事

并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事

使用多线程的优点

1.提高应用程序的响应。对图形化界面更有意义,可增强用户一滩。

2.提高计算机系统CPU的利用率。

3.改善程序结构。将既长又复杂的进程分为多个进程,独立运行,利于理解和修改。

线程的创建和使用

方式一:继承thread类

步骤:

  • 创建一个继承于thread 类的子类
  • 重写Thread 类的run()方法
  • 将此线程执行的操作声明在run()方法中
  • 创建Thread 类的子类和对象
  • 通过对象调用start()方法,使线程进入就绪状态

注意:

  • 不能够使用直接调用run() 方法启动线程。
  • 不可以再次调用已经start 的线程。

示例

//1.创建一个继承于Thread的子类
class MyThread extends Thread{
    //2.重写thread类中的run()
    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
                System.out.println("线程MyThread:" + i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3.创建子类对象
        MyThread t1 = new MyThread();
        //4.通过对象调用start()
        t1.start();
        for(int i = 0;i < 100;i++){
            if(i % 2 != 0){
                System.out.println("主线程main:" + i);
            }
        }
    }
}

运行结果:
两个线程随机交替执行

线程中常用的方法

  1. start()启动当前线程:调用当前线程的run()
  2. run():通常需要重写Thread 类中的此方法,将创建的线程要执行的操作声明在此方法中
  3. currentThread():静态方法,返回执行当前代码的线程
  4. getName():获取当前线程的名字
  5. setName():设置当前线程的名字
  6. yield():释放当前cpu 的执行权
  7. jion():在线程a中调用线程b的jion(),此时线程a就进入阻塞状态,直到线程b完成执行后,线程b才结束阻塞状态
  8. stop():已过时。当执行此方法时,强制结束当前线程
  9. sleep(long millitime):让当前线程“睡眠”指定的millitime 毫秒。在指定的millitime 毫秒时间内,当前线程是阻塞状态。
  10. isAlive():判断当前线程是否存活

线程的优先级

线程的优先级:

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5 -------->默认优先级

如何获取和设置当前线程的优先级:

getPriority():获取当前线程优先级

setPriority():设置当前线程优先级

在分线程开始之前设置优先级

注意:

  • 高优先级并不意味着一定优先执行,只是提高了优先执行的概率。
  • 线程创建时继承父线程的的优先级。

方式二:实现Runnable接口

创建步骤:

  1. 定义子类,实现Runnable接口
  2. 子类中重写Runnable接口中的run 方法
  3. 通过Thread类含参构造器创建线程对象。
  4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  5. 调用Thread类的start 方法:开启线程,调用Runnable子类接口的run方法。

示例

//1.创建一个实现Runnable接口的类
class MyThread implements Runnable{
    @Override
    //2.实现类去实现Runnable中的抽象接口run()
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){

                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadText02 {
    public static void main(String[] args) {
        //3.创建实现类的对象
        MyThread thread = new MyThread();
        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(thread);
        t1.setName("线程1001");
        //5.通过Thread类的对象调用start()
        t1.start();

        //多线程
        Thread t2 = new Thread(thread);
        t2.setName("线程1002");
        t2.start();
    }

}

运行结果:
在这里插入图片描述
使用实现Runnable接口的好处:

  • 避免了类单继承的局限性
  • 多个线程可以共享一个接口实现类的对象,非常适合多个相同线程来处理同一份资源

线程的声明周期

一个线程完整的生命周期通常要经历如下五个阶段:

  1. 新建:当一个Thread类或其子类的对象被声明并创建,新生的线程对象就处于新建状态
  2. 就绪:新建状态的线程在被调用strat 方法后,将进入线程队列等到CPU时间片,此时处于就绪状态
  3. 运行:当就绪状态的线程被调度,获取了CPU资源之后,就会进入运行状态,执行run方法中的代码
  4. 阻塞:在某种特殊的情况下,被认位挂起或执行输入输出操作时,让出CPU资源暂停执行本线程,进入阻塞状态
  5. 死亡:当线程完成了它的全部工作,或者线程被提前强制性结束,或者出现异常时,线程死亡

在这里插入图片描述

线程的同步

问题的出现:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

Synchronized 的使用

同步锁机制:

在多线程中,为了防止多个线程对同一共享资源同时访问的情况发生,就需要对共享资源加上一个锁,使得一个线程在访问此共享资源时,其他要访问此共享资源的线程必须等待,直到正在访问的线程释放锁,其他线程之一才能访问此共享资源。

Sychronized的锁:

  1. 任意对象都可以作为同步锁,所有对象都自动含有单一的锁。
  2. 同步方法的锁:静态方法(类名.class)、非静态方法(this)
  3. 同步代码块:可以自己任意指定,通常也指定为this或 类名.class

注意:

  • 必须保证同一个资源的多个线程共用同一把锁,否则无法保证线程安全。
  • 一个线程类中的所有静态方法共用同一把锁(类名.class) ,所有非静态方法公用同一把锁(this)

方式一:同步代码块

方式一:同步代码块
  synchronized(同步监视器){
      //需要被同步的代码

  }
示例

未加锁,导致相同票号重复卖出,共享数据未同步。

class Windows extends Thread{
    private static int ticket = 10;

    @Override
    public void run() {

        while (ticket > 0){
            System.out.println(getName() + ":卖票,票号为:" + ticket );
            ticket--;
        }
        if (ticket == 0){
            System.out.println("售票完毕");
        }
    }
}

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

        Windows w1 = new Windows();
        Windows w2 = new Windows();

        w1.setName("窗口一");
        w2.setName("窗口二");

        w1.start();
        w2.start();
    }
}

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

添加锁,解决问题

class Windows02 implements Runnable{
    private int ticket = 10;

    @Override
    public void run() {
        while (true) {
            synchronized (Windowss02.class) {
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    System.out.println("票已售罄");
                    break;
                }
            }
        }
    }
}

public class WindowsExer02 {
    public static void main(String[] args) {
        Windows02 w = new Windows02();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);

        t1.setName("窗口一");
        t2.setName("窗口二");

        t1.start();
        t2.start();
    }
}

运行结果:不在出现重复售票问题。
在这里插入图片描述

方式二:同步方法

用同步方法实现同上售票案例

class Windows03 implements Runnable{
    private int ticket = 20;

    @Override
    public void run() {
        while (ticket > 0) {
            sale();
        }
    }

    private synchronized void sale(){//同步监视器:this
    //同步方法
     	System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
        ticket--;
    }
}

public class WindowSExer03 {
    public static void main(String[] args) {
        Windows03 w = new Windows03();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);

        t1.setName("窗口一");
        t2.setName("窗口二");

        t1.start();
        t2.start();
    }
}

运行结果:同样解决了重复售票的问题。
在这里插入图片描述

Lock 锁(JDK 5.0新增)

解决同一问题的方式三:lock锁

class Window implements Runnable{
    private int sticket = 21;
    //实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                //调用一个锁定的方法lock()
                lock.lock();
                if (sticket > 0){
                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + sticket );
                    sticket--;
                }else{
                    break;
                }
            }finally {
                //3.调用解锁的方法unlock()
                lock.unlock();
            }
        }
    }
}
public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);

        t1.setName("线程一");
        t2.setName("线程二");

        t1.start();
        t2.start();
    }
}

运行结果:
在这里插入图片描述
注意:lock锁是显示锁,需要自己手动开启,手动关闭。使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。

线程的通信

线程间通信的三个常用方法
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器

notify();一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级最高的线程

notifyAll():一旦执行此方法,就会唤醒所有被wait的线程

注意:

  1. wait(),notify,notifyAll()三个方法必须使用在同步代码块活同步方法中
  2. 这三个方法的调用者必须是同步代码块活同步方法的同步监视器,否则会出现IllegalMonitorStateException异常
  3. 这三个方法是定义在java.lang.Object类中的。

示例:交替打印1-50

public class CommunicationTest {
    public static void main(String[] args) {
        Number num = new Number();

        Thread t1 = new Thread(num);
        Thread t2 = new Thread(num);

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}


class Number implements Runnable{
    int number = 1;

    @Override
    public void run() {
        while (number < 50){
            synchronized (this){
                this.notify();
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName() + ":" + number);
                number++;
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

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

JDK 5.0 新增线程创建方式

新增方式一:实现Callable接口

与使用Runnable相比,Callable功能更强大些

  1. 相比run()方法,可以有返回值
  2. 方法可以抛出异常
  3. 支持泛型的返回值
  4. 需要借助FutureTask类,比如获取返回结果

步骤:

  1. 创建一个实现Callable 的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中
  3. 创建Callable接口实现类的对象
  4. 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
  5. 将FutureTask的对象作为参数传递
  6. 到Thread类的构造器中,创建Thread对象,并调用start();
  7. 获取Callable中call方法的返回值,get()方法返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值

示例:

//1.创建一个实现Callable的实现类
class NumThread implements Callable{
    //实现call()方法,将此线程需要执行的操作声明再call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread num = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(num);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start();
        Thread thread = new Thread(futureTask);
        thread.start();
        //6.获取Callable中call()方法的返回值
        //get()方法返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
        try {
            Object sum = futureTask.get();
            System.out.println(sum);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

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

方式二:线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。

好处:

  1. 提高相应速度(减少了创建新线程的时间)
  2. 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  3. 便于线程管理

示例:

class NumberThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


class CallThread implements Callable{

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

public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //2. 执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合用于Runnable
        service.submit(new CallThread());//适合用于Callable
        
        //关闭线程池
        service.shutdown();

    }
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值