一、多线程的概述
多线程:就是指应用程序有多条执行路径。
进程:正在运行的应用程序。
线程:进程的执行单元,一条执行路径。
注意:线程不能脱离进程而单独存在也就是线程是依赖于进程存在
例:迅雷下载浏览器多个标签页
多线程:多条线程同时处理数据,可以提高效率,但是同时也会消耗CPU资源,所以项目中需仔细斟酌取舍有当
多线程类:Thread
注意:Java是不能操作CPU的,所以java提供了一个Thread类,但是底层开线程的东西肯定C或者C++写的,比如native
1:多线程的实现方式有两种:
方式1:继承Thread类。
A:定义一个类继承Thread类。
B:子类要重写Thread类的run()方法。
C:让线程启动并执行。
注意:调用start()方法,切记不是调用run方法这个方法,
其实做了两件事情,第一,让线程启动。第二,自动调用run()方法。
方式2:实现Runnable接口
A:创建一个类实现Runnable接口
B:重写run()方法
C:创建类的实例
D:把类的实例作为Thread的构造参数传递,创建Thread对象,让线程启动并执行
注意:既然有了继承Thread类的方式,为什么还要有实现Runnable接口的方式?
1):避免的单继承的局限性
2):实现接口的方式,只创建了一个资源对象,更好的实现了数据和操作的分离。
一般我们选择第二种方式。
这两种方式所用到的方法:
publicfinal String getName():获取线程对象的名称。默认情况下,名字的组成 Thread-编号(编号从0开始)
public final void setName(String name):设置线程名称。
publicstatic Thread currentThread():返回当前正在执行的线程对象引用
:2:多线程经典案例卖票案例:
方式一继承Thread:
- publicclass TicketThread extends Thread {
- private static int tickets = 200;
-
- @Override
- public void run() {
-
- while(true) {
- if (tickets > 0) {
- System.out.println(getName()+ "正在出售第" +(tickets--) + "张票");
- }
-
- }
- }
- }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
方式二实现Runnable:
- publicclass TicketRunnable implements Runnable {
-
- private int tickets = 200;
-
- @Override
- public void run() {
- while(true) {
- if (tickets > 0) {
- System.out.println(Thread.currentThread().getName()+ "正在出售第"
- +(tickets--) + "张票");
- }
- }
- }
-
- }
3:多线程同步机制:
1):这时候我如果把
- while(true) {
- if (tickets > 0) {
- System.out.println(getName()+ "正在出售第" +(tickets--) + "张票");
- }
-
- }
- 变成
- while(true) {
- try {
- Thread.sleep(200);
- }catch (InterruptedException e) {
- e.printStackTrace();
- }
- if (tickets > 0) {
- System.out.println(getName()+ "正在出售第" +(tickets--) + "张票");
- }
-
- }
目的是让线程拖延的时间更长一些,这样一个线程在执行这段代码的时候时间变长了,其他线程和这个线程执行同一段代码的
几率就大一些。
2):那么这个时候我们发现了一些问题,发现有卖出相同的票的情况,有卖出负票的情况,为什么呢?请看如下解释:
-
-
- if(tickets > 0) {
-
-
-
-
-
- try {
-
-
-
-
- Thread.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
-
-
-
- System.out.println(Thread.currentThread().getName()+ "正在出售第"
- + (tickets--) + "张票");
-
-
-
-
-
-
- }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
3):如何解决呢?用synchronized (obj){}给代码加锁
如何确定多线程存在不同步问题(多线程出问题的判断条件)
A:看有没有共享数据
B:看对共享数据的操作是不是多条语句
C:看是不是在多线程程序中
找到后,就把同时满足这三个条件的代码给锁起来。
- synchronized(obj) {
-
-
-
- if (tickets > 0) {
- try{
-
- Thread.sleep(100);
- }catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+ "正在出售第"
- +(tickets--) + "张票");
- }
- }
4::问题又来了?请问synchronized (obj){}小括号里面的对象传什么好?
1)这个时候我们又学了一个synchronized关键字,可以加在方法上的关键字,作用是给方法整体锁起来,
可是这个方法上的所对象是什么呢?
privatesynchronized void check() {}
原来是this(当前对象)
所以说为了和方法上的关键字synchronized的锁是用同一个对象,我们以后都在synchronized (obj){}
小括号里面写this
2)静态方法的锁对象是谁呢?
是当前类的字节码文件对象。
类名.class - Class类型的对象
比如:synchronized (TicketRunnable.class) {}
3)以后我们用同步代码块还是同步方法呢?
因为使用同步机制的话,计算机会重新开辟新的空间来管理同步机制,影响效率
所以被同步的内容越少越好,所以,一般使用同步代码块。
如果一个方法内部全部都被同步了,那么,可以考虑使用同步方法。
5:那代码被锁住了,会不会出现代码块一直被锁住的情况(死锁问题,面试的时候我觉得考的也不算太多,
但是大家能理解的还是理解吧,万一考到呢)
- publicvoid run() {
- if (flag) {
- synchronized(MyLock.objA) {
- System.out.println("true --objA");
- synchronized (MyLock.objB) {
- System.out.println("true-- objB");
- }
- }
- } else {
- synchronized(MyLock.objB) {
- System.out.println("false --objB");
- synchronized (MyLock.objA) {
- System.out.println("false-- objA");
- }
- }
- }
- }
————————————————————
二、线程间的通信问题:
当不同种类的线程同时操作共享资源的时候就会出现线程间的通信问题
比如:火车票,当有多辆火车(多个线程)空出来的时候,票就多了,当多个窗口(多个线程)卖出去的时候
火车票就少了,肯定要在有火车票的时候才能卖票,如果联系起来现今流行的p2p信贷系统,更要控制好p2p里面
资金的数据安全所以线程间的通讯问题必须要有个安全机制才行,才能保证共享数据不会出错
接着我们用了Student案例来模拟线程间的通讯问题
学生是资源,我们就可以对学生的属性进行赋值,也可以获取学生的属性值使用,这就模拟了进出线程同时控制共享资源
①当我们写出了学生案例没有加入任何同步措施的时候发现 我们有可能打印出"李雷 26,韩梅梅29"的现象,
李雷明明是29, 韩梅梅明明是26 这是为什么出现这个现象呢? 请看如下解释
t1线程:
- public class SetStudent implementsRunnable {
- privateStudent s;
- publicSetStudent(Student s) {
- this.s = s;
- }
-
- @Override
- publicvoid run() {
-
- int x = 0;
- while (true) {
- synchronized(s) {
- if (x % 2 == 0) {
- s.name= "韩梅梅";
-
-
- s.age= 26;
- } else {
- s.name= "李雷";
-
- s.age= 29;
- }
- }
- x++;
- }
- }
-
- }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
t2线程:
- public class GetStudent implementsRunnable {
- privateStudent s;
-
- publicGetStudent(Student s) {
- this.s = s;
- }
-
- @Override
- publicvoid run() {
- while (true) {
- System.out.println(s.name+ "***" + s.age);
- }
- }
-
- }
②我们考虑到了用synchronized(Object obj){}机制来解决,所以加了一个相同的锁对象,而且必须是同一个锁对象
这样以来就解决了第①步出现的问题
t1线程run方法:
- public void run() {
-
- intx = 0;
- while(true) {
- synchronized (s) {
- if(x % 2 == 0) {
- s.name = "林青霞";
- s.age = 26;
- }else {
- s.name = "刘意";
- s.age = 29;
- }
- }
- x++;
- }
- public void run() {
- while(true) {
- synchronized (s) {
- System.out.println(s.name+ "***" + s.age);
- }
- }
- }
-
③:但是问题又出现了,我们如果按照上面的代码,有可能t1还没有设置数据,t2就打印出来了null和0
而我们想用Student来模拟线程间通讯问题,其实是想,Student设置一次 就输出一次,设置一次输出一次,
Student还没有设置,怎么输出?这才是我们要模拟的线程键通讯问题,所以我们给Student加了个boolean
判断Student是否已经设置好姓名和年龄。
针对输出:
判断是否有数据,如果有就输出。否则,就等待设置数据。
针对设置:
判断是否有数据,如果有就等待输出数据,否则,就输出。
根据这一点我们就采用了等待唤醒机制 wait() notify()
t1线程run方法:
- public void run() {
- while(true) {
- synchronized (s) {
- if(!s.flag){
- try {
- s.wait();
-
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- System.out.println(s.name+ "***" + s.age);
-
-
- s.flag= false;
- s.notify();
- }
- }
- }
-
t2线程run方法:
- public void run() {
- while(true) {
- synchronized (s) {
- if(!s.flag){
- try {
- s.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- System.out.println(s.name+ "***" + s.age);
-
-
- s.flag= false;
- s.notify();
- }
- }
- }
注意:1)wait()和notify()方法是Object的,必须由锁对象调用
2)wait()和notify() 必须写在同步代码块或者同步方法里面,
也正是因为写在里面 才能有锁对象,才能让锁对象调用这两个方法
3)wait()方法 让线程退出同步代码块等待 ,所以其他线程就可以进这个同步代码块了,(这也就是
好多人常说的释放锁对象,其实是退出到了同步代码块外面去了,所以其他线程可以进入了)
4)notify()方法 唤醒线程队列中的随机一个处于等待状态的的线程
5)wait()和sleep(Long time)的区别
wait():是Object类的方法,可以不用传递参数。释放锁对象。
用锁对象来调用,需要锁对象调用notify来唤醒
wait()必须写在同步代码块或者同步方法里面
sleep():是Thread类的静态方法,需要传递参数。不释放锁对象。
Thread直接调用sleep即可,自动睡眠一段时间,不需要唤醒,一段时间后自动睡醒
一般写在run方法里面,(也可以写到其他任何地方,因为每个程序都是最起码有一个线程)
④:能不能把这些同步代码或者等待唤醒的这些代码写到一个学生类里面呢?让学生类称为一个和StringBuffer和
Vector和Hashtable似的一个同步的类 安全的类 呢??这样的话其他的代码调用学生类的时候,
不用写同步代码块或者同步方法了,就和我们调用StringBuffer一样,还需要些同步代码块和同步方法吗?
代码修改如下:
同步的学生类:
- public class Student {
- privateString name;
- privateint age;
- privateboolean flag = false;
-
- publicsynchronized void set(String name, int age) {
-
- if (this.flag) {
- try{
- this.wait();
- }catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
-
- this.name = name;
- this.age = age;
-
-
- this.flag = true;
- this.notify();
- }
-
- publicsynchronized void get() {
-
- if (!this.flag) {
- try{
- this.wait();
- }catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
-
- System.out.println(this.name +"***" + this.age);
-
-
- this.flag = false;
- this.notify();
- }
- }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
t1线程代码:
- public class SetStudent implementsRunnable {
- privateStudent s;
-
- publicSetStudent(Student s) {
- this.s = s;
- }
-
- @Override
- publicvoid run() {
-
- int x = 0;
- while (true) {
- if(x % 2 == 0) {
- s.set("林青霞", 26);
- }else {
- s.set("刘意", 29);
- }
- x++;
- }
- }
- }
-
t2线程代码:
- public class GetStudent implementsRunnable {
- privateStudent s;
-
- publicGetStudent(Student s) {
- this.s = s;
- }
-
- @Override
- publicvoid run() {
- while (true) {
- s.get();
- }
- }
-
- }
三、线程的优先级:
试线程的优先级问题:
线程默认优先级是5。范围是1-10。
public final int getPriority():获取线程优先级
public final void setPriority(intnewPriority):更改线程的优先级。
注意:优先级可以在一定的程度上,让线程获较多的执行机会。
- Thread t1 = new Thread(pd);
- Thread t2 = new Thread(pd);
- Thread t3 = new Thread(pd);
-
- t1.setName("林平之");
- t2.setName("岳不群");
- t3.setName("东方不败");
-
- t3.setPriority(10);
- t1.setPriority(1);
- t2.setPriority(1);
-
- t1.start();
- t2.start();
- t3.start();<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
四、线程的暂停:
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
是为了让线程更和谐一些的运行,但是你不要依赖这个方法保证,如果要真正的实现数据依次输出,请使用等待唤醒机制。
测试类代码:
- PriorityDemo pd = new PriorityDemo();
- Thread t1 = new Thread(pd);
- Thread t2 = new Thread(pd);
- t1.setName("林平之");
- t2.setName("岳不群");
- t1.start();
- t2.start();
线程的run方法:
- public void run() {
- for(int x = 0; x < 100; x++) {
- System.out.println(Thread.currentThread().getName()+ "---" + x);
-
- Thread.yield();
- }
- }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
五、线程的加入:
public final void join():等待该线程终止。
一旦有join()线程,那么,当前线程必须等待,直到该线程结束。
- PriorityDemo pd = new PriorityDemo();
- Thread t1 = new Thread(pd);
- Thread t2 = new Thread(pd);
- Thread t3 = new Thread(pd);
- t1.setName("林平之");
- t2.setName("岳不群");
- t3.setName("东方不败");
-
- t2.start();
-
- try {
- t2.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- t1.start();
- t3.start();
-
注意:线程必须先启动才能join 否则不行
六、线程的守护:
- public final void setDaemon(boolean on):设置线程为守护线程,一旦前台(主线程),结束,守护线程就结束了。
- DaemonDemo dd = new DaemonDemo();
- Thread t1 = new Thread(dd);
- Thread t2 = new Thread(dd);
- t1.setDaemon(true);
- t2.setDaemon(true);
- t1.start();
- t2.start();
- for (int x = 0; x < 10; x++) {
- System.out.println(Thread.currentThread().getName()+ "---" + x);
- }
注意:main方法本身也是一个线程
守护线程举例:坦克大战 英雄联盟 很多游戏都有这样的规则