一、多线程的常用的方法
stop() 停止线程 方法已过时了因为不安全 但还是有效果 一般不使用,建议用wait()
interrupt();只能短暂停止,还是会运行完线程
package day22;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
/* if(i==5){
//停止线程 stop()已过时了 用interrupt()
//stop();
interrupt();//只能短暂停止,还是会运行完线程
}*/
System.out.println(Thread.currentThread().getName()+"\t"+i);
}
}
}
package day22;
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyThread th1 = new MyThread();
th1.start();
Thread.sleep(2000);
//结束当前线程
//th1.stop();
th1.interrupt();
}
}
yield() 是静态方法 直接通过类名调用 不建议用对象名调用 暂停当前正在运行的线程 执行其他线程
阿里规范 用对象名调用静态方法增加编译器解析成本,直接用类名来访问即可。
package day22;
public class Test02 {
public static void main(String[] args) {
MyThread th1 = new MyThread();
th1.start();
//yield() 是静态方法 直接通过类名调用 不建议用对象名调用
//阿里规范 用对象名调用增加编译器解析成本,直接用类名来访问即可。
//暂停当前正在运行的线程 执行其他线程
Thread.yield();
MyThread th2 = new MyThread();
th2.start();
}
}
join()表示等待该线程执行完毕之后 其它线程才能执行(方法调用必须是在开启线程之后调用才有效果)
package day22;
public class Test03 {
public static void main(String[] args) throws InterruptedException {
MyThread th1 = new MyThread();
th1.start();
//表示等待改线程执行完毕之后 其它线程才能执行(方法调用必须是在开启线
//程之后调用才有效果)
th1.join();
MyThread th2 = new MyThread();
th2.start();
}
}
二、Object 提供线程中的使用的方法
需求分析
其原理就是根据包子存不存在就行等待休眠,同时锁住所有操作,因为包子一种状态对应不同线程的不同操作,如果不锁住,就会抢发生IllegalMonitorStateException 非法监视器状态异常
这个异常就是 线程1 刚生产了一个包子,线程2抢到消费了,包子的存在对线程1就是问题,计算机理解不了,回忆之前未使用锁时,两个线程循环打印1~100,并不是等线程1打印完线程2在打印,而是谁抢到了打印谁的,包子的状态时共有的,线程抢占就会有冲突。
或者就是单纯不能直接使用wait();notify()
在线程中调用wait方法的时候 要用synchronized锁住对象,确保代码段不会被多个线程调用,即需要obj.wait();obj.notify();obj为锁对象
包子存在就 消费者去吃,吃完唤醒老板,老板就等待
包子不存在就消费者等待,老板生产,做完包子唤醒消费者
package day22;
public class Test04 {
public static void main(String[] args) {
//定义一个锁的对象
Object obj = new Object();
//买包子消费者线程
new Thread(){
@Override
public void run() {
synchronized (obj){
//第一步
System.out.println("我需要一个包子");
//需要无线进行等待
try {
//第二步
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("==========");
//第五步
System.out.println("我正在吃包子");
}
}
}.start();
//老板线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized(obj){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//第三步
System.out.println("我的包子已经做好了,可以开始吃了");
//需要唤醒 消费者 可以来吃包子
//第四步
obj.notify();
}
}
}).start();
}
}
三、多线程中的生产者与消费者模式
需求分析
包子类
package day22;
public class BaoZi {
public String pi;
public String xian;
//定义一个标记表示是否存在包子 false 没有包子 ture 有包子
boolean flag = false;
//boolean 默认值为false
}
生产者
package day22;
public class ProducterThread extends Thread{
private BaoZi baoZi;
public ProducterThread(BaoZi baoZi){
this.baoZi = baoZi;
}
@Override
public void run() {
//一直生产包子
while (true){
//同步代码块 //互斥锁
synchronized(baoZi){
//已存在包子 等待消费 不需要在生产
if(baoZi.flag){
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//不存在包子 开始生产包子
baoZi.pi = "薄皮";
baoZi.xian ="韭菜鸡蛋";
System.out.println("==============");
System.out.println("我生产了一个"+baoZi.pi+baoZi.xian+"的包子");
//将标记值设置为ture
baoZi.flag = true;
//通知消费者去消费
baoZi.notify();
}
}
}
}
消费者
package day22;
public class CustomerThread extends Thread{
private BaoZi baoZi;
public CustomerThread(BaoZi baoZi){
this.baoZi=baoZi;
}
@Override
public void run() {
while (true){
synchronized (baoZi){
//如果包子不存在 等包子生成出来
if(!baoZi.flag){
//无线等待
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//包子存在 吃包子
System.out.println("我消费了一个"+baoZi.pi+baoZi.xian+"的包子");
baoZi.pi=null;
baoZi.xian=null;
//标记设置为false
baoZi.flag = false;
//通知生产者开始生产
baoZi.notify();
}
}
}
}
测试类
多个类(称为线程一类,线程二类) 需要一个共有的变量,即将这个变量封装为一个类(称为属性类)的属性,注意属性类的属性修饰符为public公共的,在测试类中new 这个属性类实例化为对象,然后将此对象作为参数传入需要的多个类(线程一,线程二)中,就可以实现共享对象的属性即变量,且多个类(线程一,线程二) 需要定义此对象(属性类)为私有变量,并提供有参构造
package day22;
public class Test05 {
public static void main(String[] args) {
BaoZi baoZi =new BaoZi();
//开启生产者的线程
new ProducterThread(baoZi).start();
//开启消费者的线程
new CustomerThread(baoZi).start();
}
}
四、多线程的第三种实现方式(实现类连接Callable接口-带返回值)
步骤
定义一个类 实现Callable 这个接口
实现接口中的抽象方法 cell()
实例化 实现Callable的类
实例化任务的对象 FutureTask 构造方法的参数 需要传递 Callable 的实现类
实例化Thead对象 构造方法中需要传递 任务对象 调用start() 开启线程
根据任务对象的get() ==>获取返回值
案例 实现多线程1-100之和 获取其返回值结果
package day22;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Integer count =0;
for (int i = 0; i < 100; i++) {
count+=i;
}
System.out.println(Thread.currentThread().getName());
return count;
}
}
package day22;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test06 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//实例化MyCallable对象
MyCallable call = new MyCallable();
//实例化任务对象
FutureTask<Integer> ft = new FutureTask<>(call);
//实例化Thread对象
new Thread(ft).start();
//调用任务对象的get方法 获取其返回值
Integer count = ft.get();
System.out.println(count);
}
}
五、枚举
1.枚举的概念:用于间接的表示一些固定的值 Java提供了枚举来进行表示
在枚举中可以将变量 一一进行罗列 而枚举的值必须是罗列的值 限制的数据的范围
2.定义的格式
访问修饰符 enum 枚举的名称 {
枚举项
}
enum 是枚举的关键字
3.代码
public enum Color {
//枚举的选项
BLUE,YELLOW,RED;
}
以前程序员手写实现枚举作用的类,便于理解枚举
class EnumByClass{
public static final int RED=0;
public static final int GREEN=1;
public static final int BLUE=2;
}
4.枚举的特点
1.所有的枚举类都是Enum子类
2.每一个枚举项都一个对象
3.每一个枚举项都可以通过枚举类来进行获取 枚举类.枚举项
4.一般枚举项是编写在第一行 不同的枚举项以,来进行分割 最后以分号进行结尾
5.枚举类中可以有成员变量
4.枚举类中可以有构造 但是只能是私有的构造 默认的也是私有的构造
5.枚举类中可以有抽象方法,但是每一个枚举项都必须实现其抽象方法
5.代码
//枚举的选项实例 BLUE,YELLOW,RED都可以理解是Color的子类对象 且每个枚举实例都是static final类型的,也就表明只能被实例化一次,且每次访问都是在实例化,因为final修饰地址值不变,对象是相等的
package day22;
public enum Color {
//枚举的选项 BLUE,YELLOW,RED都可以理解是Color的子类对象
BLUE{
@Override
public String getColor() {
return "蓝色";
}
},YELLOW{
@Override
public String getColor() {
return "黄色";
}
},RED{
@Override
public String getColor() {
return "红色";
}
};
private String name;
private Color(){
}
public abstract String getColor();
}
6.枚举中常用的一些方法
package day22;
public class Test07 {
public static void main(String[] args) {
/*//输出结果为BLUE YELLOW RED
System.out.println(Color.BLUE);
System.out.println(Color.YELLOW);
System.out.println(Color.RED);*/
Color c1 = Color.RED;
Color c = Color.YELLOW;
System.out.println(c.name());
System.out.println(c.ordinal());
//c1-c的索引值之差
System.out.println(c1.compareTo(c));
System.out.println(c1.toString());
System.out.println(Color.valueOf("YELLOW"));
Color[] values = Color.values();
for (Color value : values) {
System.out.println(value.name());
}
}
}
7.switch与枚举进行搭配的使用
package day22;
public enum Week {
星期一,星期二,星期三,星期四,星期五,星期六,星期天;
}
package day22;
public class Test08 {
public static void main(String[] args) {
Week w = Week.星期四;
switch (w){
case 星期一:
System.out.println("猪脚饭");
break;
case 星期二:
System.out.println("烧鸭饭");
break;
case 星期三:
System.out.println("白切鸡");
break;
case 星期四:
System.out.println("烧腊");
break;
case 星期五:
System.out.println("猪排");
break;
case 星期六:
System.out.println("茄子");
break;
case 星期天:
System.out.println("土豆丝");
break;
default:
break;
}
}
}
六、线程的生命周期
1.线程的基本状态
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
1)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
3)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
原文地址
记忆:一个三角形 加外延两点 即 可运行 正在运行 阻塞 新建和死亡
具体的状态转换看下图
2.线程的生命周期
3.线程的状态
Thread.State ==> 通过枚举来进行一一罗列
4.获取线程状态的方法
public Thread.State getState()
5.代码
线程类
public class MyThread extends Thread {
@Override
public void run() {
}
}
测试类
public class Test {
public static void main(String[] args) {
MyThread th1 = new MyThread();
System.out.println(th1.getState());
th1.start();
System.out.println(th1.getState());
}
}
七、线程池
1.线程池:用于存储大量线程的的容器 就称为叫做线程池
2.没有线程池之前
3.有线程池之后
4.使用线程池的步骤
1.通过这个Executors工具类来获取获取线程池对象 并且设置线程的个数
newFixedThreadPool(设置线程池的个数)
newSingleThreadExecutor() 线程池的线程的个数1
2.创建任务的对象
定义一个类 实现Runnable
定义一个类 实现Callable
3.调用submit方法 提交任务到线程池中 submit(任务对象)
4.调用这个类Future的get() 可以获取到返回值
Executors 线程池工具的方法
创建任务的对象 Runnable 没有返回值 Callable 有返回值
1.实现Runnable接口
package day22.threadpool;
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("哈哈哈");
}
}
2.实现Callable
package day22.threadpool;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Integer count =0;
for (int i = 0; i < 100; i++) {
count+=i;
}
return count;
}
}
创建线程池,提交任务到线程池中
package day22.threadpool;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Test09 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//获取单个线程的线程池对象
//创建一个线程
ExecutorService service1 = Executors.newSingleThreadExecutor();
//创建3个线程
ExecutorService service2 = Executors.newFixedThreadPool(3);
//不带返回值
service2.submit(new MyRunnable());
service2.submit(new MyRunnable());
service2.submit(new MyRunnable());
//带返回值
Future f = service1.submit(new MyCallable());
Integer count = (Integer) f.get();
System.out.println(count);
}
}
线程两个方法
package day22.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test10 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
executorService.submit(new MyRunnable());
//执行已提交的任务,不会在接受新任务
executorService.shutdown();
//试图停止所有正在执行的活动任务,暂停处理正在等待的任务
//完成时间短的任务一般先完成了,但是正在等待的任务一定会被停止,不执行
executorService.shutdownNow();
}
}
八、java中定时任务
1.生活中的定时任务就是闹钟 在开发中定时发送邮件 定时向文件中写入内容
2.定时任务使用的步骤
1.定义一个类 继承 TimerTask == > 表示是一个任务的对象
2.创建定时器对象 Timer
3.需要将任务提交到定时对象中
3.需求: 指定的时间向文件中写入内容 ==> 使用定时任务
4.代码
package day22.threadpool;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.TimerTask;
public class RemindTask extends TimerTask {
@Override
public void run() {
System.out.println("我是定时任务");
try {
BufferedWriter bw = new BufferedWriter(new FileWriter("1.txt"));
bw.write("定时任务");
bw.flush();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
如果d 在当前时间之前就会直接执行定时任务
如果d 在当前时间之后 就会等到指定d的时间才会执行定时任务
package day22.threadpool;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Scanner;
import java.util.Timer;
public class Test11 {
public static void main(String[] args) throws ParseException {
/*long time = System.currentTimeMillis();
Date d = new Date(time+5000);*/
// 如果d 在当前时间之前就会直接执行定时任务
//如果d 在当前时间之后 就会等到指定d的时间才会执行定时任务
//Date d= new Date(time-5000);
Scanner sc = new Scanner(System.in);
System.out.println("请输入时间");
String s = sc.nextLine();
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d = sf.parse(s);
System.out.println(d);
Timer t =new Timer();
RemindTask ta = new RemindTask();
t.schedule(ta,d);
}
}
可以直接通过设置第二个参数来延迟时间完成
//timer.schedule(remindTask,date);
//第二个参数可以为long类型,即延迟执行的时间,单位毫秒
timer.schedule(task,5000);
九、单例设置模式
1.特点: 有且仅实例化一个对象 保证对象的唯一 即实例化两次对象,第一次对象 == 第二次对象 为true
2.使用场景: 开发中的工具类 properties文件的加载 也只需要加载一次
3.有且仅实例化
1.私有的属性
2.私有的构造 ==> 只能在本类实例化这个对象
3.提供一个公有的方法
4.单例设置模式的分类 使用双重锁来实现单例设置模式
1.饿汉模式
优点: 没有锁机制 效率比较高
缺点: 项目初始化的时候 就进行加载 可能出现项目卡顿
代码
从上往下一一执行,一一看,一一理解就行了
package day22.danli;
import java.util.Date;
/*
* 实例化一次*/
public class DateUtils {
//私有的属性
private static DateUtils utils = new DateUtils();
//私有的构造
private DateUtils(){
}
//提供一个公有的方法
public static DateUtils getInstance(){
if(utils!=null){
return utils;
}
return null;
}
}
测试类
public class Test {
public static void main(String[] args) {
DateUtils d1 = DateUtils.getInstance();
DateUtils d2 = DateUtils.getInstance();
System.out.println(d1);
System.out.println(d2);
System.out.println(d1==d2);
}
}
2.懒汉模式
优点: 使用的时候进行加载 不是项目初始化就立即加载 懒加载
缺点: 在多线程不安全的
代码
package day22.danli;
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateUtils1 {
private static DateUtils1 utils1;
private DateUtils1(){
}
public static DateUtils1 getInstance(){
if(utils1 == null){
utils1 = new DateUtils1();
}
return utils1;
}
}
测试类
public class Test {
public static void main(String[] args) {
DateUtils1 d1 = DateUtils1.getInstance();
DateUtils1 d2 = DateUtils1.getInstance();
System.out.println(d1);
System.out.println(d2);
System.out.println(d1==d2);
}
}
3.双重锁(使用最多)
优点:使用多线程的锁的机制来保证多线程中的安全
缺点: 效率会降低
代码
其他地方看到的双重锁只是双重检验,方法上没有synchronized,即没有对getInstance()方法加锁
理解: 在多个线程中如果是单个锁,在线程1进入getInstance()方法通过if(utils2 ==null)是被其他线程抢占,就会多次执行utils2=new DateUtils2(); 语句,导致utils2地址冲突
而双重锁 在线程1进入getInstance()方法通过if(utils2 ==null)还要获取锁对象,如果获取了synchronized (DateUtils2.class)锁对象,此时被其他线程抢占,其执行到synchronized (DateUtils2.class)时,因为锁对象被线程1占取,进入阻塞状态,只有等线程1执行完所有语句(创建了utils2对象),在释放锁对象其他线程才能进入运行状态,当其他线程进行运行状态,此时线程1已创建了utils2对象,if(utils2 ==null)结果为false
从阻塞状态进入运行状态,其代码接着未运行的代码继续运行(每个线程都有单独的栈运行方法,阻塞只是栈没获取到cpu,放在阻塞区,转为就绪状态时无需新建栈重新运行),所以如果变量发生改变就可能报错,通过锁来保证安全,
package day22.danli;
public class DateUtils2 {
//私有的属性 volatile 可见性 变量发生改变之后 所有线程获取到改变后的值
private static volatile DateUtils2 utils2;
//私有的构造
private DateUtils2(){
}
//提供一个公有的方法
public static synchronized DateUtils2 getInstance(){
//单个锁
/*if(utils2 ==null){
utils2=new DateUtils2();
}*/
if(utils2 == null){
//双重锁
synchronized (DateUtils2.class){
if (utils2 ==null){
utils2 = new DateUtils2();
}
}
}
return utils2;
}
}
测试类
public static void main(String[] args) {
DateUtils2 d1 = DateUtils2.getInstance();
DateUtils2 d2 = DateUtils2.getInstance();
System.out.println(d1);
System.out.println(d2);
System.out.println(d1==d2);
}
4.使用静态内部类
package day22.danli;
public class DateUtils3 {
//静态的内部类
private static class Holer{
//在内部类中实例化外部类的对象
public static final DateUtils3 utiils3 = new DateUtils3();
}
//私有的构造方法
private DateUtils3(){
}
//提供一个公有的方法来进行访问
public static DateUtils3 getInstance(){
return Holer.utiils3;
}
}
测试类
public static void main(String[] args) {
DateUtils3 d1 = DateUtils3.getInstance();
DateUtils3 d2 = DateUtils3.getInstance();
System.out.println(d1);
System.out.println(d2);
System.out.println(d1==d2);
}
5.枚举单例模式
访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。
在调用构造方法时,我们的单例被实例化。所以使用枚举能够实现懒加载、线程安全。
package day22.work;
public enum DataUtils {
DATA_UTILS;
DataUtils(){}
public DataUtils getInstance(){
return DATA_UTILS;
}
}
测试
package day22.work;
public class Work02 {
public static void main(String[] args) {
DataUtils dataUtils1= DataUtils.DATA_UTILS;
DataUtils dataUtils2= DataUtils.DATA_UTILS;
System.out.println(dataUtils1 ==dataUtils2);
}
}