17 多线程,锁,线程池,定时器

17 多线程,锁,线程池

Properties配置文件的读取

  • 目的 : 便于维护
  • 新建配置文件 : 在src右键–>new–>file–>config.properties
  • 步骤 :
    • 创建Properties对象
    • 加载配置文件
    • 使用对象获取配置文件中的信息

config.properties

在这里插入图片描述

PropertiesDemo1

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;

//如何从配置文件中获取信息
public class PropertiesDemo1 {
	public static void main(String[] args) throws Exception {
		
		//1.创建properties对象
		Properties pro = new Properties();
		
		//2.通过对象加载配置文件,将配置文件中的内容加载到流中
		pro.load(PropertiesDemo1.class.getClassLoader().getResourceAsStream("config.properties"));
		
				
		//3.通过pro对象获取流中加载过来的数据
		String value = pro.getProperty("birthday");
		
		System.out.println(value);
		
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
		Date parse = sdf.parse(value);
		
		System.out.println(parse);
	}
}

标准输入输出流

  • System.in : InputStream
  • System.out : PrintStream

线程与进程

  • 进程 ( Process ) : 一个程序的运行 , 程序在内存中分配的空间

    • 至少有一个进程
  • 线程 ( Thread ) : 进程中的一个执行单位 , 执行路径

    • 如果进程中有多个线程—>多线程的程序
  • 并行 : 某一时间点 , 有多个程序同时执行 , 多核CPU

  • 并发 : 某一时间段 , 有多个程序同时执行 , 并不是真正意义的同时执行—>多线程

  • 并发 ( 多线程 ) 真的是同时执行吗?

    • 不是 , 而是时间间隔很短 , 造成了同时执行的错觉
  • 多线程的程序有什么优点?

    • 提高了用户的体验 , 提高了程序的运行效率 ? 提高了CPU的使用率
  • 前面的代码都是单线程的程序 , 都是在main方法中依次执行

    • 主线程 : 用于执行main方法的线程

多线程的概念

在这里插入图片描述

实现多线程的第一种方式
  • 如何实现线程 :
    • 继承Thread
    • 重写run ( 线程要做的事 , 定义在该方法中 )
    • 创建子类的对象
    • 使用该对象调用start()方法
  • run()方法和start()方法的区别 :
    • run()方法 : 只是普通方法的调用 , 不会开启新的线程
    • start()方法 : 开启新的线程 , 并自动调用run()方法
public class ThreadDemo1 {
	public static void main(String[] args) {
		
		MyThread th1 = new MyThread();
		th1.setName("一号子线程");
		th1.start();
		
		MyThread th2 = new MyThread();
		th2.setName("二号子线程");
		th2.start();
		
		Thread.currentThread().setName("主线程");
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"-----"+i);
		}
	}
}


class MyThread extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"-----"+i);
		}
	}
}
线程中的常用方法
  • 线程阻塞
public static void sleep(long millis)					//静态方法,时间结束后进入可执行

public final void join()								//成员方法,被谁调用,让哪个线程先执行,执行完毕,再执行所在线程

public static void yield()								//让步,让其他线程先执行,不一定生效,因为是CPU决定的
  • 中断线程
public final void stop()								//停止一个线程,已过时

public void interrupt()									//打断线程的阻塞状态,进入可执行状态,会抛出异常
    • sleep()方法
public class ThreadDemo2 {
	public static void main(String[] args) throws Exception {
		MyTheard2 mt = new MyTheard2();
		
		Thread.sleep(3000);
		mt.setName("子线程");
		mt.start();
	}
}

class MyTheard2 extends Thread{
	@Override
	public void run() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"-----"+i);
		}
	}
}
    • join()方法
public class ThreadDemo3 {
	public static void main(String[] args) throws Exception {
		MyThread3 mt3 = new MyThread3();
		
		mt3.start();
		
		mt3.join();
		
		System.out.println(MyThread3.sum);
	}
}

class MyThread3 extends Thread {
	public static int sum = 0;

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			sum += i;
		}
	}
}
实现多线程的第二种方式
  • 实现多线程的方式二 :
    • 实现Runnable接口
    • 重写run()方法
    • 创建Runna接口的子类对象
    • 创建Thread类的对象 , 把第三步的对象传到构造方法中
    • 使用Thread类的对象 , 调用start()方法
  • 既然有了第一种实现方式 , 为什么还要第二种 ?
    • 解决单继承的局限性
    • 符合面向对象的思想 : 把线程任务和线程对象分离开来
public class RunnableDemo1 {
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable();
		
		Thread th1 = new Thread(mr,"一号子线程");
		Thread th2 = new Thread(mr,"二号子线程");
		
		th1.start();
		th2.start();
		
		for (int i = 0; i < 10; i++) {
			System.out.println("主线程-----"+i);
		}
	}
}

class MyRunnable implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"-----"+i);
		}
	}
	
}
/**
 * 创建两个线程 ,一个线程负责打印大写字母表,一个线程负责打印小写字母表
 * @author Apase
 *
 */
public class RunnableDemo2 {
	public static void main(String[] args) {
		MyRunnable1 mr1 = new MyRunnable1();
		
		Thread th1 = new Thread(mr1);
		
		MyRunnable2 mr2 = new MyRunnable2();
		
		Thread th2 = new Thread(mr2);
		
		th1.start();
		th2.start();
	}
}

class MyRunnable1 implements Runnable{

	@Override
	public void run() {
		for (char i = 'a'; i <= 'z'; i++) {
			System.out.println(Thread.currentThread().getName()+"-----"+i);
		}
	}
}

class MyRunnable2 implements Runnable{

	@Override
	public void run() {
		for (char i = 'A'; i <= 'Z'; i++) {
			System.out.println(Thread.currentThread().getName()+"-----"+i);
		}
	}
}
线程的生命周期

在这里插入图片描述

线程安全问题
//卖票案例

public class MyTicketsTest1 {
	public static void main(String[] args) {
		MyTicket mt1 = new MyTicket();
		mt1.setName("北京窗口");
		
		MyTicket mt2 = new MyTicket();
		mt2.setName("西双版纳窗口");
		
		MyTicket mt3 = new MyTicket();
		mt3.setName("布宜诺斯艾利斯窗口");
		
		mt1.start();
		mt2.start();
		mt3.start();
	}
}

class MyTicket extends Thread{
	static int tickets = 100;
	@Override
	public void run() {
		while (true) {
			if (tickets > 0) {
				System.out.println(Thread.currentThread().getName()+"还剩"+tickets--+"张票");
			} else {
				break;
			}
			
		}
	}
}
产生原因

在卖票过程中 , 出现了多个窗口卖同一张票 , 或者票数为0及负数的情况 , 这就是传说中的线程安全问题

  • 线程安全问题产生的条件是什么 ?
    • 具备多线程的环境
    • 多个线程操作共享操作
    • 操作共享数据的代码有多条
解决方案
  • 加锁 , 让某一时刻只能有一个线程进行操作 .
    • 同步代码块
    • 同步方法
    • Lock
同步代码块
synchronized(锁对象){
    容易产生线程安全问题的代码
}

锁对象 : 可以是任意对象 , 要求多个线程使用的是同一个对象

public class MyTicketsTest2 {
	public static void main(String[] args) {
		MyTicket2 mt = new MyTicket2();
		
		new Thread(mt,"北京窗口").start();
		new Thread(mt,"西双版纳窗口").start();
		new Thread(mt,"布宜诺斯艾利斯窗口").start();
	}
}

class MyTicket2 implements Runnable {
	int tickets = 100;

	Object obj = new Object();
	@Override
	public void run() {
		while (true) {
			synchronized(obj) {
				if (tickets > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "还剩" + tickets-- + "张票");
				} else {
					break;
				}
			}
			
		}
	}
}

上述代码依然存在线程安全问题 , 因为锁对象的选取不符合条件

解决方案 :

    • 通过构造方法传入同一个obj

      public class MyTicketsTest3 {
      	public static void main(String[] args) {
      		Object obj = new Object();
      
      		MyTicket3 mt1 = new MyTicket3(obj);
      		mt1.setName("迪丽热巴");
      
      		MyTicket3 mt2 = new MyTicket3(obj);
      		mt2.setName("古力娜扎");
      
      		MyTicket3 mt3 = new MyTicket3(obj);
      		mt3.setName("马尔扎哈");
      
      		mt1.start();
      		mt2.start();
      		mt3.start();
      	}
      }
      
      class MyTicket3 extends Thread {
      	static int tickets = 100;
      	private Object obj;
      
      	public MyTicket3(Object obj) {
      		this.obj = obj;
      	}
      
      	@Override
      	public void run() {
      		while (true) {
      			synchronized (obj) {
      				if (tickets > 0) {
      					try {
      						Thread.sleep(10);
      					} catch (InterruptedException e) {
      						e.printStackTrace();
      					}
      					System.out.println(Thread.currentThread().getName() + "还剩" + tickets-- + "张票");
      				} else {
      					break;
      				}
      
      			}
      
      		}
      	}
      }
      
    • 使用静态的对象
    • 使用字符串的常量
    • 使用class对象 : 在JVM中 , 每一个类的class对象总共只有一个
同步方法
  • 把synchronized放到方法的修饰符中 , 锁的是整个方法
  • 默认的锁对象
    • 成员方法 : this
    • 静态方法 : 类名.class
public class MyTicketsTest3 {
	public static void main(String[] args) {
		Object obj = new Object();

		MyTicket3 mt1 = new MyTicket3(obj);
		mt1.setName("迪丽热巴");

		MyTicket3 mt2 = new MyTicket3(obj);
		mt2.setName("古力娜扎");

		MyTicket3 mt3 = new MyTicket3(obj);
		mt3.setName("马尔扎哈");

		mt1.start();
		mt2.start();
		mt3.start();
	}
}

class MyTicket3 extends Thread {
	static int tickets = 100;
	private Object obj;

	public MyTicket3(Object obj) {
		this.obj = obj;
	}

	@Override
	public void run() {
		while (true) {
			synchronized (obj) {
				if (tickets > 0) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "还剩" + tickets-- + "张票");
				} else {
					break;
				}

			}

		}
	}
}

上述代码虽然解决了线程安全问题 , 但是变成了单线程的程序 , 原因是锁的范围太大了导致的 ; 在使用同步方法的时候也要注意这个问题

  • 单例设计 : 解决懒汉式的线程安全问题
public class SingInstanceDemo1 {
	public static void main(String[] args) {
		MyInstance ms1 = new MyInstance();
		MyInstance ms2 = new MyInstance();
		MyInstance ms3 = new MyInstance();
		
		ms1.start();
		ms2.start();
		ms3.start();
	}
	
}

class SingleInstance {
	
	private static SingleInstance single = null;
	
	private SingleInstance() {};
	
	public synchronized static SingleInstance getInstance(){
		if (single == null) {
			single = new SingleInstance();
		}
		
		return single;
		
	}
	
}

class MyInstance extends Thread{
	@Override
	public void run() {
		SingleInstance s1 = SingleInstance.getInstance();
		System.out.println(s1);
	}
}
Lock

JDK1.5产生的新特性 : ReentrantLock

  • lock : 释放锁的代码要放到finally代码块中 , 让他一定会执行到 , 否则可能会造成程序阻塞
  • unlock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyTicketsTest4 {
	public static void main(String[] args) {
		MyTickets4 mt4 = new MyTickets4();
		
		new Thread(mt4,"大白").start();
		new Thread(mt4,"小黑").start();
		new Thread(mt4,"黑白").start();
	}
}

class MyTickets4 implements Runnable{
	
	int tickets = 100;
	
	Lock lock = new ReentrantLock();;//也要保证该锁对象对于多个线程是同一个
	
	@Override
	public void run() {
		while (true) {
			
			lock.lock();//加锁
			
			try {
				if (tickets > 0) {
					System.out.println(Thread.currentThread().getName() + "还剩" + tickets-- + "张票");
				} else {
					break;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}finally {
				lock.unlock();//释放锁
			}
		}
	}
}
死锁
  • 死锁 : 是指两个或者两个以上的线程在执行的过程中 , 因争夺资源产生的一种互相等待的现象
public class DiedLock {
	public static void main(String[] args) {
		new MyThread(true).start();
		new MyThread(false).start();
	}
}

class Lock {
	public static Object lockA = new Object();
	public static Object lockB = new Object();
}

class MyThread extends Thread {
	private boolean flag;

	public MyThread(boolean flag) {
		this.flag = flag;
	}

	@Override
	public void run() {
		while (true) {
			if (flag) {
				synchronized (Lock.lockA) {
					System.out.println("lockA");
					synchronized (Lock.lockB) {
						System.out.println("lockB");
					}
				}
			} else {
				synchronized (Lock.lockB) {
					System.out.println("lockB");
					synchronized (Lock.lockA) {
						System.out.println("lockA");
					}
				}
			}
		}
	}
}
线程池
  • 为什么要是使用线程池 ?
    • 减少了创建和销毁线程的次数 , 每个工作线程都可以被重复利用 , 可执行多个任务
    • 可以根据系统的承受能力 , 调整线程池中工作线线程的数目 , 防止因为消耗过多的内存 , 而把服务器累趴下 ( 每个线程需要大约1MB内存 , 线程开的越多 , 消耗的内存也就越大 , 随后死机 )
pubic static ExecutorService newCachedThreadPool()				//创建一个具有缓存功能的线程池(60秒)(21亿)

public static ExecutorService newFixedThreadPool(int nThreads)			//创建一个可重用的,具有固定线程数的线程池

public static ExecutorService newSingleThreadExecutor()
	//创建一个只有单线程的线程池,相当于上个方法的参数是1
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo1 {
	public static void main(String[] args) {
		
		/*ExecutorService pool = Executors.newCachedThreadPool();
		pool.execute(new Runnable() {
			
			@Override
			public void run() {
				System.out.println("hhhhhhhh");
			}
		});*/

		ExecutorService ntp = Executors.newFixedThreadPool(3);

		ntp.execute(new Runnable() {

			@Override
			public void run() {
				try {
					Thread.sleep(5000);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("haha");
			}
		});

		ntp.execute(new Runnable() {

			@Override
			public void run() {
				try {
					Thread.sleep(5000);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("hehe");
			}
		});

		ntp.execute(new Runnable() {

			@Override
			public void run() {
				try {
					Thread.sleep(5000);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("heihei");
			}
		});

		ntp.execute(new Runnable() {

			@Override
			public void run() {
				System.out.println("hiahia");
			}
		});
	}
}
wait/notify/notifyAll
  • 得到锁和释放锁

    • 当一个线程进入了同步代码块 , 并开始执行了里面的代码 , 就叫做获得锁
    • 当他执行完毕后 , 退出代码块 , 允许其他程序进入同步代码块就叫做释放锁
  • wait , notify , notifyAll 这三个方法都是Object中的方法 , 并且这三个方法必须在同步方法或同步代码块中使用 ( 锁对象 )

    • wait : 让线程进入等待状态 , 进入等待状态的线程会释放锁对象 ( 也就是允许其他线程进入同步代码块 ) , 直到使用相同的锁对象调用notify/notifyAll方法才会结束阻塞状态
    • notify : 唤醒 , 会从等待池中唤醒一个处于等待状态的线程唤醒 , 使其进入可执行状态
    • notifyAll : 唤醒全部 , 会将等待池中所有处于等待状态的线程唤醒 , 使其进入可执行状态
  • notify或notifyAll以后 , 被唤醒的线程并不是立马执行 , 需要等到notify , notifyAll所在的代码块执行完毕后才会执行 , 因为只有同步代码块执行完毕后 , 才会释放锁对象 , 其他线程才可以进来

  • wait方法会释放锁对象 , 也就是一个线程使用wait进入等待状态后 , 允许其他线程进入同步代码块

    • sleep方法不会释放锁对象 , 到时间自己醒
  • wait , notify , notifyAll三个方法为什么位于Object类中 ?

    • 因为这三个方法必须使用锁对象调用 , 锁对象可以是任意对象 , 锁在Object类中
public class WaitNotifyDemo1 {
	public static void main(String[] args) {
		Object obj = new Object();
		
		new Thread(new WaitThread(obj)).start();
		new Thread(new WaitThread(obj)).start();
		new Thread(new WaitThread(obj)).start();
		new Thread(new WaitThread(obj)).start();
	}
}

class WaitThread extends Thread {
	private Object obj;

	int num = 20;

	public WaitThread(Object obj) {
		this.obj = obj;
	}

	@Override
	public void run() {
		synchronized (obj) {
			System.out.println(Thread.currentThread().getName() + "wait...start...");

			try {
				obj.wait();
			} catch (Exception e) {
				e.printStackTrace();
			}

			System.out.println(Thread.currentThread().getName() + "wait...end...");
		}
	}
}

class NotifyThread extends Thread {
	private Object obj;

	public NotifyThread() {
		this.obj = obj;
	}

	@Override
	public void run() {
		synchronized (obj) {

			System.out.println(Thread.currentThread().getName() + "notify...start...");

			obj.notifyAll();

			System.out.println(Thread.currentThread().getName() + "notify...end...");
		}
	}
}

在这里插入图片描述

定时器

框架 : framework

public Timer()													//得到Timer对象

public void schedule(TimerTask task, long delay) 				//延迟多少毫秒后执行定时任务

public void schedule(TimerTask task,Date date)					//指定时间执行定时任务

public void schedule(TimerTask task,long delay,long period) 	//延迟执行,指定间隔后循环执行

public void cancel()          									//取消定时任务
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class SocketDemo1 {
	public static void main(String[] args) throws Exception {
		Timer timer = new Timer();
		
		timer.schedule(new TimerTask() {
			
			@Override
			public void run() {
				System.out.println("该起床了");
			}
		}, 3000);
		
		String s = "2020-7-18 9:26:40";
		
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		
		Date d = sdf.parse(s);
		
		timer.schedule(new TimerTask() {
			
			@Override
			public void run() {
				System.out.println("早上八点必须起床");
				timer.cancel();
			}
		}, d);
		
		timer.schedule(new TimerTask() {
			
			@Override
			public void run() {
				System.out.println("C4 5秒后循环爆炸");
			}
		}, 5000,2000);
	}
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值