1基本概念
1什么是进程什么是线程
进程:是程序执行一次的过程,他是动态的概念,是资源分配的基本单位。一个应用程序(1个进程是一个软件)。
线程:一个进程可以有多个线程,线程是cpu调度的单位,一个进程中的执行场景/执行单元。
对于java程序来说,当在DOS命令窗口中输入:
java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法(main方法就是主线程)。
同时再启动一个垃圾回收线程负责看护,回收垃圾。
2、进程和线程是什么关系?
进程:可以看做是现实生活当中的公司。
线程:可以看做是公司当中的某个员工。
进程和进程
进程跟进程资源不是共享的,内存独立不共享
线程和线程:
线程与线程的共享资源是“堆,方法区” 每个线程都有自己独立的“栈”
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。
3为什么要学习多线程
java中之所以有多线程机制,目的就是为了 提高程序的处理效率
。
4思考一个问题
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?
main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。
5分析一个问题对于单核的CPU来说,真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没问题的。4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。
对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给别人的感觉是:多个事情同时在做!!!
eg.
线程A:播放音乐
线程B:运行魔兽游戏
线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
给我们的感觉是同时并发的。(因为计算机的速度很快,我们人的眼睛很慢,所以才会感觉是多线程!)
6什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。t1不会影响t2,t2也不会影响t1
。这叫做真正的多线程并发。
7关于线程对象的生命周期?★★★★★
- 新建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
8线程构造方法
构造方法名 | 备注 |
---|---|
创建线程第一种方式 | |
Thread() | |
Thread(String name) | name为线程名字 |
创建线程第二种方式 | |
Thread(Runnable target) | |
Thread(Runnable target, String name) | name为线程名字 |
9java创建线程(Thread)的5种方式
1继承thread类 重写run方法
2实现runnable接口 重写run方法
3实现callable接口 重写call方法
4使用线程池
5使用匿名类
第一种方式:继承Thread类重写run方法
编写一个类,直接 继承 java.lang.Thread
,重写 run方法
。
- 怎么创建线程对象? new继承线程的类。
- 怎么启动线程呢? 调用线程对象的
start()
方法。
public class Demo01 {public static void main(String[] args) {tese tese =new tese();tese.start();for (int i = 0; i < 100; i++) {System.out.println("这个主线程的方法"+i);}} } class tese extends Thread{public void run(){for (int i = 0; i < 100; i++) {System.out.println("这个副线程的方法"+i);}} /* * 运行结果1 运行结果2 这个主线程的方法0 这个副线程的方法0 这个副线程的方法0 这个副线程的方法1 这个主线程的方法1 这个主线程的方法1 这个副线程的方法1 这个主线程的方法2 这个主线程的方法2 这个主线程的方法3 这个副线程的方法2 这个主线程的方法4 * 两个线程单独运行 * */
注意:
t.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)
t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
调用run()方法内存图: 调用start()方法内存图:
第二种方式:实现Runnable接口
编写一个类,实现 java.lang.Runnable
接口,实现run方法
。
- 怎么创建线程对象? new线程类传入可运行的类/接口。
- 怎么启动线程呢? 调用线程对象的
start()
方法。
例子
1 package 多线程.实现runnable接口重写run方法; 2 //方法一写一个实现runnable的类 3 public class Demo01 { 4 public static void main(String[] args) { 5 Thread t1 = new Thread(new Test() {}); 6 t1.start(); 7 for (int i = 0; i < 1000; i++) { 8 System.out.println( "主线程"+i); 9 } 10 11 } 12 } 13 class Test implements Runnable { 14 @Override 15 public void run() { 16 for (int i = 0; i < 1000; i++) { 17 System.out.println( "分支线程"+i); 18 } 19 } 20 }
注意:
第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。
第三种方式实现callable接口
实现Callable接口的方式创建线程的强大之处
- call()可以有返回值的
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable是支持泛型的
1 package 多线程.实现callable接口重写call方法; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 public class Demo0 { 8 public static void main(String[] args) { 9 Demo1 demo1 = new Demo1(); 10 FutureTask<Integer> futureTask = new FutureTask<Integer>(demo1); 11 Thread thread = new Thread(futureTask); 12 thread.start(); 13 int i =0 ; 14 try{ 15 i = futureTask.get(); 16 System.out.println("总和为:" + i); 17 } catch (ExecutionException e) { 18 throw new RuntimeException(e); 19 } catch (InterruptedException e) { 20 throw new RuntimeException(e); 21 } 22 for (int j = 0; j < 10; j++) { 23 System.out.println("j:" +j); 24 System.out.println("i:" +i); 25 int x =j+i; 26 System.out.println("主线程:"+x); 27 } 28 } 29 } 30 class Demo1 implements Callable { 31 32 @Override 33 public Object call() throws Exception { 34 int x=0; 35 for (int i = 0; i < 100; i++) { 36 x++; 37 } 38 return x; 39 } 40 }
方法四:使用线程池
线程池好处:
1.提高响应速度(减少了创建新线程的时间)
2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
3.便于线程管理
核心参数:
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
1 package 多线程.使用线程池; 2 3 4 import java.util.concurrent.Executors; 5 import java.util.concurrent.Callable; 6 import java.util.concurrent.ExecutorService; 7 import java.util.concurrent.ThreadPoolExecutor; 8 9 class Demo01 implements Runnable{ 10 @Override 11 public void run() { 12 for (int i = 0; i < 10; i++) { 13 if (i % 2 != 0) { 14 System.out.println( "实现runnable接口的线程 " + i); 15 } 16 } 17 } 18 } 19 class Demo02 implements Callable { 20 21 @Override 22 public Object call() throws Exception { 23 int j=0; 24 for (int i = 0; i < 10; i++) { 25 if (i % 2 == 0) { 26 System.out.println("实现Callable接口的线程" + ": " + i); 27 } 28 j=i++; 29 } 30 return j; 31 } 32 33 } 34 public class ThreadPool { 35 36 public static void main(String[] args) { 37 //1. 提供指定线程数量的线程池 38 ExecutorService service = Executors.newFixedThreadPool(10); 39 40 //输出class java.util.concurrent.ThreadPoolExecutor 41 System.out.println(service.getClass()); 42 43 ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; 44 //自定义线程池的属性 45 // service1.setCorePoolSize(15); 46 // service1.setKeepAliveTime(); 47 48 //2. 执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 49 service.execute(new Demo01());//适用于Runnable 50 service.submit(new Demo02());//适合使用于Callable 51 52 //3. 关闭连接池 53 service.shutdown(); 54 } 55 56 }
5第五中方式匿名类
package 多线程.使用匿名类; public class Demo01 {public static void main(String[] args) {//匿名类实现线程创建Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程开始启动....");for (int i = 0; i < 30; i++) {System.out.println("子 i:" + i);}}});//start是启动线程 t1.start();System.out.println("主线程开始启动....");for (int i = 0; i < 30; i++) {System.out.println("主 i:" + i);}} }
1 package 多线程.使用匿名类; 2 3 public class Demo02 { 4 public static void main(String[] args) { 5 new Thread(() -> { 6 System.out.println(Thread.currentThread().getName() + "\t上自习,进入教室"); 7 for (int i = 1; i < 100; i++) { 8 System.out.println(Thread.currentThread().getName() + "学习时间"+i+ "分钟"); 9 } 10 System.out.println(Thread.currentThread().getName() + "\t学习完成,离开教室"); 11 }, "赵克明").start(); 12 new Thread(() -> { 13 System.out.println(Thread.currentThread().getName() + "\t上自习,进入教室"); 14 for (int i = 1; i < 50; i++) { 15 System.out.println(Thread.currentThread().getName() + "学习时间"+i+ "分钟"); 16 } 17 System.out.println(Thread.currentThread().getName() + "\t学习完成,离开教室"); 18 },"1帅").start(); 19 } 20 }
10、获取当前线程对象、获取线程对象名字、修改线程对象名字
方法名 | 作用 |
---|---|
static Thread currentThread() | 获取当前线程对象 |
String getName() | 获取线程对象名字 |
void setName(String name) | 修改线程对象名字 |
例子
1 Thread t1 = new Thread(new Runnable() { 2 public void run() { 3 System.out.println("测试线程"); 4 } 5 }); 6 //获取当前线程对象 7 Thread thread = t1.currentThread(); 8 System.out.println(thread); 9 //或许这个线程名字 10 String name = thread.getName(); 11 System.out.println(name); 12 //获取当前线程的名字 13 String name1 = t1.currentThread().getName(); 14 System.out.println(name1); 15 //修改这个线程的名字 16 t1.setName("赵线程"); 17 //运行线程 18 t1.start();
11静态代理模式
package 多线程.静态代理; public class Demo01 {public static void main(String[] args) {//实例化对象我 // new wo();//实例化对象婚庆公司hunQing HunQing = new hunQing(new wo());//婚庆公司举行仪式 HunQing.yishi();//线程的底层实现原理就是静态代理 // Demo03 demo03 = new Demo03(); // new Thread(demo03,"兔子").start(); } } //结婚 interface jieHun{public void yishi(); } //我 class wo implements jieHun{@Overridepublic void yishi() {System.out.println("赵克明要结婚了");} } //婚庆公司 class hunQing implements jieHun{private jieHun jieHun;public hunQing(jieHun jieHun) {this.jieHun = jieHun;}@Overridepublic void yishi() {//布置场景 x();this.jieHun.yishi();//收取尾款 w();}private void w() {System.out.println("收取尾款");}private void x() {System.out.println("布置场景");} }
线程的底层用的是这种静态代理
12Lamda表达式
1 package 多线程.Lamda表达式; 2 3 public class Demo01 { 4 //静态内部类 5 static class Deno04 implements Deno02 { 6 @Override 7 public void test() { 8 System.out.println("test2"); 9 } 10 11 public static void main(String[] args) { 12 //普通接口调用 13 Deno02 d = new Deno03(); 14 d.test(); 15 //静态内部类调用 16 Deno04 d2 = new Deno04(); 17 d2.test(); 18 //局部内部类 19 class Deno05 implements Deno02 { 20 @Override 21 public void test() { 22 System.out.println("test3"); 23 } 24 } 25 //局部内部调用 26 Deno05 d3 = new Deno05(); 27 d3.test(); 28 //匿名内部类 29 Deno02 deno02 = new Deno02() { 30 @Override 31 public void test() { 32 System.out.println("test4"); 33 } 34 }; 35 //匿名内部类调用 36 deno02.test(); 37 38 //Lamda表达式 39 deno02 = ()-> { 40 System.out.println("test5"); 41 }; 42 deno02.test(); 43 } 44 } 45 //定义的接口 接口只有一个抽象方法的接口叫函数式接口 46 interface Deno02{ 47 public void test(); 48 } 49 //接口的实现 50 static class Deno03 implements Deno02{ 51 @Override 52 public void test() { 53 System.out.println("test1"); 54 } 55 } 56 }
lamda表达式简化了代码 这个使用必须在函数式接口中才能使用
总结
Lambda 表达式的优点很明显,在代码层次上来说,使代码变得非常的简洁。缺点也很明显,代码不易读。
优点:
代码简洁,开发迅速
方便函数式编程
非常容易进行并行计算
Java 引入 Lambda,改善了集合操作
缺点:
代码可读性变差
在非并行计算中,很多计算未必有传统的 for 性能要高
不容易进行调试
13线程的sleep方法
方法名 | 作用 |
---|---|
static void sleep(long millis) | 让当前线程休眠millis秒 |
1静态方法:Thread.sleep(1000);
2参数是毫秒
3作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
Thread.sleep()方法,可以做到这种效果:
4间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
1 public class ThreadTest06 { 2 public static void main(String[] args) { 3 //每打印一个数字睡1s 4 for(int i = 0; i < 10; i++){ 5 System.out.println(Thread.currentThread().getName() + "--->" + i); 6 7 // 睡眠1秒 8 try { 9 Thread.sleep(1000); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 } 14 } 15 }
14线程中断sleep()的方法
方法名 | 作用 |
---|---|
void interrupt() | 终止线程的睡眠 |
1 public class ThreadTest08 { 2 public static void main(String[] args) { 3 Thread t = new Thread(new MyRunnable2()); 4 t.setName("t"); 5 t.start(); 6 7 // 希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了。) 8 try { 9 Thread.sleep(1000 * 5); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 // 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。) 14 t.interrupt(); 15 } 16 } 17 18 class MyRunnable2 implements Runnable { 19 @Override 20 public void run() { 21 System.out.println(Thread.currentThread().getName() + "---> begin"); 22 try { 23 // 睡眠1年 24 Thread.sleep(1000 * 60 * 60 * 24 * 365); 25 } catch (InterruptedException e) { 26 e.printStackTrace(); 27 } 28 //1年之后才会执行这里 29 System.out.println(Thread.currentThread().getName() + "---> end"); 30 }
15java中强行终止一个线程的执行(不推荐使用,了解即可!)
package 多线程.终止线程;public class Demo01 {public static void main(String[] args) {Thread t1 = new Thread(new MyThread() {});t1.start();// 模拟5秒try {Thread.sleep(1000 * 5);} catch (InterruptedException e) {e.printStackTrace();}// 5秒之后强行终止t线程t1.stop(); // 已过时(不建议使用。) } } class MyThread implements Runnable {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(i);}try {Thread.sleep(1000 * 5);} catch (InterruptedException e) {throw new RuntimeException(e);}} }
注意:
这种方式存在很大的缺点:容易丢失数据。
因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。不建议使用。
16Java中合理结束一个进程的执行(常用)
在线程中加个判断为true运行为false保存数据然后不运行代码让他自动结束
package 多线程.终止线程;public class Demo02 {public static void main(String[] args) {A a = new A();Thread t1 = new Thread(a);t1.start();for (int i = 0; i < 1000; i++) {System.out.println(Thread.currentThread().getName()+i);if (i==100){a.stop1();break;}}} } class A implements Runnable{boolean flag = true;public void stop1() {this.flag = false;}@Overridepublic void run() {for (;;) {if (flag==true){System.out.println(Thread.currentThread().getName()+"运行中");}else {//处理你线程结束需要保存的数据System.out.println(Thread.currentThread().getName()+"停止");break;}}} }
17补充小知识:线程调度(了解)
1.常见的线程调度模型有哪些?
抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
java采用的就是抢占式调度模型。
均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。
2.java中提供了哪些方法是和线程调度有关系的呢?
2.1实例方法:
方法名 | 作用 |
---|---|
int getPriority() | 获得线程优先级 |
void setPriority(int newPriority) | 设置线程优先级 |
- 最低优先级1
- 默认优先级是5
- 最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
2.2静态方法:
方法名 | 作用 |
---|---|
static void yield() | 让位方法,当前线程暂停,回到就绪状态,让给其它线程。 |
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
2.3实例方法:
方法名 | 作用 |
---|---|
void join() | 将一个线程合并到当前线程中,当前线程受阻塞,加入的线程执行直到结束 |
join将一个线程a合并到当前线程b中,当a运行结束b才运行
1 class MyThread1 extends Thread { 2 public void doSome(){ 3 MyThread2 t = new MyThread2(); 4 t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。 5 } 6 } 7 8 class MyThread2 extends Thread{ 9 10 }
18、Java进程的优先级
常量:
常量名 | 备注 |
---|---|
static int MAX_PRIORITY | 最高优先级(10) |
static int MIN_PRIORITY | 最低优先级(1) |
static int NORM_PRIORITY | 默认优先级(5) |
方法:
方法名 | 作用 |
---|---|
int getPriority() | 获得线程优先级 |
void setPriority(int newPriority) | 设置线程优先级 |
public class ThreadTest11 {public static void main(String[] args) {System.out.println("最高优先级:" + Thread.MAX_PRIORITY);//最高优先级:10System.out.println("最低优先级:" + Thread.MIN_PRIORITY);//最低优先级:1System.out.println("默认优先级:" + Thread.NORM_PRIORITY);//默认优先级:5// main线程的默认优先级是:5System.out.println(hread.currentThread().getName() + "线程的默认优先级是:" + currentThread.getPriority());Thread t = new Thread(new MyRunnable5());t.setPriority(10);t.setName("t");t.start();// 优先级较高的,只是抢到的CPU时间片相对多一些。// 大概率方向更偏向于优先级比较高的。for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "-->" + i);}} }class MyRunnable5 implements Runnable {@Overridepublic void run() {for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "-->" + i);}} }
注意:
- main线程的默认优先级是:5
- 优先级较高的,只是抢到的CPU时间片相对多一些。大概率方向更偏向于优先级比较高的。
19、关于线程的yield()方法 (让位)
方法名 | 作用 |
---|---|
static void yield() | 让位,当前线程暂停,回到就绪状态,让给其它线程。 |
public class ThreadTest12 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable6());t.setName("t");t.start();for(int i = 1; i <= 10000; i++) {System.out.println(Thread.currentThread().getName() + "--->" + i);}} }class MyRunnable6 implements Runnable {@Overridepublic void run() {for(int i = 1; i <= 10000; i++) {//每100个让位一次。if(i % 100 == 0){Thread.yield(); // 当前线程暂停一下,让给主线程。 }System.out.println(Thread.currentThread().getName() + "--->" + i);}} }
注意: 并不是每次都让成功的,有可能它又抢到时间片了。
20,关于线程的join()方法 (强制执行)
方法名 | 作用 |
void join() | 将一个线程a合并到当前线程b中,a执行完b才能执行 |
void join(long millis) | 接上条,等待该线程终止的时间最长为 millis 毫秒 |
void join(long millis, int nanos) | 接第一条,等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒 |
public class ThreadTest13 {public static void main(String[] args) {System.out.println("main begin");Thread t = new Thread(new MyRunnable7());t.setName("t");t.start();//合并线程try {t.join(); // t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。} catch (InterruptedException e) {e.printStackTrace();}System.out.println("main over");} }class MyRunnable7 implements Runnable {@Overridepublic void run() {for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);}} }
注意: 一个线程.join(),当前线程会进入”阻塞状态
“。等待加入线程执行完!
21补充小知识:多线程并发环境下,数据的安全问题(重点)
1.为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是: 你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:★★★★★)
2.什么时候数据在多线程并发的环境下会存在安全问题呢?★★★★★
满足三个条件:
条件1:多线程并发。
条件2:有共享数据。
条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。
3.怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?
线程排队执行。(不能并发)。用排队执行解决线程安全问题。
这种机制被称为:线程同步机制。
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
线程同步就是线程排队了,线程排队了就会 牺牲一部分效率
,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。
线程同步:队列+锁
4.两个专业术语:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。
其实就是:多线程并发(效率较高。)
异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。
效率较低。线程排队执行。
同步就是排队。
22守护线程(Deamon)
一、什么是Daemon线程
Daemon线程也是守护线程,它是一种支持型的线程,主要用在程序的后台调度以及一些支持性(服务性)的工作,常见的例子:JVM中垃圾回收线程就是典型的守护线程
二、守护线程和用户线程的区别
守护线程与用户线程的区别发生在JVM的离开:
可以说JVM想要运行,用户线程也必须运行
守护线程是服务于用户线程的,如果用户线程不在了,那么守护线程的存在是没有意义的,此时该程序(进程)就没有运行的必要了,JVM也就退出了
守护线程的优先级是低于用户线程的
三、用户设置守护线程
守护线程并不是固定死的,用户也可以将自己的线程(服务性质)设置为守护线程,设置方法为Thread.setDaemon()方法,
语法:
1 package 多线程.守护线程; 2 3 public class Demo01 { 4 public static void main(String[] args) { 5 I i =new I(); 6 J j =new J(); 7 Thread thread = new Thread(j); 8 thread.setDaemon(true);//定义为守护线程 一般线程默(用户线程)认为false 9 thread.start(); 10 Thread thread1 = new Thread(i); 11 thread1.start(); 12 } 13 } 14 class I implements Runnable { 15 16 @Override 17 public void run() { 18 for (int i = 0; i < 100; i++) { 19 if (i==0){ 20 System.out.println("你出生了"); 21 } 22 System.out.println("你已经活了"+i+"岁了"); 23 } 24 System.out.println("你已经死了"); 25 } 26 } 27 class J implements Runnable { 28 @Override 29 public void run() { 30 while (true){ 31 System.out.println("上帝守护这你"); 32 } 33 34 } 35 }
在Daemon中产生的新线程也是守护线程
前面说到了,服务性质的线程可以设置为守护线程,类似于读写操作、计算逻辑。
如果带有这些操作的线程被设置为守护线程,在它执行时,有用户线程在执行的话还好说,但是可能会出现该线程创建完成且启动但是还没有执行到读写操作,尤其是写操作,可能是作为某个程序的最后一步执行,那么此时该程序中的用户线程都已经执行完毕了,而main函数只负责创建守护线程并启动,然后,main线程也会关闭,JVM发现没有用户线程了,便会退出,守护线程也会终止,此时可能还来不及进行写操作。
22线程安全
1.synchronized-线程同步
线程同步机制的语法是:
synchronized(){// 线程同步代码块。 }
重点:
synchronized后面小括号() 中传的这个“数据”是相当关键的。这个数据必须是 多线程共享
的数据。才能达到多线程排队。
1.1 ()中写什么?
那要看你想让哪些线程同步。
假设t1、t2、t3、t4、t5,有5个线程,你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?
你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。
注意:
在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。)
100个对象,100把锁。1个对象1把锁。
1.2 代码的执行原理?(★★★★★)
1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。
2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁,
找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
占有这把锁的。直到同步代码块代码结束,这把锁才会释放。
3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束,
直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
t2占有这把锁之后,进入同步代码块执行程序。
4、这样就达到了线程排队执行
重中之重:
这个共享对象一定要选好了。这个共享对象一定是你需要排队
执行的这些线程对象所共享的。
1.3 在实例方法上可以使用synchronized
synchronized出现在实例方法上,一定锁的是 this
。
没得挑。只能是this。不能是其他的对象了。所以这种方式不灵活。
1.3.1 缺点
synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低。所以这种方式不常用。
1.3.2 优点
代码写的少了。节俭了。
1.3.3 总结
如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。
1 public synchronized void withdraw(double money){ 2 double before = this.getBalance(); 3 double after = before - money; 4 try { 5 Thread.sleep(1000); 6 } catch (InterruptedException e) { 7 e.printStackTrace(); 8 } 9 this.setBalance(after); 10 }
1.4 在方法调用处使用synchronized
在方法上用synchronized 要锁的是线程共享的对象
1 package 多线程.线程同步; 2 3 public class demo02 { 4 5 public static void main(String[] args) { 6 Account a1 =new Account(1000,"赵可明"); 7 new Thread(new A(a1,50,"赵小可")).start(); 8 new Thread(new A(a1,100,"赵1可")).start(); 9 new Thread(new A(a1,100,"赵2可")).start(); 10 new Thread(new A(a1,100,"赵3可")).start(); 11 new Thread(new A(a1,100,"赵4可")).start(); 12 new Thread(new A(a1,500,"赵5可")).start(); 13 new Thread(new A(a1,500,"赵6可")).start(); 14 } 15 } 16 17 //银行取钱 18 class A implements Runnable{ 19 //账户的钱 20 Account account; 21 //取多少钱 22 private int getAccount; 23 //你拿到多少钱 24 private int own; 25 //取钱人 26 private String name; 27 public A(Account account, int getAccount , String name){ 28 this.account = account; 29 this.getAccount=getAccount; 30 this.name=name; 31 } 32 public void run() { 33 //这个锁的是银行A 这个银行对象不是共享的 这些线程共享的应该是账户 34 //synchronized(this){ 35 //synchronized(this){和在方法上加 public synchronized void run() { 是一样的 36 synchronized (account){ 37 if (account.getMoney()-getAccount<0){ 38 System.out.println("余额不足,你的账户余额:"+account.getMoney()); 39 return; 40 }else { 41 try { 42 Thread.sleep(1000); 43 } catch (InterruptedException e) { 44 throw new RuntimeException(e); 45 } 46 System.out.println("你的账户余额:"+account.getMoney()); 47 account.setMoney(account.getMoney() - getAccount); 48 System.out.println(account.getName()+"你的取款金额为:"+getAccount+"你的账户余额:"+account.getMoney()+"操作人"+name); 49 } 50 51 } 52 } 53 } 54 //账户类 55 class Account { 56 private int money; 57 private String name; 58 59 public Account(int money, String name) { 60 this.money = money; 61 this.name = name; 62 } 63 64 public int getMoney() { 65 return money; 66 } 67 68 public String getName() { 69 return name; 70 } 71 72 public void setMoney(int money) { 73 this.money = money; 74 } 75 76 public void setName(String name) { 77 this.name = name; 78 } 79 }
23、Java中有三大变量?★★★★★
- 实例变量:在堆中。
- 静态变量:在方法区。
- 局部变量:在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。
- 因为局部变量不共享。(一个线程一个栈。)
- 局部变量在栈中。所以局部变量永远都不会共享。
- 实例变量在堆中,堆只有1个。
- 静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
总结:
- 局部变量+常量:不会有线程安全问题。
- 成员变量(实例+静态):可能会有线程安全问题。
24、以后线程安全和非线程安全的类如何选择?
如果使用局部变量的话:
建议使用:StringBuilder。
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。
反之:
使用StringBuffer。
- ArrayList是非线程安全的。
- Vector是线程安全的。
- HashMap HashSet是非线程安全的。
- Hashtable是线程安全的。
25总结synchronized
第一种:同步代码块
灵活
synchronized(线程共享对象){同步代码块; }
第二种:在实例方法上使用synchronized
表示共享对象一定是 this 并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized
表示找 类锁。类锁永远只有1把。
就算创建了100个对象,那类锁也只有1把。
注意区分:
对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。
26、我们以后开发中应该怎么解决线程安全问题?
是一上来就选择线程同步吗?synchronized不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。
第一种方案:尽量使用局部变量 代替 “实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。
27、死锁(DeadLock)
死锁代码要会写。一般面试官要求你会写。
只有会写的,才会在以后的开发中注意这个事儿。
因为死锁很难调试。
1 package 多线程.死锁DeadLock; 2 3 public class Demo01 { 4 public static void main(String[] args) { 5 C c1 = new C(); 6 D d1 = new D(); 7 Thread threadA = new Thread( new A(c1,d1)); 8 threadA.setName("小胖子"); 9 Thread threadB = new Thread(new B(c1,d1)); 10 threadB.setName("小瘦子"); 11 threadA.start(); 12 threadB.start(); 13 } 14 } 15 class A implements Runnable { 16 C c; 17 D d; 18 19 public A(C c1, D d1) { 20 this.c = c1; 21 this.d = d1; 22 } 23 24 @Override 25 public void run() { 26 synchronized (c){ 27 System.out.println(Thread.currentThread().getName()+":我穿裤子A"); 28 try { 29 Thread.sleep(5000); 30 } catch (InterruptedException e) { 31 throw new RuntimeException(e); 32 } 33 synchronized (d){ 34 System.out.println("===================="); 35 } 36 } 37 38 } 39 } 40 class B implements Runnable{ 41 C c; 42 D d; 43 44 public B( C c,D d){ 45 this.c =c; 46 this.d=d; 47 } 48 @Override 49 public void run() { 50 synchronized (d){ 51 System.out.println(Thread.currentThread().getName()+":我穿上衣B"); 52 try { 53 Thread.sleep(5000); 54 } catch (InterruptedException e) { 55 throw new RuntimeException(e); 56 } 57 synchronized (c){ 58 System.out.println("===================="); 59 } 60 } 61 } 62 } 63 class C { 64 65 } 66 class D { 67 68 }
这个程序就卡死啦 你需要上衣我需要裤子
28、定时器
1定时器的作用:
间隔特定的时间,执行特定的程序。
例
每周要进行银行账户的总账操作。
每天要进行数据的备份操作。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:
可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
构造方法:
构造方法名 | 备注 |
Timer() | 创建一个定时器 |
Timer(boolean isDaemon) | isDaemon为true为守护线程定时器 |
Timer(String name) | 创建一个定时器,其线程名字为name |
Timer(String name, boolean isDaemon) | 结合2、3 |
方法:
方法名 | 作用 |
---|---|
void schedule(TimerTask task, Date firstTime, long period) | 安排指定的任务在指定的时间开始进行重复的固定延迟执行 |
void cancel() | 终止定时器 |
29、使用定时器实现日志备份
平时格式
1 class TimerTest01{ 2 public static void main(String[] args) { 3 Timer timer = new Timer(); 4 // Timer timer = new Timer(true);//守护线程 5 String firstTimeStr = "2021-05-09 17:27:00"; 6 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 7 try { 8 Date firstTime = sdf.parse(firstTimeStr); 9 timer.schedule(new MyTimerTask(), firstTime, 1000 * 5);//每5s执行一次 10 } catch (ParseException e) { 11 e.printStackTrace(); 12 } 13 } 14 } 15 16 class MyTimerTask extends TimerTask{ 17 @Override 18 public void run() { 19 Date d = new Date(); 20 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 21 String time = sdf.format(d); 22 System.out.println(time + ":备份日志一次!"); 23 } 24 }
匿名内部类格式
class TimerTest02{public static void main(String[] args) {Timer timer = new Timer();String firstTimeStr = "2021-05-09 17:56:00";SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");try {Date firstTime = sdf.parse(firstTimeStr);timer.schedule(new TimerTask() {@Overridepublic void run() {Date d = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String time = sdf.format(d);System.out.println(time + ":备份日志一次!");}}, firstTime, 1000 * 5);} catch (ParseException e) {e.printStackTrace();}} }
30、关于Object类的wait()、notify()、notifyAll()方法
1方法:
方法名 | 作用 |
---|---|
void wait() | 让活动在当前对象的线程无限等待(释放之前占有的锁) |
void notify() | 唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁) |
void notifyAll() | 唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁) |
2方法详解
第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是 Object类中自带
的。
wait方法和notify方法不是通过线程对象调用,
不是这样的:t.wait(),也不是这样的:t.notify()…不对
第二:wait()方法作用?
Object o = new Object(); o.wait();
表示:让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止
o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。
第三:notify()方法作用?
Object o = new Object(); o.notify();
表示:唤醒正在o对象上等待的线程。
第四:notifyAll() 方法 作用?
Object o = new Object(); o.notifyAll();
表示:这个方法是唤醒o对象上处于等待的所有线程。
3图文
4总结 ★★★★★(呼应生产者消费者模式)
1、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。
2、wait方法和notify方法建立在 线程同步 的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。
3、wait方法作用:o.wait() 让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
4、notify方法作用:o.notify() 让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
31、生产者消费者模式(wait()和notify())
1什么是“生产者和消费者模式”?
- 生产线程负责生产,消费线程负责消费。
- 生产线程和消费线程要达到均衡。
- 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。
2模拟一个业务需求
模拟这样一个需求:
-
仓库我们采用List集合。
-
List集合中假设只能存储1个元素。
-
1个元素就表示仓库满了。
-
如果List集合中元素个数是0,就表示仓库空了。
-
保证List集合中永远都是最多存储1个元素。
-
必须做到这种效果:生产1个消费1个。
使用wait方法和notify方法实现“生产者和消费者模式”
public class ThreadTest16 {public static void main(String[] args) {// 创建1个仓库对象,共享的。List list = new ArrayList();// 创建两个线程对象// 生产者线程Thread t1 = new Thread(new Producer(list));// 消费者线程Thread t2 = new Thread(new Consumer(list));t1.setName("生产者线程");t2.setName("消费者线程");t1.start();t2.start();} }// 生产线程 class Producer implements Runnable {// 仓库private List list;public Producer(List list) {this.list = list;}@Overridepublic void run() {// 一直生产(使用死循环来模拟一直生产)while(true){// 给仓库对象list加锁。synchronized (list){if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。try {// 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。 list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 程序能够执行到这里说明仓库是空的,可以生产Object obj = new Object();list.add(obj);System.out.println(Thread.currentThread().getName() + "--->" + obj);// 唤醒消费者进行消费 list.notifyAll();}}} }// 消费线程 class Consumer implements Runnable {// 仓库private List list;public Consumer(List list) {this.list = list;}@Overridepublic void run() {// 一直消费while(true){synchronized (list) {if(list.size() == 0){try {// 仓库已经空了。// 消费者线程等待,释放掉list集合的锁 list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 程序能够执行到此处说明仓库中有数据,进行消费。Object obj = list.remove(0);System.out.println(Thread.currentThread().getName() + "--->" + obj);// 唤醒生产者生产。 list.notifyAll();}}} }