多线程
守护线程
-
守护线程 又叫兜底线程
-
每个程序运行当中,都会默认开启一个守护线程,用于监听我们正常的程序
-
简单来说,就是没有任何一个线程的时候,JVM就需要退出了,这个时候守护线程也会退出,主要完成垃圾回收等功能
-
但是 我们可以使用Thread.setDameon() 方法 把某个线程设置为守护线程
-
但是必须在启动 start之前,否则报错
下面是如何使用:
public class Thread01daemon {
public static void main(String[] args) {
//创建线程
//用的Thread 引用=new 继承Thread的类的构造方法
//父类引用指向子类对象,多态
Thread t1=new Processor01();
Thread t2=new Processor01();
//设置线程t1和t2的名字
t1.setName("t1");
t2.setName("t2");
//设置t1为守护线程
//没有其他线程在运行后,守护线程就会退出
t1.setDaemon(true);
//启动t1和t2线程
t1.start();
t2.start();
for (int i = 0; i <10; i++) {
System.out.println("main--->"+i);
try {
//线程休眠200毫秒
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//继承Thread类的子类
class Processor01 extends Thread{
//覆写run方法
public void run(){
int i=0;
//死循环
while(true){
//getName是Thread类里的静态方法
//但由于继承的缘故我们可以直接使用
System.out.println(getName()+"="+i++);
try{
//线程休眠半秒
Thread.sleep(500);
}catch(Exception e){
e.printStackTrace();
}
}
}
}
Timer
-
定时器 计划任务,只要有一个任务监听 就会是一个线程
-
1 执行任务的类 , 2 执行任务起始时间 3 执行任务间隔时间
下面是如何使用:
public class Thread02Timer {
//定时器,计划任务,只要有一个任务监听,就会是一个线程
//1 执行任务的类 2执行任务起始时间 3执行任务间隔时间
public static void main(String[] args) throws ParseException {
//1 创建定时器
Timer t=new Timer();
//执行任务对象
LogTimerTask logTimerTask=new LogTimerTask();
//指定起始时间
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
//1 任务对象 2起始时间 3 间隔时间(毫秒)
//这个schedule方法自动调用run方法输出时间
t.schedule(logTimerTask, sdf.parse("2021-01-30 20:53:00 000"), 1000*3);
}
}
//任务类
class LogTimerTask extends TimerTask{
@Override
public void run() {
//日期格式化对象,yyyy是年,MM是月,dd是日,HH是时,mm是分,ss是秒,SSS是毫秒
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
//获取当前系统时间
Date date=new Date();
//用日期格式化对象格式化输出当前系统时间
System.out.println(sdf.format(date));
}
}
锁
-
如果访问了一个对象中加锁的成员方法,那么该对象中所有的加锁的成员方法全部锁定,都不能被其他线程访问
-
但是和静态无关,和其他对象也无关
-
如果访问了一个类加锁的静态方法,那么该类中所有的加锁的静态方法都被锁定,不能访问
-
但是和成员无关
下面是如何使用:
public class Thread03synchronized {
public static void main(String[] args) {
//创建Myclass01类的对象my
Myclass01 my=new Myclass01();
//创建线程对象的方法
//Thread 对象名=new Thread有参的构造方法,构造方法参数传入一个实现了Runnable接口的类的对象
//创建线程t1、t2、t3
Thread t1=new Thread(new Processor03(my));
Thread t2=new Thread(new Processor03(my));
Thread t3=new Thread(new Processor03(my));
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
//t1线程启动
t1.start();
//睡眠半秒,保证t1先执行,先进入加锁的m1方法,并睡眠5秒
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
// 因为t1进入m1后就加锁,并睡眠,
//所以在t1线程睡眠时t2线程无法调用加锁的m2方法,
//因为访问同一个对象的加锁成员方法,该对象中所有的加锁成员方法都不能被访问
//m3方法没有加锁,所以可以正常执行
t2.start();
t3.start();
}
}
//实现Runnable接口的类
class Processor03 implements Runnable{
//类中有一个成员变量
//Myclass01类型的my对象
Myclass01 my;
//类里有参的构造方法,传入Myclass01类的对象
public Processor03(Myclass01 my){
super();
//创建对象的my是传入的my
this.my=my;
}
//实现Runnable类,覆写run方法
public void run(){
//如果当前启动线程的名字是t1
//那么t1的my调用m1方法
if("t1".equals(Thread.currentThread().getName())){
my.m1();
}else if("t2".equals(Thread.currentThread().getName())){
my.m2();
}else if("t3".equals(Thread.currentThread().getName())){
my.m3();
}
}
}
class Myclass01 {
//加锁的成员方法
public synchronized void m1() {
System.out.println("加锁成员方法m1,即将进入睡眠");
//线程睡眠5秒
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void m2() {
System.out.println("加锁成员方法m2");
}
public void m3() {
System.out.println("未加锁成员方法m3");
}
}
死锁
-
死锁 : 就是大家在执行过程中,都遇到对方进入加锁方法中,导致大家都访问不了
-
原理 :
-
1 某个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象1
-
2 另一个线程的执行完成,需要先后,嵌套,锁定执行两个对象,并且先执行对象2
-
3 在第一个线程中,要去访问对象2的时候,发现被锁定 了,只能等待
-
在第二个线程中,要去访问对象1的时候,发现被锁定了,只能等待
synchronized(xxx){}代码块锁,可以锁类,也可以锁对象
如果锁对象,当访问该代码块锁的时候,该对象中所有的代码块锁和加锁的成员方法都被锁定
同理访问对象中加锁的成员方法的时候,代码块锁也会被锁定如果是锁类,当访问该代码块的时候,该类中所有的代码块锁和加锁的静态方法都被锁定
同理访问类中加锁的静态方法的时候,代码块锁也会被锁定死锁实现思路 :
-
1 两个线程
-
2 两个对象
-
3 两个线程中 保存的两个对象是相同的
-
4 线程1 先访问对象1 再嵌套访问对象2
-
5 线程2 先访问对象2 再嵌套访问对象1
两个线程相当于两辆车,两个对象相当于两条路,路中间有个栅栏,栅栏前面可以拐弯,
两辆车都想往对方道路拐,但是由于加锁,相当于有红灯,不让拐
下面是代码实现:
public static void main(String[] args) {
//创建两个Object类对象
Object o1=new Object();
Object o2=new Object();
//创建两个线程
//创建线程的方法用的是
//Thread 线程名=new Thread类有参的构造方法
//传入一个实现
Thread t1=new T1(o1,o2);
Thread t2=new T2(o1,o2);
t1.start();
t2.start();
}
}
//继承Thread类,覆写run方法
class T1 extends Thread{
Object o1;
Object o2;
public T1(Object o1,Object o2){
super();
this.o1=o1;
this.o2=o2;
}
public void run(){
synchronized(o1){
try {
//加入睡眠,保证一定会死锁,因为要确保执行到这里时t2把o2锁定了
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
synchronized(o2){
System.out.println("T1执行完成");
}
}
}
}
class T2 extends Thread{
Object o1;
Object o2;
public T2(Object o1,Object o2){
super();
this.o1=o1;
this.o2=o2;
}
public void run(){
synchronized(o2){
try{
//需要加入睡眠,保证一定会死锁,因为执行到这里,需要t1先把o1给锁定
Thread.sleep(10);
}catch(Exception e){
e.printStackTrace();
}
synchronized (o1) {
System.out.println("T2执行完成");
}
}
}
}
线程通信
wait() 与 notify() 和 notifyAll()
a)wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
b)notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
c)notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
注意:这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报
java.lang.IllegalMonitorStateException异常
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声
-
Object 中的方法
-
wait() : 该线程进入等待状态,功能还在,只是没有运行,当被唤醒之后进入就绪状态,当执行的时候接着当前等待的状态继续执行
-
notify() : 唤醒该对象上等待中的某一个线程(优先级)
-
notifyAll() : 唤醒该对象上所有等待的线程
-
必须用在成员方法中,因为是对象相关,并且该成员方法必须加锁(synchronized)
-
wait : 无参 或者是传入 0 ,说明不会自动醒,只能被notifyAll/notify唤醒
-
也可以传入long类型的值,单位是毫秒数,过了指定时间之后自动醒
-
注意 sleep是Thread类中的静态方法,睡眠指定时间,不会交出锁(其他线程依旧不能交出该方法)
-
而 wait是Object中的成员方法,也就是每个对象都有的方法,挂起,会交出锁(其他线程就可以访问该方法了)
下面是代码实现:
public class Thread05Wait {
public static void main(String[] args) {
//创建Num类的对象
Num num=new Num(1);
//创建线程t1和t2
//用Thread 线程引用=new 继承Thread子类的构造方法创建线程
Thread t1=new PrintOdd(num);
//用Thread 线程引用=new Thread有参的构造方法,传入参数为实现Runnable接口的类的对象
Thread t2=new Thread(new PrintEven(num));
//设置t1和t2线程的名字
t1.setName("t1");
t2.setName("t2");
//t1和t2线程启动
t1.start();
t2.start();
}
}
//打印奇数
class PrintOdd extends Thread{
//Num类型的引用
Num num;
//打印奇数
//构造方法,传入一个Num类型的引用
public PrintOdd(Num num){
super();
//构造方法初始化类的成员属性
this.num=num;
}
public void run(){
while(true){
//用num对象去调用Num类的成员方法
num.printOdd();
}
}
}
//打印偶数
class PrintEven implements Runnable{
Num num;
//打印偶数
public PrintEven(Num num){
super();
//将调用这个方法的对象的num成员属性赋值为num
this.num=num;
}
@Override
public void run() {
while(true){
//启动线程就会通过num对象调用Num类的打印偶数的成员方法
num.printEven();
}
}
}
//业务类
class Num{
int count;
public Num(int count){
super();
this.count=count;
}
//加锁的成员方法,如果在访问一个加锁的成员方法时,那么该类中所有其他加锁的成员方法都不能用
public synchronized void printOdd(){
System.out.println(Thread.currentThread().getName()+":"+count);
count++;
//自己唤醒其他线程
this.notifyAll();
try {
//当前线程睡眠半秒,使效果直观
Thread.sleep(500);
//自己进入挂起等待
this.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void printEven(){
System.out.println(Thread.currentThread().getName()+":"+count);
count++;
// 唤醒其他线程
this.notifyAll();
// 自己进入挂起等待
try {
// 加入睡眠,效果更直观
Thread.sleep(500);
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
生产者和消费者问题
下面是代码实现:
public class Thread06producerconsumer {
public static void main(String[] args) {
//创建业务类的对象s
SynStack s=new SynStack();
//创建消费者线程,传入消费者类的对象
Thread t1=new Thread(new Consumer(s));
//创建生产者线程,传入生产者类的对象
Thread t2=new Thread(new Producer(s));
//启动线程
t1.start();
t2.start();
}
}
//实现Runnable接口的消费线程类
class Consumer implements Runnable{
//SynStack类型的引用s
SynStack s;
//构造方法初始化成员变量s
public Consumer(SynStack s){
super();
this.s=s;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
//将产品弹栈,进行消费
s.pop();
//线程睡眠半秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//生产线程类
class Producer implements Runnable{
SynStack s;
public Producer(SynStack s) {
super();
this.s = s;
}
@Override
public void run() {
char ch;
//生成产品
for (int i = 0; i < 20; i++) {
ch = (char)('a'+i);
//将产品压栈
s.push(ch);
}
}
}
//业务类
class SynStack{
// 保存以生产的个数.同时也是下一个元素的下标
int cnt = 0;
char[] data = new char[6];
//加锁保证生产产品的同时不能消费产品
//访问一个类的加锁的成员方法时,其他加锁的成员方法也会被锁定
public synchronized void push(char ch){
// 生产个数如果和容器大小一致,说明生产满了,就不再生产
//挂起生产者线程
if (cnt == data.length) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 先唤醒消费者 提醒消费
notifyAll();
// 把元素保存到数组中
data[cnt] = ch;
// 个数+1
cnt++;
System.out.println("生产了 : "+ch+" , 容器中剩余 "+cnt+" 个字符");
}
public synchronized char pop() {
// 生产个数如果是0 就不进行消费
//挂起消费者线程
if (cnt == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 先唤醒生产者提醒生产
notifyAll();
// 先 -- 再取出,因为最后一个元素下标为个数-1 , 而 cnt是个数,所以要先--
cnt--;
char ch = data[cnt];
System.out.println("消费了 : " + ch + " , 容器中剩余 " + cnt + " 个字符");
return ch;
}
}
线程池
线程池的作用 :
-
线程池作用就是限制系统中执行线程的数量
-
根据系统的环境情况,可以自动或者手动来设置线程数量,以达到运行的最佳效果
-
少了浪费系统资源,多了造成系统拥挤效率不高
-
用线程池控制线程数量,其他线程排队等候
-
一个任务 执行完成,再从队列中取最前面的任务开始执行
-
如果队列中没有等待进程,线程池的这个资源处于等待状态
-
当一个新任务需要运行时,如果此时线程池中还有等待中的工作线程时,可以直接开始运行
-
否则需要进入等待队列
-
为什么要使用线程池
-
1 减少了创建 和销毁线程的次数,因为每个工作线程都可以被重复使用,可执行多个任务
-
2 可以根据系统的承受能力,调整线程池中的线程数量,防止因为消耗过多的内存,导致服务器死机
-
(每个线程需要大概1MB内存,线程开的越多,消耗内存越大,最后导致死机)
下面是代码实现:
public class Newcachedthreadpooltest {
public static void main(String[] args) {
// 创建一个可缓存线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程
// 若没有可以回收的,则新建线程,线程池规模不存在限制,数量不固定
// 创建池子
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//线程池启动10个线程
for (int i = 0; i < 10; i++) {
// 执行任务,传入了Runnable的匿名内部类,也可以是实现类对象
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
//线程睡眠1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//输出当前线程的名字
System.out.println(Thread.currentThread().getName());
}
});
}
System.out.println("----------");
// 关闭线程池
cachedThreadPool.shutdown();
}
}