##1、概念
?线程是jvm调度的最小单元,也叫做轻量级进程,进程是由线程组成,线程拥有私有的程序技术器以及栈,并且能够访问堆中的共享资源。这里提出一个问题,为什么要用多线程?有一下几点,首先,随着cpu核心数的增加,计算机硬件的并行计算能力得到提升,而同一个时刻一个线程只能运行在一个cpu上,那么计算机的资源被浪费了,所以需要使用多线程。其次,也是为了提高系统的响应速度,如果系统只有一个线程可以执行,那么当不同用户有不同的请求时,由于上一个请求没处理完,那么其他的用户必定需要在一个队列中等待,大大降低响应速度,所以需要多线程。这里继续介绍多线程的几种状态:
这里可以看到多线程有六种状态,分别是就绪态,运行态,死亡态,阻塞态,等待态,和超时等待态,各种状态之间的切换如上图所示。这里的状态切换是通过synchronized锁下的方法实现,对应的Lock锁下的方法同样可以实现这些切换。
##2、线程的创建
?线程的创建有两种方式,第一种是继承Thread类,第二种是实现Runnable接口。第一个代码:
然后main函数中创建:
第二种方法:
main函数中:
这就是两种创建线程的方法,在这两种方法中第二种方法时一般情况下的用法,因为继承只能继承一个类,但是可以实现多个接口,这样拓展性更好。
##3、线程安全测试
?线程安全是多线程编程中经常需要考虑的一个问题,线程安全是指多线程环境下多个线程可能会同时对同一段代码或者共享变量进行执行,如果每次运行的结果和单线程下的结果是一致的,那么就是线程安全的,如果每次运行的结果不一致,那么就是线程不安全的。这里对线程安全做一个测试:
main函数中:
可以看到,这里同时两个线程同时对共享变量j进行访问,并且减1,但最后的输出结果却是:
并且多次执行程序的结果还不一致,这就是线程不安全的情况,通过加锁可以保证线程安全。
##4、锁
?java中有两种锁,一种是重量级锁synchronized,jdk1.6经过锁优化加入了偏向锁和轻量级锁,一种是JUC并发包下的Lock锁,synchronized锁也称对象锁,每个对象都有一个对象锁。这里通过加锁的方式实现线程安全:
代码:
main中创建两个线程,测试多次的结果都是:
说明实现的线程安全,因为当加锁过后,每次只能有一个线程访问被加锁的代码,这样就不会出现线程安全了。
##5、sleep
?sleep是让当前线程睡眠,睡眠一段时间后重新获取到cpu的使用权。
代码如下:
这里表示线程会睡眠100ms后再次到就绪状态中,这里为什么sleep是Thread的类方法而不是线程的方法,因为,能调用sleep的线程肯定是运行着的,而其他线程也是未运行的,所以调用其他线程的sleep是没有意义的。
##6、wait、notify
?wait表示当前线程释放掉锁进入等待状态,所以调用wait和notify的前提是已经获取到对象的锁,如果没有获取到锁就使用wait那么会出异常信息。而进入等待状态的线程需要通过notify或者通过中断来唤醒进入到阻塞状态来等待锁的获取。这里对这种情况进行测试,使用notify唤醒一个等待状态的线程:
main中:
这里可以看到,在main函数中,主线程将创建一个线程t1然后进入t1的锁的同步块中启动线程t1,然后调用wait进入等待状态,这个时候线程t1也进入到同步块中,调用notify后释放掉锁,可以看到主线程后续的东西继续被输出。当有多个线程调用了wait之后如果采用notify只会随机的唤醒其中的一个线程进入阻塞态,而采用notifyall会将所有的线程给唤醒。在线程运行结束后会调用notifyall将所有等待状态的线程唤醒。
##7、join
?join的作用是让父线程等待子线程运行结束后在运行,通过查看源码可以知道:
其实也是调用了先获取到子线程的锁然后调用wait方法来实现的,因为当子线程运行结束后会调用notifyall所以主线程会被唤醒并且再次获取到子线程的锁继续运行。
main函数中:
运行结果:
可以看到,当主线程调用join后子线程开始运行,等子线程运行结束后主线程被唤醒。
##8、yeild
?yeild的作用是线程让步,当前线调用yeild方法后线程从运行态进入到就绪态重新进入到CPU资源的竞争中。这里进行测试:
main函数中:
结果:
可以看到他们两个基本上是交替运行,而不用yeild让步的话大概率一个线程执行完成了另一个线程才会执行。
##9、priority
?priority代表线程的优先级,在JVM中优先级高的线程不一定会先运行,只是先运行的概率会比低优先级的线程大。
##10、中断
?对于一个正常运行的线程中,中断基本是没有作用的,只是作为一个标志位来查询。而线程的其他几种状态下如sleep、join、wait状态下是可以被中断,并且通过中断来跳出当前状态的。
main中:
结果
可以看到,当主线程启动子线程后,子线程会进入到循环中并且进入到睡眠状态,然后主线程通过调用中断让子线程唤醒并且推出循环后死亡。
##11、死锁
?死锁指的是,两个线程互相等待对方释放资源导致卡死。例子:
可以看到t1线程获得A的锁然后睡眠,然后t2线程获得B的锁然后再等待A释放锁,而线程t1睡眠完成后在等待t2释放B的锁,导致程序卡死。
##12、生产者与消费者
?生产者和消费者是多线程中一个很常见的应用场景,这里首先用一个共享变量实现生产者和消费者,接着再使用阻塞队列实现。首先实现第一种:
仓库代码:
生产者代码:
消费者代码:
main中:
结果:
可以看到实现了生产者消费者模型。
第二种利用阻塞队列实现。直接利用阻塞队列当做仓库,生产者:
消费者:
main函数:
结果:
这里阻塞队列的作用是,当容量不足的消费者进入等待队列,而当容量有剩余的时候消费者被唤醒,当容量已满的时候生产者进入等待队列,当容量被消费后生产者被唤醒。
标签:main,java,##,入门教程,调用,线程,多线程,运行 From: https://blog.51cto.com/yetaotao/5800188