1、首先从宏观上解释一下多线程到底是个什么东西,我们拿一个生活中例子来看一下;汽车上高速和出高速的时候都需要经过收费站吧,我们可以脑补下这个场景;现在汽车要下高速,收费站最主要的任务是对每辆车进行收费,这样我们可以把收费站看成一个程序方法体,专门处理收费这个功能;这里我们先要抛弃掉多个窗口的那种收费站,最原始的情况下,就像在一条上设了一个卡口,每辆车都要只能从这个一个卡口通过,这是原始的情况;当然我们知道这种方式效率很慢,所有的车都必须等待,只能从一个口出,进而为了提高效率收费站进行了改造,不是单一的窗口了,而是并排建了多个收费窗口,车辆可以选择任意一个窗口缴费,因为每个窗口的功能都是一样的;这样一对比,比原始的单一窗口效率高出好几倍;同样程序代码中也是这样,每个任务相当于每辆车,进入方法体都要执行缴费的功能,在程序代码中开辟多个线程来处理,提高执行效率;
2、好了,在大致理解什么是多线程之后,我们在来看java对线程的解释也就简单的多了;java中对线程的解释是这样的,线程是进程的单位,进程是由线程组成;这里我们可以把收费站看成一个单一功能的软件,这个软件开启了一个收费进程;进程中最少也有一条线程在执行这里什么意思呢,就是说进程其实就是有线程组成的,确实,同样拿收费站来看,最原始的时候收费站就只有一个窗口,就相当于一条线程,也可以是改良后的多个窗口,多个线程组成;
同样线程的一些特点也能解释的同了,同一类线程共享数据空间和代码块,代码块就是指的收费站这个整体,而数据空间则是指每个窗口其实都是同一个数据库,每条记录都是记在同一张表中,不管是从哪个窗口录入的;每个线程有独立运行的栈和程序计数器,也就相当于每个窗口,每个收费窗口之间互不影响,每个窗口都记录从该窗口缴费的车型,费用;
1)、继承Thread类;
2)、实现Runnable接口;
3)、实现Callable接口,该接口是用于线程池中的,后面再讲;
/**
* Created by zelei.fan on 2017/7/23.
*/
public class ThreadTest extends Thread {
private int num;
ThreadTest(int num){
this.num = num;
}
@Autowired
public void run(){
System.out.println("缴费" + num);
}
}
/**
* Created by zelei.fan on 2017/7/23.
*/
public class test {
public static void main(String[] args) {
for (int i = 0; i < 10; i ++){
int num = new Random().nextInt(100);/*取一个随机数*/
ThreadTest threadTest = new ThreadTest(num);
threadTest.start();
}
}
}
/**
* Created by zelei.fan on 2017/7/23.
*/
public class ThreadTest implements Runnable {
private int num;
ThreadTest(int num){
this.num = num;
}
@Autowired
public void run(){
System.out.println("缴费" + num);
}
}
/**
* Created by zelei.fan on 2017/7/23.
*/
public class test {
public static void main(String[] args) {
int num = new Random().nextInt(100);/*取一个随机数*/
ThreadTest threadTest = new ThreadTest(num);/*这边实例其实是可以方法循环中,但是这杨就不能体现出和thread的差别了*/
for (int i = 0; i < 10; i ++){
new Thread(threadTest).start();
}
}
}
继承thread类的实例不能重复调用start(),原因是一个thread实例不能重复调用start(),因为当前这个thread已经变成可执行状态,当再次调用start()方法时,有可能线程已经在执行过程中了,而线程中除了阻塞和可运行状态之间可以相互逆转之外,其他状态时不能逆转的,所以也就解释了上述问题,运行中不能再次逆转成可运行状态;
但是实现runnable接口则不然,只new一个程序代码,但是多线程的时候是每用一个都是new一个新的thread类来启动这个程序实例这样就不存在上述thread的那样的问题,同时也实现了多个线程处理同一资源;
1、runnable适合多个相同的程序代码的线程去处理同一资源;当用继承thread类时,new出的多个ThreadTest实例,在同时启动线程的时候这几个线程都是在各自运行各自中的资源, 各自的线程中都有num这个资源,并且代码空间也都是互相独立,因为ThreadTest被new出多个实例,这几个线程并不共用同一实例;
但是实现runnable接口就不一样了,new出一个RunnableTest实例后,下面几个线程都是用的同一个实例来启动线程,当执行到程序中时,每个线程用的代码都是同一个,而且用的资源num也是同一个,这样就实现了多线程对同一资源的处理;
2、可以避免java中的单继承的限制,继承thread:如果ThreadTest需要继承一个父类代码,但是又同时想实现多线程这个功能,那就和java单继承有冲突,实现runnable:可以实现在继承thread的同时再实现runnable接口;
3、代码可以被多个线程共享,代码和数据独立; 代码共享:都是用的同一实例;代码和数据独立:即代码和数据是分开的,数据是一个公共的资源,个线程之间都能使用
4、线程池中只能放入runnable接口或者callable接口;
1、创建一个线程(new Thread())
2、可运行状态(调用start()方法)
3、正常情况下,获取到cpu使用权,直接执行
4、阻塞:
1)、等待阻塞,线程调用wait()方法,jvm会吧该线程放到等待池中;
2)、同步阻塞,线程在获取同步锁时,同步锁还在被其他线程占用中,jvm把该线程放到锁池中;
3)、其他阻塞,调用sleep()或者join(),线程sleep超时或者join等到其他线程终止,改线成则重新回到可执行状态;(注意执行sleep时不释放锁)
5、线程结束
Thread.sleep(100);:sleep不会释放对象锁,而wait在调用后会释放对象锁
new Thread().wait();:线程等待,等到其他线程调用notify()或者notifyAll()方法来唤醒这些wait的线程
Thread.yield();:线程让步,把资源让给优先级高的线程,让当前线程回到可执行状态,允许相同优先级的其它线程获得执行机会
new Thread().join();:等待其他线程终止,在当前线程中调用另一个线程的join方法,则当前线程进入阻塞状态,直到另一个线程完成菜进入就绪状态
new Thread().notify();:唤醒在等待的线程中的某一个线程,随机唤醒其中一个;notifyAll()唤醒所有线程
Thread.interrupted();:终止线程,在线程阻塞的时候(join,sleep)执行会导致InterruptedException异常
/**
* Created by zelei.fan on 2017/7/23.
*/
public class ThreadTest extends Thread {
private String name;
ThreadTest(String name){
this.name = name;
}
ThreadTest(){}
private int index = 5;
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"线程开始运行");
while (index > 0) {
System.out.println(Thread.currentThread().getName()+name + "运行 : " + index);
index --;
}
System.out.println(Thread.currentThread().getName()+"线程运行结束");
}
public static void main(String[] args) {
/*join用法
* 作用:当主线程需要子线程的执行结果时,主线程必须等待子线程执行结束才能继续执行
* */
System.out.println("主线程运行开始");
ThreadTest a = new ThreadTest("A");
a.start();
System.out.println("主线程运行结束");
}
}
主线程运行开始
主线程运行结束
Thread-0线程开始运行
Thread-0A运行 : 5
Thread-0A运行 : 4
Thread-0A运行 : 3
Thread-0A运行 : 2
Thread-0A运行 : 1
Thread-0线程运行结束
/**
* Created by zelei.fan on 2017/7/23.
*/
public class ThreadTest extends Thread {
private String name;
ThreadTest(String name){
this.name = name;
}
ThreadTest(){}
private int index = 5;
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"线程开始运行");
while (index > 0) {
System.out.println(Thread.currentThread().getName()+name + "运行 : " + index);
index --;
}
System.out.println(Thread.currentThread().getName()+"线程运行结束");
}
public static void main(String[] args) {
/*join用法
* 作用:当主线程需要子线程的执行结果时,主线程必须等待子线程执行结束才能继续执行
* */
System.out.println("主线程运行开始");
ThreadTest a = new ThreadTest("A");
a.start();
try {
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程运行结束");
}
}
主线程运行开始
Thread-0线程开始运行
Thread-0A运行 : 5
Thread-0A运行 : 4
Thread-0A运行 : 3
Thread-0A运行 : 2
Thread-0A运行 : 1
Thread-0线程运行结束
主线程运行结束
如下:
/**
* Created by zelei.fan on 2017/7/23.
*/
public class ThreadTest extends Thread {
private String name;
ThreadTest(String name){
this.name = name;
}
ThreadTest(){}
private int index = 5;
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"线程开始运行");
while (index > 0) {
System.out.println(Thread.currentThread().getName()+name + "运行 : " + index);
index --;
}
System.out.println(Thread.currentThread().getName()+"线程运行结束");
}
public static void main(String[] args) {
ThreadTest a = new ThreadTest("A");
ThreadTest b = new ThreadTest("B");
a.start();
b.start();
}
}
Thread-0线程开始运行
Thread-1线程开始运行
Thread-1B运行 : 5
Thread-1B运行 : 4
Thread-0A运行 : 5
Thread-0A运行 : 4
Thread-0A运行 : 3
Thread-0A运行 : 2
Thread-0A运行 : 1
Thread-0线程运行结束
Thread-1B运行 : 3
Thread-1B运行 : 2
Thread-1B运行 : 1
Thread-1线程运行结束
/**
* Created by zelei.fan on 2017/7/23.
*/
public class ThreadTest extends Thread {
private String name;
ThreadTest(String name){
this.name = name;
}
ThreadTest(){}
private int index = 5;
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"线程开始运行");
while (index > 0) {
if (3 == index){
Thread.yield();
System.out.println(Thread.currentThread().getName()+name + "线程让步");
index --;
}else {
System.out.println(Thread.currentThread().getName()+name + "运行 : " + index);
index --;
}
}
System.out.println(Thread.currentThread().getName()+"线程运行结束");
}
public static void main(String[] args) {
ThreadTest a = new ThreadTest("A");
ThreadTest b = new ThreadTest("B");
a.start();
b.start();
}
}
Thread-0线程开始运行
Thread-1线程开始运行
Thread-0A运行 : 5
Thread-1B运行 : 5
Thread-0A运行 : 4
Thread-0A线程让步
Thread-1B运行 : 4
Thread-0A运行 : 2
Thread-0A运行 : 1
Thread-1B线程让步
Thread-0线程运行结束
Thread-1B运行 : 2
Thread-1B运行 : 1
Thread-1线程运行结束
从上面的打印结果可以看出:当a让步后,b获得资源,然后执行;但是这只是一种情况,也有可能还是a继续执行,他们之间是公平竞争,谁抢到谁执行;
下面通过实例来讲解下,需求是我要打印出ABABAB这种结果,不能乱:
class SortThread implements Runnable{
private int index = 10;
private String name;
private Object prev;
private Object self;
SortThread(String name, Object prev, Object self){
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
while (index > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
self.notify();
index--;
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Object a = new Object();
Object b = new Object();
SortThread sortThread = new SortThread("A", a, b);
SortThread sortThread1 = new SortThread("B", b, a);
new Thread(sortThread).start();
Thread.sleep(100);
new Thread(sortThread1).start();
}
先从main方法中的两个对象说起,a和b其实分别是下面两个线程的锁,当然这边涉及到锁的知识后面再讲,这两个线程都持有自身的锁和对方的锁;Thread.sleep(100);主要是让A能先执行;
然后我们再看run方法中做了什么,第一步获取对方的锁和自身的锁,当获取到自身锁时,就算B是可执行状态也进不来,以为第一层B要执行就得先获得A的锁,而A的锁已经被A在使用,只有等待A释放;self.notify();唤醒其它线程,此时在门外等待的B线程被唤醒;执行完成后首先是释放自身的锁;当A释放自身锁的时候,B就能获取到锁进入第一层,然而B的自身锁还没有被A释放;当A的自身锁释放后继续执行到prev.wait();,此处执行就会立马释放B的锁,只要一释放B会立马抢占到自身锁,然后执行方法,而此刻A线程则被关在门外;就是这样循环下去;