进程:当一个程序进入内存运行,即变成一个进程。
(单核计算机在同一个时间点上,游戏进程和音乐进程是同时在运行吗?不是,因为计算机的CPU只能在某个时间点上做一件事。由于计算机将在游戏进程和音乐进程之间频繁的执行,切换速度极高,人类感觉游戏和音乐在同时进行。(一个大脑)多进程并发其实是2核和4核的计算机,(2个和4个大脑)
多进程的作用:不是提高执行速度,而是提高CPU的使用类
多线程不是为了提高执行速度,而是提高程序的使用率。可以给现实世界中的人类一种错觉,感觉多个线程在同时并发执行。
线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈。(重要)
进程和进程之间的内存是独立的。
线程:线程是进程中的一个执行单元。负责当前进程中程序的执行。简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
多线程:即一个程序中有多个线程在同时执行。
举例:单线程程序:即去网吧,一个人下机下一人上机。多线程程序:网吧能够让多个人同时上网。
java程序的运行原理?
java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,表示启动了一个进程,该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法。所以main方法运行在主线程中。
程序运行原理:
分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度。(正确理解为优先级高的线程获取的CPU时间片多一点)
抢占式调度详解:QQ,迅雷同时在运行,感觉这些软件好像在同一时刻运行着。实际上,CPU(中央处理器)使用抢占式模式在多个线程间进行着高速的切换,对于CPU的一个核而言,某个时刻,只能执行一个线程,而CPU在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率。让CPU的使用率更高。
在java语言中实现多线程的第一种方式:
第一步:继承java.lang.Thread;
第二步:重写run方法.
三个知识点:
如何定义线程?
如何创建线程?
如何启动线程?
*/
public class ThreadTest02
{
public static void main(String[] args){
//创建线程
Thread t = new Processor();
//启动
t.start(); //这段代码执行瞬间结束。告诉JVM再分配一个新的栈给t线程.
//run不需要程序员手动调用,系统线程启动之后自动调用run方法.
//t.run(); //这是普通方法调用,这样做程序只有一个线程,run方法结束之后,下面程序才能继续执行。
//这段代码在主线程中运行.
for(int i=0;i<10;i++){
System.out.println("main-->" + i);
}
//有了多线程之后,main方法结束只是主线程栈中没有方法栈帧了。
//但是其他线程或者其他栈中还有栈帧。
//main方法结束,程序可能还在运行。
}
}
//定义一个线程
class Processor extends Thread
{
//重写run方法
public void run(){
for(int i=0;i<30;i++){
System.out.println("run-->" + i);
}
}
}
java中实现线程的第二种方式:
第一步:写一个类实现java.lang.Runnable;接口
第二步:实现run方法.
public class ThreadTest03
{
public static void main(String[] args){
//创建线程
Thread(Runnable target)构造方法
Thread t =new Thread(Runable);创建线程其实底层是这个方法
Thread t = new Thread(new Processor());
//启动
t.start();
}
}
//这种方式是推荐的。因为一个类实现接口之外保留了类的继承。
class Processor implements Runnable
{
public void run(){
for(int i=0;i<10;i++){
System.out.println("run-->"+i);
}
}
}
线程的生命周期?
线程新建之后调用start方法进入就绪状态,就绪状态的线程表示有权力去获取CPU的时间片,拿到CPU的时间片进入运行状态,运行执行run方法,时间片用完后再回去到就绪状态,等待拿到CPU时间片,拿到CPU时间片再去运行run方法,反复如此,直到run方法执行完毕,这个线程就消亡。运行时候会出现线程阻塞,线程阻塞后等待线程解除阻塞,解除阻塞后,进入就绪状态
CPU时间片:是执行权,当线程拿到CPU时间片之后就马上执行run方法,这个时候就带白哦进入了运行状态。
新建:采用new语句创建完成
就绪:执行start后
运行:占用CPU时间
阻塞:执行了wait语句,执行了sleep语句和等待某个对象锁,等待输入的场合
终止:退出run()方法
2.线程的调度与控制
通常我们的计算机只有一个CPU,CPU在某一个时刻职能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行命令,在单CPU的机器上线程不是并行运行的,只有在多个CPU上线程才可以并行运行,java虚拟机要负责线程的调度,取得CPU的使用权,目前有两种调度模型:分时调度模型和抢占式调度模型,java使用抢占式的调度模型。
分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用cpu的时间片
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些。
2.1.线程的优先级
线程的优先级主要分三种:max_priority(最高级10); min_priority(最低级1) norm_priority(标准5)默认(优先级一般是1--10)
1.获取当前线程对象Thread.currentThread();
2.给线程起名 t.setName("t1");
3.获取线程的名字 t.getName();
线程优先级高的获取的CPU时间片相对多一些。
优先级:1-10。//设置优先级 t1.setPriority(5);
System.out.println(Thread.NORM_PRIORITY); //5
1.Thread.sleep(毫秒);
2.sleep方法是一个静态方法.
3.该方法的作用:阻塞当前线程.腾出CPU,让给其他线程。
public class ThreadTest06
{
public static void main(String[] args) throws InterruptedException{
//Thread.currentThread(); //t保存的内存地址指向的线程是“主线程对象”
Thread t1 = new Processor();
t1.setName("t1");
t1.start();
//阻塞主线程
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
Thread.sleep(500);
}
}
}
class Processor extends Thread
{
//Thread中的run方法不抛出异常,所以重写run方法之后,在run方法的声明位置上不能使用throws
//所以run方法中的异常只能try...catch...
public void run(){
for(int i=0;i<10;i++){
//Thread.currentThread(); //t保存的内存地址指向的线程是“t1线程对象”
System.out.println(Thread.currentThread().getName()+"--->"+i);
try{
Thread.sleep(1000); //让当前线程阻塞1S。
}catch(InterruptedException e){
e.printStackTrace();
}
}
// m1();
}
//m1方法是可以使用throws的.
public void m1() throws Exception{
}
面试题:考点其实是对象也能调用静态方法()
//静态的方法按照正规的方式访问:“类名.” //静态的方法也能用“引用.”访问 st.m2(); //编译阶段检查出st是StaticTest03类型,编译通过,运行的时候,仍然使用 "StaticTest03."的方式访问。//该方法执行不需要对象。//空的引用去访问成员的时候会出现空指针异常。//m2方法不是“成员”而是静态的。//所以即使引用是空的,也不会报空指针异常。(其实也就是说,m2 方法执行底层是 不需要这个对象的。)
public class ThreadTest07{
public static void main(String[] args) throws Exception{
//创建线程
Thread t = new Processor();
t.setName("t");
//启动线程
t.start();
//休眠
t.sleep(5000); //等同于Thread.sleep(5000); 阻塞的还是当前线程,和t线程无关。
System.out.println("HelloWorld!");
A a = null;
a.m1(); //不会出现空指针异常。}
}
class Processor extends Thread
{
public void run(){
for(int i=0;i<200;i++){
System.out.println(Thread.currentThread().getName()+"------->"+i);
} } }
class A{
public static void m1(){} }
yield 让位,它与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
某线程正在休眠,如果打断它的休眠.
以下方式依靠的是异常处理机制。//打断t的休眠.t.interrupt();
3.线程同步锁机制
t1和t2
异步编程模型:t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁。
同步编程模型:t1线程和t2线程执行,当t1线程必须等t2线程执行结束之后,t1线程才能执行,这是同步编程模型。
什么时候要同步呢?为什么要引入线程同步呢?
1.为了数据的安全。尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制。
线程同步机制使程序变成了(等同)单线程。
2.什么条件下要使用线程同步?
第一:必须是多线程环境
第二:多线程环境共享同一个数据.
第三:共享的数据涉及到修改操作。
以下程序演示取款例子。以下程序不使用线程同步机制,多线程
同时对同一个账户进行取款操作,会出现什么问题?
使用线程同步机制保证数据的安全。
public class ThreadTest12
{
public static void main(String[] args){
//创建一个公共的账户
Account act = new Account("actno-001",5000.0);
//创建线程对同一个账户取款
Thread t1 = new Thread(new Processor(act));
Thread t2 = new Thread(new Processor(act));
t1.start();
t2.start();
}}
//取款线程
class Processor implements Runnable
{
//账户
Account act; //成员变量账户
//Constructor提供构造方法
Processor(Account act){
this.act = act;
}
public void run(){
act.withdraw(1000.0);
System.out.println("取款1000.0成功,余额:" + act.getBalance());
}
}
//账户
class Account
{
private String actno; // 账户
private double balance; 余额
public Account(){}
public Account(String actno,double balance){
this.actno = actno;
this.balance = balance;
}
//setter and getter
public void setActno(String actno){
this.actno = actno;
}
public void setBalance(double balance){
this.balance = balance;
}
public String getActno(){
return actno;
}
public double getBalance(){
return balance;
}
//对外提供一个取款的方法
public void withdraw(double money){ //对当前账户进行取款操作
double after = balance - money;
//延迟
try{Thread.sleep(1000);}catch(Exception e){}
//更新
this.setBalance(after);
}
}
//对外提供一个取款的方法
public void withdraw(double money){ //对当前账户进行取款操作
原理:t1线程和t2线程.
t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
如果找到this对象锁,则进入同步语句块中执行程序。当同步语句块中的代码
执行结束之后,t1线程归还this的对象锁。
在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到
synchronized关键字,所以也去找this的对象锁,但是该对象锁被t1线程持有,
只能在这等待this对象的归还。
//把需要同步的代码,放到同步语句块中. this就是共享对象this就是账户
synchronized(this){
double after = balance - money;
//延迟
try{Thread.sleep(1000);}catch(Exception e){}
//更新
this.setBalance(after);
}}}
总结:线程执行代码,遇到了synchronized关键字,就会去这个对象的对象锁,找到则执行,找不到则等待。再看它是不是共享同一个数据
//synchronized关键字添加到成员方法上,线程拿走的也是this的对象锁。
public synchronized void withdraw(double money){ //对当前账户进行取款操作
double after = balance - money;
//延迟
try{Thread.sleep(1000);}catch(Exception e){}
//更新
this.setBalance(after);
}
}
这样的使用方式也可以,比第一种范围大了。根据需求使用