什么是多线程?
举个例子:
小明有个很不好的习惯,吃饭的时候喜欢讲话。他吃一口饭,说一句话,又吃一口饭,又说了三句话,他边吃饭边说话,而不是单纯的进行一项活动,这就是多线程。至于他想吃饭,还是想说话,完全看他的心情,多线程的运行也是如此,想运行哪个服务,完全看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();
}
}
线程的五个状态
线程的常用方法
- setPriority(int newPriority) :设置优先级,多线程优先级区间为 [1,10],默认优先级为5。
- sleep(long millis) :让指定线程休眠单位时间(毫秒)。
- join() :强启这个线程,直至执行完,再执行其他线程。
- yield():暂停这个线程,执行其他线程。
- interrupt():中断这个线程(不建议使用这种方式,推荐设计代码让线程运行结束自行停止)。
- isAlive():判断这个线程是否还处于活动状态。
- setDaemon(boolean bool):设置这个线程是否为守护线程。
用户线程和守护线程
线程分为两种,用户线程和守护线程。
用户线程就是一般用户定义的线程(当然,用户也可以定义守护线程)。
垃圾回收线程是典型的守护线程。
如果程序中所有的用户线程都退出了,那么所有的守护线程就都会被杀死,哪怕你的守护线程运行条件设置为无限循环,也会被停止。
线程同步方法之 synchronized 关键字
一个线程调用被synchronized修饰的资源时,会被分配一个“锁”,我更喜欢把它理解为“钥匙”,只有有这个“钥匙”的线程才能打开“锁”,操作资源。当此线程释放资源后,“钥匙”也会被释放,下一个线程才能拿到“钥匙”,对资源再进行操作。以此保证同一时间只有一个线程对同一资源进行操作,防止数据冲突产生严重错误。
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
- 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁。
- 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁。
- 同步方法块,锁是括号里面的对象,对给定对象加锁,一般选取需要操作的对象,进入同步代码库前要获得给定对象的锁。
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停止了,子线程肯定无法继续运行了。