java多线程

程序,进程,线程,并行,并发

  • 程序是静态的,进程process是动态的
  • 一个进程至少有一个线程
  • 多线程程序优点
    • 提高应用程序的响应
    • 提高CPU利用率
    • 改善程序结构
  • 并行parallel,指两个或多个事件在同一时刻发生
  • 并发concurrency,两个或多个事件在同一个时间段内发生,宏观上是多个进程同步进行

创建和启动线程

JVM允许程序运行多个线程,java.lang.thread代表线程。所有的线程对象都必须是Thread类或其子类实例。

创建线程的方式:

  • 1、继承Thread类
  • 2、实现Runnable接口

创建线程方式1示例

java 复制代码
public class EvenNumberTest {
    public static void main(String[] args) {

        PrintNumber printNumber = new PrintNumber();
        printNumber.start(); // 启动线程,调用当前线程的run(),即子类重写的run()方法
    }
}


class PrintNumber extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++){
            if ( i % 2 == 0) System.out.println(i);
        }
    }
}
  • 获取当前线程的名称:Thread.currentThread().getName()

  • 一条路径就是单线程。

  • 不能用run()代替start()方法,start()要先启动线程,创建一个新的线程。

  • 不能让已经start()的线程,再次执行start()操作。

创建线程方式2示例

java 复制代码
public class EvenNumberTest {
    public static void main(String[] args) {
        EvenNumberPrint t1 = new EvenNumberPrint();
        new Thread(t1).start();

    }
}


class EvenNumberPrint implements Runnable{

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

创建方式1,方式2的区别

共同点:

  • 启动线程,使用的都是Thread类中定义的start()
  • 创建的线程对象,都是Thread类或其子类的实例。

不同点:一个是类的继承,一个是接口的实现。

建议:建议使用实现Runnable接口的方式。

Runnable方式的好处:

① 实现的方式,避免的类的单继承的局限性

② 更适合处理有共享数据的问题。

③ 实现了代码和数据的分离。

联系:public class Thread implements Runnable (代理模式)

Thread类常用方法和生命周期

构造器

  • public Thread() :分配一个新的线程对象。
  • public Thread(String name) :分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :指定创建线程的目标对象,它实现了Runnable接口中的run方法
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

public Thread(String name)示例

java 复制代码
public class EvenNumberTest {
    public static void main(String[] args) {
        EvenNumberThread t1 = new EvenNumberThread("线程1");// 相当于给线程命名
        t1.start();

        

    }
}

class EvenNumberThread extends Thread{

    public EvenNumberThread(){

    }

    public EvenNumberThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++){
            if (i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

线程常用方法

  • start():①启动线程 ②调用线程的run()
  • run():将线程要执行的操作,声明在run()中。
  • currentThread():获取当前执行代码对应的线程
  • getName(): 获取线程名
  • setName(): 设置线程名
  • sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数
  • yield():静态方法,一旦执行此方法,就释放CPU的执行权
  • join(): 在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行。
  • isAlive():判断当前线程是否存活

线程的优先级

每个线程都有一定的优先级,同优先级线程组成先进先出队列,使用分时调度。优先级高的采用抢占式策略。

每个线程的默认优先级与创建他的父线程具有相同的优先级。

  • getPriority():获取线程的优先级
  • setPriority():设置线程的优先级。范围[1,10]

Thread的三个优先级常量:

  • MAX_PRIORITY(10),最高优先级
  • MIN_PRIORITY,最低优先级
  • NORM_PRIORITY,普通优先级

生命周期

JDK1.5之前,5中状态

之后

阻塞分的更细

同步代码块解决两种线程创建方式的线程安全问题

示例及原因

同一个资源问题和线程安全问题。模拟车站售票,实现多个窗口同时售票。不能出现错票,重票。

下面的实现,是一个线程不安全的问题。两种实现线程方式都会出现重票,错票的情况。

java 复制代码
public class WindowTest {
    public static void main(String[] args) {
        // 创建对象
        SaleTicket s = new SaleTicket(); // 创建一个对象,被三个线程所共享
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);
        // 创建三个窗口
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

//        SaleTicket1 t2 = new SaleTicket1();
//        t2.start();
    }
}

原因是当前进行的线程还没结束,其他线程也参与进来,对ticket进行操作。

解决办法

必须保证一个线程a在操作ticket的过程中,其它线程必须等待,直到线程a操作ticket结束以后,其它线程才可以进来继续操作ticket。

这里ticket共同操作的数据,即共享数据。

java解决方式

使用线程的同步机制。

两种方式:

  • 同步代码块
  • 同步方法
    本质上是一样的。

同步代码块

java 复制代码
synchronized(同步监视器){
    //需要被同步的代码
}

说明:

  • 需要被同步的代码,即为操作共享数据的代码。
  • 共享数据:即多个线程都需要操作的数据。比如:ticket
  • 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其它线程必须等待。
  • 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。
  • 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。
使用接口runnable

在实现Runnable接口的方式中,同步监视器可以考虑使用:this

java 复制代码
public class WindowTest {
    public static void main(String[] args) {
        // 创建对象
        SaleTicket s = new SaleTicket(); // 创建一个对象,被三个线程所共享
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);
        // 创建三个窗口
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

//        SaleTicket1 t2 = new SaleTicket1();
//        t2.start();
    }
}

// 接口方式
class SaleTicket implements Runnable{
    Integer tickets = 100;

    Object obj =  new Object();

    @Override
    public void run() {
//        System.out.println(Thread.currentThread().getName() +":卖票");
        while (true){
            // 增加休眠时间
            try{
                Thread.sleep(5);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            synchronized(obj){ // 可直接用this,也代替对象
				// 这里面如果添加sleep也是锁住进程的
                if (tickets > 0){
                    System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
                    tickets --;
                }else break;
            }
        }

    }
}
继承Thread

如果监视器使用this,则每个线程会有一个监视器,不能保证锁的唯一性。

在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class

java 复制代码
public class WindowTest {
    public static void main(String[] args) {
        // 创建对象


        SaleTicket1 t2 = new SaleTicket1();
        SaleTicket1 t3 = new SaleTicket1();
        SaleTicket1 t4 = new SaleTicket1();
        t2.setName("窗口2");
        t3.setName("窗口3");
        t4.setName("窗口4");
        t2.start();
        t3.start();
        t4.start();
    }
}

class SaleTicket1 extends Thread {
    static int tickets = 100;
    static Object obj = new Object(); // 加上static,静态唯一的

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            synchronized (obj) {// 实际上也是对象class clz = 当前类.class 
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
                    tickets--;
                } else break;
            }

        }
    }
}

同步方法

如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。

  • 非静态的同步方法,默认同步监视器是this。
  • 静态的同步方法,默认同步监视器是当前类本身。
使用接口runnable

先将同步代码块中的需要被同步的代码用方法进行封装,此时还是同步代码块。

java 复制代码
class SaleTicket2 implements Runnable{
    Integer tickets = 100;

    Object obj =  new Object();

    Boolean isFlag = true;
    @Override
    public void run() {
//        System.out.println(Thread.currentThread().getName() +":卖票");
        while (isFlag){
            // 增加休眠时间
            try{
                Thread.sleep(5);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            synchronized(obj){

               show();
            }
        }

    }

    public void show() {
        if (tickets > 0){
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
            tickets --;
        }else{
            isFlag = false;
        }
    }
}

修改为同步方法,

非静态的同步方法,默认同步监视器是this。

java 复制代码
  public synchronized void show() { // 非静态,监视器是this
        if (tickets > 0){
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
            tickets --;
        }else{
            isFlag = false;
        }
    }

注意:synchronized好处:解决了线程的安全问题。

弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。

java 复制代码
public class WindowTest1 {
    public static void main(String[] args) {
        SaleTicket2 t2 = new SaleTicket2();
        SaleTicket2 t3 = new SaleTicket2();
        SaleTicket2 t4 = new SaleTicket2();
        t2.setName("窗口2");
        t3.setName("窗口3");
        t4.setName("窗口4");
        t2.start();
        t3.start();
        t4.start();
    }
}


class SaleTicket2 extends Thread {
    static int tickets = 100;
    static Object obj = new Object(); // 加上static,静态唯一的

    static boolean isFlag = true;
    @Override
    public void run() {
        while (isFlag) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

//            synchronized (SaleTicket1.class) { // 实际上也是对象class clz = 当前类.class
            show();
//            }

        }
    }

    public static synchronized void show(){
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
            tickets--;
        } else isFlag = false;
    }
}
相关推荐
草莓base几秒前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
Allen Bright14 分钟前
maven概述
java·maven
编程重生之路16 分钟前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端
薯条不要番茄酱16 分钟前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
努力进修25 分钟前
“探索Java List的无限可能:从基础到高级应用“
java·开发语言·list
politeboy26 分钟前
k8s启动springboot容器的时候,显示找不到application.yml文件
java·spring boot·kubernetes
Daniel 大东1 小时前
BugJson因为json格式问题OOM怎么办
java·安全
Theodore_10225 小时前
4 设计模式原则之接口隔离原则
java·开发语言·设计模式·java-ee·接口隔离原则·javaee
冰帝海岸6 小时前
01-spring security认证笔记
java·笔记·spring
世间万物皆对象7 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试