多线程
1,什么是进程?什么是线程?
进程是一个应用程序(1个进程是一个软件),线程是一个进程中的执行场景/执行单元,一个进程可以有多个线程
2.对于java程序来说,当在DOS命令窗口中输入java Helloworld回车之后,先会启动jvm,而jvm就是一个进程,jvm再启动一个主线程,调用main方法,同时再启动一个垃圾回收线程负责看护回收垃圾。最起码现在的java程序中至少有两个线程并发。一个是垃圾回收线程,一个是执行main方法的主线程。
3.线程和进程是什么关系?举例:
阿里巴巴: 进程
马云: 阿里巴巴的一个线程
童文红:阿里巴巴的一个线程
京东 : 进程
强东:京东的一个线程
妹妹:京东的一个线程
进程可以看做现实生活中的公司,线程可以看作是公司当中的某个员工
注意:进程A和进程B的内存独立不共享(阿里和京东资源不会共享)
线程A和线程B呢?
在java语言中:线程A和线程B,堆内存和方法去内存共享 但是栈内存独立,一个线程一个栈。
假设10个线程,会有10个栈空间,每个栈和每个栈之间互不干扰,各自执行各自的,这就是多线程并发
举例:火车站可以看做是一个进程,火车站中的每一个售票窗口可以看作是一个线程,我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。java中之所以有多个线程机制,目的就是为了提高程序的处理效率。
4,思考一个问题
使用了多线程机制之后,main方法结束了,是不是有可能程序也不会结束,main方法结束了只是主线程结束了,主栈空了,其它的栈(线程)可能还在弹栈压栈
5,分析一个问题?
对于单核CPU来说,真的可以做到真正的多线程并发吗?对于多核的CPU电脑来说,真正的多线程并发是没有问题的,4核的CPU表示同一时间点上,可以真的有4个进程并发执行。
什么是真正的多线程并发?
t1线程执行t1的 t2线程执行t2的, t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。
单核的CPU表示只有一个大脑: 不能做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快。多个线程之间频繁切换执行,给人的感觉是:多个事情同时再做。
线程A:播放音乐 线程B:运行英雄联盟
线程A和线程B频繁切换执行,人类会感觉音乐一直播放,游戏一直在运行,给我们的感觉时同时并发的。
例如: 电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度后,人类的眼睛产生错觉,感觉是动画的。这说明人类的反应速度很慢,就像是一根钢针扎到手上,到最终感觉到疼,这个过程时需要“很长的时间”的,在这个期间计算机可以进行亿万次的循环,所以计算机的执行速度很快。
6,分析这个程序有几个线程,除垃圾回收线程之外,有几个线程? 一个线程 因为只有一个栈
public class ThreadTest { public static void main(String[] args) { System.out.println("main begin"); m1(); System.out.println("main over"); } private static void m1(){ System.out.println("m1 begin"); m2(); System.out.println("m1 over"); } private static void m2(){ System.out.println("m2 begin"); m3(); System.out.println("m2 over"); } private static void m3(){ System.out.println("m3 begin"); } // main begin //// m1 begin //// m2 begin //// m3 begin //// m2 over //// m1 over //// main over 进行弹栈压栈 }
7,java语言中,实现线程的方式有两种
java支持多线程机制,并且java已经将多线程实现,我们只有需要继承就可以了
第一种:编写一个类 ,直接继承java.long.Thread. 重写run()方法
怎么创建线程对象? new对象 就行 怎么启动线程呢? 调用线程对象的start()方法
public class ThreadTest01 { public static void main(String[] args) { MyThread myThread = new MyThread(); //新建一个分支线程对象 myThread.start(); //不去调用myThread.start()方法,不会去启动分支线程,申请一个新的线程来执行run()方法 for (int i = 0; i < 100; i++) { System.out.println("主线程-->" + i); } } } class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("线程分支" + i); } } }
start()方法的作用是,启动一个分支线程,在jvm开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。这段代码的任务只是为了开启一个新的栈空间,只要新的栈空开辟出来,start()方法就结束了。线程就会启动成功了。
启动成功的线程会自动调用run()方法,并且run()方法在分支的栈底部(压栈)。run()方法在分支栈的栈底部,main()方法在主栈的栈底部 ,run()和main()是平级的。
第二种:编写一个类,实现java.lang.Runable接口,实现ruan()方法。
注意:第二种比较常用,因为一个类实现接口,它可以继承其它的类,更灵活。
public class ThreadTest02 { public static void main(String[] args) { MyThread01 myThread01 = new MyThread01(); Thread t= new Thread(myThread01); t.start(); for (int i = 0; i < 1000; i++) { System.out.println("主线程--->" + i); } } } class MyThread01 implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("分支线程" + i); } } }
第二种采用匿名内部类的方式
public class ThreadTest03 { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("分支线程" + i); } } }); t.start();//启动线程 for (int i = 0; i <1000; i++) { System.out.println("main主线程" + i); } } }
8,线程生命周期
=
9,线程的一些常用方法
1,怎么获取当前线程对象?
Thread t = Thread.currentThread() 返回值t就是当前线程
2,获取线程对象的名字?
String name = 线程对象.getName();
3,修改线程对象的名字
线程对象.setName("线程名字");
4,当线程没有设置名字的时候,默认的名字有什么规律
Thread ----------------0;
Thread-----------------1;
5,定时器 : 关于sleep()方法
static void sleep(long millis)
1)静态方法 :Thread.sleep(10000);
2) 参数是毫秒
3)作用:让当前线程进入休眠,进入”阻塞状态“,放弃占有的CPU时间片,让给其它线程使用。
进入代码出现在A线程中,A线程进入睡眠
进入代码出现在B线程中,B线程进入睡眠
4)T和read.sleep() 方法,可以做到这种效果
间隔特定时间,去执行一段特定的代码,每隔多久执行一次。
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
Thread.sleep(1000);
}
}
}
如果 Thread t = new MyThread() //多态 t.sleep(1000*5)
在执行的时候,还是会转换成:Thread.sleep(10000*5); 这行代码的作用是:当前线程进入休眠,也就是main线程进入休眠,出现在main方法中,main方法休眠,与MyThread03类无关
5)sleep睡眠太久了,如果希望在半道上醒来,应该怎么办?也就是说怎么叫醒一个正在睡眠的线程?
注意:这个不是线程的执行,是终止线程的睡眠
t.interrupt()这个方法终止睡眠。
强行终止线程: t.stop() 这个方法过时,已经不在使用。缺点:容易丢失数据,直接将线程杀死。
//重点:run()方法当中的异常不能throws,只能try-catch 因为run()方法在父类中没public class ThreadDemo05 {
ThreadDemo05
public static void main(String[] args) throws InterruptedException { MyRunnable1 r = new MyRunnable1(); Thread t = new Thread(); t.setName("t"); t.start(); r.run(); t.sleep(3000); //模拟5秒 r.run = false; } } class MyRunnable1 implements Runnable{ boolean run = true; //打一个标记 @Override public void run() { for (int i = 0; i < 20; i++) { if (run){ System.out.println(Thread.currentThread().getName() + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }else { return; //如果false终止 } } }
6,静态方法:
static void yield(); 让位方法 暂停当前正在执行的线程对象,并执行其它线程。
yiele() 方法不是阻塞方法,让当前线程让位,让给其它线程使用。
yiele()方法的执行会让当前线程从”运行状态“回到”就绪状态“,
注意:在回到就绪状态之后,有可能还会再次抢到
public class Thread01 extends Thread { public static void main(String[] args) { Thread t = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { if (i%100 == 0){ Thread.yield(); } System.out.println(Thread.currentThread().getName() + i); } } }); t.setName("t"); t.start(); for (int i = 0; i < 1000; i++) { System.out.println(Thread.currentThread().getName() + i); } } }
7,实例方法
void join() 合并线程
public class Thread02 { public static void main(String[] args) throws InterruptedException { MyThread05 t = new MyThread05(); t.start(); t.run(); t.join(); //当线程进入阻塞t线程执行,知道t线程结束,当前线程继续执行 for (int i = 0; i < 10; i++) { System.out.println("main线程" + i); } } } class MyThread05 extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("分支线程" + i); } } }
10,线程调度的概念(了解)
1,常见的线程调度模型有哪些?
抢占式调度模型:哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些,java采用的就是抢占式调度模型。
均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片长度一样,平均分配,一切平等。有一些编程语言,线程调度模型采用的是这种方式
2,JAVA中提供了哪些方法式和线程调度有关系的呢?
实例方法:
Void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程优先级
最低优先级1,默认优先级式5,最高优先级式10,优先级比较高的获取CPU时间片可能会多一些。(但也不会是,大概率是多一些)。
11,线程不安全
1,什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件: 1) 多线程并发 2)有共享数据 3)共享数据有修改的行为
满足以上3个条件之后,就会存在线程安全问题
2,怎么解决线程安全问题呢?
线程排队执行 (不能并发),用排队执行解决线程安全问题,这种机制被称为:线程同步机制
线程同步就是线程排队,线程排队了就会牺牲一部分效率。没办法,数据安全第一位,只有数据安全了,我们才可以谈效率
3,线程同步涉及两个专业术语
1)异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2也不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是多线程并发(效率较高);异步就是并发。
2)同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束。或者说在t2线程执行的时候,必须等待t1线程执行结束。两个线程之间发生等待关系,这就是同步编程模型。
效率比较低,线程排队执行,同步就是排队。
public class Account { private String account; //账号 private double balance; //余额 public Account() { } public Account(String account, double balance) { this.account = account; this.balance = balance; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } public void withDraw(double money){ //取款方法 synchronized (this){ double before = this.getBalance(); //取款之前的余额 double after = before - money; //取款之后的余额 this.setBalance(after); // 跟新余额 } } }
public class AccountThread extends Thread { private Account act; //两线程必须同一个共享对象 public AccountThread(Account act){ //通过构造方法传递过来账户对象 this.act = act; } @Override public void run() { double money = 5000; //假设取款5000 act.withDraw(money); //取款 System.out.println("账户" + act.getAccount() + "取款成功,余额" + act.getBalance()); } } class Test01{ public static void main(String[] args) { Account act = new Account("act-001", 1000);//创建账户对象 AccountThread t1 = new AccountThread(act); AccountThread t2 = new AccountThread(act); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } }
12,synchronized有三种写法:
1),同步代码块 灵活
synchronized(线程共享对象){
同步代码块;
}
2)在实例方法上使用synchronized,表示共享对象一定是this,并且同步代码块式整个方法体。
3),在静态方法上使用synchronized ,表示找类锁,类锁永远只有1把,就算创建100个对象,那类锁也只有一把
对象锁:1个对象一把锁,100个对象100把锁
类锁 : 100个对象,也可能只有1把类锁。
聊一聊,我们以后开发中应该怎么解决线程安全问题?
是一上来就选择线程同步吗?synchronized 不是,synchronized会让程序的执行效率低,用户体验不好。系统的用户吞吐量降低,用户体验差。在不得已的情况下再选择线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共存了。(一个线程对应一个对象,100个线程对应100个对象,对象不共存就没有安全问题了)。
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
13,守护线程
1)java语言中线程分为两大类:
一类:用户线程
一类:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程);
守护线程的特点:一般守护线程是一个死循环,所有的用户线程结束,守护线程自动结束。
注意:主线程main是一个用户线程
守护线程用在什么地方呢?
每天00:00的时候系统数据自动备份。这个需要使用到定时器,并且我们可以将定时器设置为守护线程,一直在那里看着,每到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。
t.setDaemon(true); 启动线程之前将线程设置为守护线程。
2),定时器:
作用:间隔特定的时间,执行特定的程序
在实际开发中,每隔多久执行一段特殊的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现;
可以使用sleep方法,设置睡眠时间,每到这个时间点醒来执行任务,这种方式是最原始的定时器(比较low).
在java的类库中已经写好了一个定时器:java.util.Timer. 可以直接拿来用,不过这种方式在目前的开发中很少用,因为现在有很多高级的框架都是支持定时人物的。
在实际开发中,目前使用较多的是spring 框架中提供的springTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
tirmer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerTest { public static void main(String[] args) throws ParseException { Timer timer = new Timer(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:ss:mm"); Date firstTime = sdf.parse("2021-12-16 15:55:50"); timer.schedule(new LongTimerTask(),firstTime,1000*2); /** * new LongTimerTask(), 定时任务 * firstTime, 第一次执行之间 * 1000*10 间隔多久执行一次 */ } } class LongTimerTask extends TimerTask{ @Override public void run() { //编写你要执行的任务 System.out.println("备份文件"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:ss:mm"); String strTime = sdf.format(new Date()); System.out.println(strTime + "完成一次备份"); } }
3) , 实现线程的第二种方式:实现Callable接口
这种方式实现的线程可以获取线程的返回值,之前的两种方式无法获取返回值,因为run()方法返回void
task.get(); 获取返回值
优点:可以获取到线程的执行结果
缺点:效率低,在获取t线程的执行结果的时候,当前线程变阻
4),关于object类中的wait()和notify()方法(生产者和消费者模式);
第一 wait()和notify()方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是object类自带的。
wait()方法和notify()方法不是通过线程对象调用的。不是这样的:t.wait(); 也不是这样的:t.notify() 不对。
T线程在O对象上活动,T线程是当前线程对象。当调用o.wait()方法后,下线程进入无线等待。直到最终调用o.notify()方法,可以让正在o对象上等待的 线程唤醒。

浙公网安备 33010602011771号