关于Java多线程

什么是多线程?

举个例子:
小明有个很不好的习惯,吃饭的时候喜欢讲话。他吃一口饭,说一句话,又吃一口饭,又说了三句话,他边吃饭边说话,而不是单纯的进行一项活动,这就是多线程。至于他想吃饭,还是想说话,完全看他的心情,多线程的运行也是如此,想运行哪个服务,完全看cpu的“心情”,我们能尽量改变cpu的“心情”,但不能强制它做决定,这就是线程优先级。
虽然看起来他是“边吃饭边说话”,但其实是在“交替进行”。线程也是如此,因为cpu处理速度极快,所以我们认为两个线程在同时进行。

java实现多线程的三种方式

第一种:继承Thread 类

不多说,直接上代码。

//第一种实现方式,继承Thread类
public class Thread01 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("thread01线程执行--"+i);
        }
    }
}

我们新建一个主线程类,调用这个子线程,进行测试。

public class ThreadMain {
    public static void main(String[] args) {
        Thread01 thread01=new Thread01();
        thread01.start();
        for (int i = 0; i < 200; i++) {
            System.out.println("测试线程--"+i);
        }
    }
}

这里我们先调用thread01的start() 方法
测试结果:
在这里插入图片描述这里我们可以看出,程序交替执行。

然后我们再调用thread01的run() 方法测试。

public class ThreadMain {
    public static void main(String[] args) {
        Thread01 thread01=new Thread01();
        thread01.run();
        for (int i = 0; i < 200; i++) {
            System.out.println("测试线程--"+i);
        }
    }
}

经过多次反复测试,发现每次都是先执行完子线程的for循环,又进行主线程的for循环。
在这里插入图片描述
因此我们可以推导出,start()方法和run()方法的执行流程。
在这里插入图片描述(start()方法的执行是并发执行,作图的时候写成了“并行”,不严谨)

第二种:实现Runnable接口(推荐使用)

新建线程thread02:

public class Thread02 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 2000; i++) {
            System.out.println("thread02线程执行--"+i);
        }
    }
}

测试线程:

public class ThreadMain {
    public static void main(String[] args) {
        Thread02 thread02=new Thread02();
        //这里执行start(),也是要通过Thread对象执行,thread02作为参数传入
        new Thread(thread02).start();
        for (int i = 0; i < 2000; i++) {
            System.out.println("测试线程--"+i);
        }
    }
}

这种方法避免了OOP的单继承局限性,相比较直接继承Thread类,推荐使用此方法。

第三种:实现Callable接口

新建线程thread03:


//这里需要自定义一个返回值,我在这里设置为boolean类型
public class Thread03 implements Callable<Boolean> {
    private String name;
    public Thread03(String name){
        this.name=name;
    }
    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 2000; i++) {
            System.out.println(name+"线程执行--"+i);
        }
        return true;
    }
}

测试线程:

public class ThreadMain {
    public static void main(String[] args) throws Exception {
        Thread03 thread=new Thread03("thread---1---");
        Thread03 thread2=new Thread03("thread---2---");
        //创建一个容量为2的线程池
        ExecutorService executorService= Executors.newFixedThreadPool(2);
        //把两个thread放入线程池
        Future<Boolean> rs = executorService.submit(thread);
        Future<Boolean> rs2 = executorService.submit(thread2);
        //获取线程运行结果,对应Thread03.call()方法中的return
        Boolean aBoolean = rs.get();
        Boolean aBoolean2 = rs.get();
        System.out.println("执行结果1:"+aBoolean+",执行结果2:"+aBoolean2);
        //运行结束,不允许再往线程池中添加任务,但已经存在的任务会继续执行完
        executorService.shutdown();
    }
}

线程的五个状态

在这里插入图片描述

线程的常用方法
  1. setPriority(int newPriority) :设置优先级,多线程优先级区间为 [1,10],默认优先级为5。
  2. sleep(long millis) :让指定线程休眠单位时间(毫秒)。
  3. join() :强启这个线程,直至执行完,再执行其他线程。
  4. yield():暂停这个线程,执行其他线程。
  5. interrupt():中断这个线程(不建议使用这种方式,推荐设计代码让线程运行结束自行停止)。
  6. isAlive():判断这个线程是否还处于活动状态。
  7. setDaemon(boolean bool):设置这个线程是否为守护线程。
用户线程和守护线程

线程分为两种,用户线程和守护线程。
用户线程就是一般用户定义的线程(当然,用户也可以定义守护线程)。
垃圾回收线程是典型的守护线程。
如果程序中所有的用户线程都退出了,那么所有的守护线程就都会被杀死,哪怕你的守护线程运行条件设置为无限循环,也会被停止。

线程同步方法之 synchronized 关键字

一个线程调用被synchronized修饰的资源时,会被分配一个“锁”,我更喜欢把它理解为“钥匙”,只有有这个“钥匙”的线程才能打开“锁”,操作资源。当此线程释放资源后,“钥匙”也会被释放,下一个线程才能拿到“钥匙”,对资源再进行操作。以此保证同一时间只有一个线程对同一资源进行操作,防止数据冲突产生严重错误。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  1. 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁。
  2. 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁。
  3. 同步方法块,锁是括号里面的对象,对给定对象加锁,一般选取需要操作的对象,进入同步代码库前要获得给定对象的锁。
Lock 锁 和 synchronized 的区别

两者区别:

1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;
5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)
6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

什么是死锁
神评:“车钥匙在家里,家钥匙在车里”

比如有两个线程,每个线程要执行四个操作:

第一个线程:1.拿到筷子 2.拿到碗 3.吃饭 4.把碗筷放回餐柜
第二个线程:1.拿到碗 2.拿到筷子 3.吃饭 4.把碗筷放回餐柜

这个时候发生了这样一件事:第一个线程拿到了筷子的同时,第二个线程拿到了碗。由于第一个线程没有办法拿到碗,所以造成了线程阻塞,进而无法释放筷子资源。而第二个线程也无法拿到筷子,也造成了阻塞,无法释放碗资源。因此造成的后果,就叫做死锁。

关于junit4测试多线程结果不正确的问题

我在之前测试多线程时一直用的main方法而不是junit的@Test注解是有原因的。
我写了一个线程类进行测试:

public class Thread01 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("thread01线程执行--"+i);
        }
    }
}

输出结果:
在这里插入图片描述
我们发现当输出到52的时候线程就停止了,并没有像我们想象的输出到1000。

这里我们看一下junit4的源代码:junit.textui.TestRunner 类

 public static void main(String args[]) {
        TestRunner aTestRunner = new TestRunner();
        try {
            TestResult r = aTestRunner.start(args);
            if (!r.wasSuccessful()) {
                System.exit(FAILURE_EXIT);
            }
            System.exit(SUCCESS_EXIT);
        } catch (Exception e) {
            System.err.println(e.getMessage());
            System.exit(EXCEPTION_EXIT);
        }
    }

这段代码的大概意思就是:主线程(main)成功执行结束后,执行 System.exit(SUCCESS_EXIT),也就是直接停止jvm。jvm停止了,子线程肯定无法继续运行了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值