概念:
进程--是一个正在执行中的程序。
每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫做一个控制单元。
线程--就是进程中的一个独立的控制单元。
线程在控制着进程的执行。
Java虚拟机启动的时候会有一个进程java.exe, 该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,改线程称之为主线程。
扩展:其实更细节说明java Virtual Machine, JVM启动不止一个线程,还有负责垃圾回收机制的线程。
1,如何在自定义的代码中,自定义一个线程呢?
方法1:
继承Thread类
步骤:
1 定义类继承Thread
2 复写Thread类中的run方法。====》目的:将自定义的代码存储在run方法,让线程运行。
3 调用线程的start方法,该方法有两个作用:开始线程+调用run方法
eg:两个类一个运行主线程main,一个运行子线程:
public class Demoextends Thread {
@Override
public void run() {
super.run();
for (int i = 0; i < 60; i++)
System.out.println("demo run" + i);
}
}
public class ThreadDemo {
public static void main(String[] args) {
Demo d = new Demo();
d.start();
for (int i = 0; i < 60; i++)
System.out.println("Hello World!" + i);
}
}
发现运行结果每一次都不同,因为多个线程都在获取CPU的执行权,CPU执行到谁,谁就运行。
明确一点:在某一时刻只能有一个程序在运行,(多核除外)
CPU在做着快速的切换,以达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为看作是在互相抢夺CPU的执行权。
这就是多线程的一个特性:随机性,谁抢到谁执行,至于执行多长时间,CPU说了算。
为什么要创建run方法?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
也就是说Thread类中的run方法,用于存储线程要运行的代码。
如果不调用start(),即说明线程执行没有开始。。。。。。
多线程的运行状态:
线程拥有自己默认的名称。Thread-编号 该编号从0开始。
static Thread currentThread();获取当前线程对象。
getName();获取线程名称。
设置线程的名称,setName()或者构造函数。
如何让 几个线程共享一个变量?
将这个变量设为static
方法二: 实现Runnable接口
步骤:
1 定义类实现Runnable接口
2 覆盖Runnable接口中的run方法
将线程要运行的代码存放在该run方法中
3 通过Thread类简历线程对象
4 将Runnable 接口的子类对象作为实际参数传递给Thread类的构造函数
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以,要让线程去制定制定对象的run方法,就必须明确该run方法所属对象。
5 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
实现方式和继承方式有什么区别?
实现方式好处:避免了单继承的局限性,在定义线程时,建议使用实现方式。eg:Student类继承Person类,但是Student类中部分代码是要用到多线程,,,由于智能单继承,所以智能实现runnable().
两种方式区别:
继承Thread:线程代码存放Thread子类run方法中。不共享非静态变量
实现Runnable:存成代码存放在接口的子类的run方法中。 共享静态变量
多线程运行时,存在安全隐患,eg:只打印ticket>0的值,但是可能会打印出小于0的值。(当加入Thread.sleep(100)来模拟多线程的等待现象)。
问题原因: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
Java对于多线程的安全问题提供了专业的解决方式。=======同步代码块:
synchronized(对象){
需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行。没有锁的线程即使获得cpu的执行权,也进不去,因为没有获取锁。
火车上的卫生间-----经典例子:
每个人都想往里冲,但是,一次只能一个人进入,进入后将门锁上,之后被熏晕,即使此时坑没有被占,。。。
同步的前提:
1 必须要有两个或者两个以上的线程
2 必须是多个线程使用同一个锁
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,会消耗更多的资源。
举例:
银行有一个金库,有两个储户分别存300rmb,每次存100,存三次。
目的:该程序是否有安全问题,如果有,如何解决?
如何找问题:
1 明确哪些代码是多线程运行代码
2 明确共享数据
3 明确多线程运行代码中哪些语句是操作共享数据的。
两种同步方式:
1 同步代码块
Object obj = new Object();
public void run( ){
while(true){
synchronized(obj){
Try(Thread.sleep(10);) catch(Exception e){}
System.out.println(ticket--);
}
}
}
2 同步函数(将共享数据的代码抽离成一个函数)
public synchronized void show(){
Try(Thread.sleep(10);) catch(Exception e){}
System.out.println(ticket--);
}
同步函数用的是哪一个锁?
函数需要被对象调用,那么函数都有一个所需对象引用,就是this.
所以同步函数使用的锁是this.
如果想让同步代码块,和同步函数共享数据,且保证安全性?
同步代码块,synchronized(this){....}, 因为要想共享数据,得让他们的锁保持一致。
多线程-静态同步函数的锁是Class对象。
如果同步函数被静态修饰后,使用的锁是什么呢?
由于静态方法中不能定义this,所以静态同步函数的锁不是this.
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。====类名.class, 该对象的类型是Class
静态的同步方法,使用的锁是该方法所在类的字节码文件对象,即:类名.class
所以:
静态同步函数:
public static synchronized void show(){
if(ticket>0){
try{Thread.sleep(10);} catch(Exception e){}
System.out.println(Thread.currentThread.getName()+"....."+ticket--);
}
}
静态同步代码块:
synchronized(Ticket.class){
if(ticket>0){
try{Thread.sleep(10);} catch(Exception e){}
System.......
}
}
死锁:同步中嵌套同步,