1、多线程程序执行是乱序执行的,代码中要调用sleep()方法,来释放cpu资源,让其他线程进入
2、调用start()方法运行线程的时候并不会马上执行代码,使线程进入runable状态(就绪),什么时候运行是系统决定的,看哪一个线程先得到cpu资源
3、run()方法是多线程的一个约定,所有的多线程代码都放在run()方法里
4、实现Runnable接口比继承Thread类所具有的优势:
①:适合多个相同的程序代码的线程去处理同一个资源
②:可以避免java中的单继承的限制
③:增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
5、在java中,启动程序,最少产生两个线程,一个是main线程,一个是垃圾回收机制线程
6、线程状态:
例:1、【创建】下载某个app
2、【就绪】开通该app的网络、定位权限等
3、【运行】使用该app
4、【阻塞】点击'发布动态'按钮,此时又刷新了首页,发布动态的行为就暂阻塞,先刷新了首页,后发布了动态
5、【死亡】退出app
①创建状态(new):创建一个线程对象
②就绪状态(runable):调用start()方法,使线程进入就绪状态,等待得到cpu资源
③运行状态(running):线程得到cpu资源,开始运行
④阻塞状态(blocked):线程因为某种原因放弃cpu使用权,暂时停止运行,直到线程进入到就绪状态才有机会进入到运行状态
(1)等待阻塞:运行的线程执行wait()方法,jvm将线程放入等待池中
(2)同步阻塞:运行的线程在获取对象的同步锁时,该同步锁被其他线程占用,JVM会把该线程放入锁池中
(3)其他阻塞:运行的线程执行sleep()或者join()方法,或者发出I/O请求,jvm会把该线程设置成阻塞状态,当sleep()超时、join()等待线程时间终止或者超时,I/O请求处理完成,JVM会将该线程重新纳入就绪状态,等待运行
⑤死亡状态:线程运行结束或者其他异常退出run()方法,该线程结束生命周期
7.多线程常用方法:
(1)sleep(long millis):指定该线程执行秒数内的休眠(暂停执行)
(2)join():等待该线程终止,(例:主线程要等待子线程处理的结果,所以不能让主线程在子线程之前结束,子线程.join(),等待该子线程结束)
(3)yield():暂停当前正在执行的线程,让当前线程回到可运行状态,并执行其他线程。目的是相同优先级的线程之间能适当的轮流执行,(先检测当前是否有相同优先级的线程处于可运行状态,如有,把CPU的占有权给它,反之,继续运行原来的线程,所以,yield方法称为‘退让’,把运行机会让给同等优先级的其他线程)
① yield()和sleep(long millis)的区别:
Ⅰ sleep方法使线程进入阻塞状态,在规定的时间内不会运行,yield方法使线程进入可执行状态,有可能在进入到可执行状态后马上又被执行
Ⅱ sleep方法使线程睡眠一段时间,这个时间是可以控制的,yield方法使当前线程让出CPU资源,但让出的时间是不可设定的
Ⅲ sleep方法允许较低优先级的线程获取CPU资源,yield方法只允许同级的线程获取CPU资源
(4)setPriority():更改线程的优先级
(5)wait():强迫一个线程等待
8.继承Thread类,重写run方法:
public class Test extends Thread { private String name; public Test(String name) { this.name = name; } public void run() { for (int i = 0; i < 5; i++) { System.out.println(name + "運行:" + i); } } public static void main(String[] args) { Test thread = new Test("A"); //sart方法实际执行的是run方法,start方法只是让线程进入可执行状态,什么时候执行是由JVM确定的 Test thread2 = new Test("B"); thread2.start(); } } 输出结果: B運行:0 A運行:1 .................再运行一次程序,结果会不同 |
public class Test implements Runnable { private String name; public Test(String name) { this.name = name; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(name + "運行:" + i); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { new Thread(new Test("C")).start(); new Thread(new Test("D")).start(); } } 输出结果: C運行:0 |
10.synchronized 同步关键字
1)当两个并发线程(runableA、runableB)访问同一个对象(test)中的synchronized代码块时,在同一时刻只能有一个线程执行,另一个线程阻塞,等当前线程执行完毕后另一个线程才能执行该代码块。
public class Test implements Runnable { @Override public void run() { synchronized (this) { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName() + "運行:" + i); } try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Test test = new Test(); new Thread(test, "runableA").start(); new Thread(test, "runableB").start(); } } 运行结果【运行多次结果仍一致】: runableA運行:0runableA運行:1 runableA運行:2 runableB運行:0 runableB運行:1 runableB運行:2 |
2)将以上代码中的main方法代码修改为以下
当两个并发线程(runableA、runableB)访问Test的两个对象中的synchronized代码时,两个线程可以同时进行,因为synchronized只锁定对象,每个对象只有一个锁与之关联,以下代码创建了 两个Test类的对象,所以会有两把锁分别锁定Test类的两个对象,而这两把锁互不干扰。
public static void main(String[] args) { new Thread(new Test(), "runableA").start(); new Thread(new Test(), "runableB").start(); } 输出结果: runableB運行:0 再运行一次: runableA運行:0 |
4)synchronsized(object)修饰对象,即给该对象加了锁,当一个线程访问object对象时,其他试图访问object对象的线程将会阻塞,直到该线程访问object对象结束,谁拿到那个锁谁就有执行那块代码的权利。
5)synchronsized修饰一个方法:
public synchronsized void methodName(){} |
6)synchronsized修饰一个代码块:
public void methodName(){ synchronsized(this){ //todo } } |
注:synchronsized修饰方法和代码块的作用是等价的,都是锁定了整个方法的内容。
7)synchronsized 关键字不能继承
虽然synchronsized可以用来修饰方法,但它并不是定义方法的一部分,如果父类定义了synchronsized修饰的公共方法,子类覆盖了该方法,子类中的该方法是默认不同步的,可以通过显示的在子类继承的方法中添加synchronsized修饰,也可以通过在子类的方法中调用父类的同步方法,效果是等同的。
class Person(){ public synchronsized void method(){} } class Student extends Person(){ public synchronsized void method(){} } 或者 class Student extends Person(){ public void method(){ super.method(); } } |
8)接口方法不能用synchronsized修饰;构造方法不能使用synchronsized修饰,但是可以在构造方法中使用synchronsized修饰代码块。
9)synchronsized修饰静态方法,静态方法属于类而不属于对象,所以synchronsized修饰的静态方法的使用对象是这个类的所有对象。相当于该类的所有对象用了同一把锁(效果等同synchronized修饰类 即synchronsized(Person.class))。
注:实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
11.线程数据传递
1)多线程编写程序对线程处理的参数通常可通过构造函数来传入,这样能保证在线程在运行之前该参数就已经传入
public class Test extends Thread { private String name; public Test(String name) { this.name = name; } public void run() { System.out.println("hello:" + name); } public static void main(String[] args) { Thread thread = new Test("Thread1"); thread.start(); } } |
2)当有多个参数的时候,放在构造方法中会显得构造方法很冗余,可通过变量(字段)和set方法来传入
public class Test implements Runnable { private String name; private int age; private String school; public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setSchool(String school) { this.school = school; } public void run() { System.out.println("My name is:" + name + ",age:" + age + ",school:" + school); } public static void main(String[] args) { Test thread = new Test(); thread.setName("TOM"); thread.setAge(20); thread.setSchool("浙大"); new Thread(thread).start(); } } |