JavaDay24 多线程与多进程


tags:

  • 进程
  • 线程

JavaDay24 多线程与多进程

@toc

代码示例:

package DemoDay24;

import org.junit.jupiter.api.Test;

/**使用线程实现同时视频和语音
 * @author GJXAIOU
 * @create 2019-07-24-20:54
 */

class VideoThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("视频中。。。。。");
        }
       
    }
}
class AudioThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("语音中。。。。。");
        }
        
    }
}

public class Demo1 {
    
    //方法一:使用main函数进行调试
//    public static void main(String[] args) {
//        VideoThread videoThread = new VideoThread();
//        AudioThread audioThread = new AudioThread();
//
//        videoThread.start();
//        audioThread.start();
//    }

    //方法二:使用JUnit中@test进行调试
    @Test
    public void test(){
        VideoThread videoThread = new VideoThread();
        AudioThread audioThread = new AudioThread();

        videoThread.start();
        audioThread.start();
    }

}

程序运行结果:
每次运行结果会不同,进程会别抢占;

语音中。。。。。
语音中。。。。。
语音中。。。。。
语音中。。。。。
语音中。。。。。
视频中。。。。。
视频中。。。。。
视频中。。。。。
视频中。。。。。
视频中。。。。。

一、线程中的常用方法

方法名含义说明
Thread(String name);初始化线程的名字属于线程的一个有参数的构造方法
setName(String name);修改线程的名字
getName();获取线程的名字
sleep();static静态方法,通过Thread类名调用,这里需要处理一些异常,要求当前线程睡觉多少毫秒;【哪一个线程执行了sleep方法,哪一个线程就睡觉】。
currentThead();static静态方法,返回当前的线程对象;【哪一个线程执行了currentThread方法,就返回哪一个线程对象】。
getPriority();返回当前线程的优先级CPU执行的优先级,不是绝对的,仅仅是提升概率。
setPriority(int newPriority);设置线程的优先级。
  • 【注意】
    线程的优先级范围是从1 ~ 10, 10最高,1最低
    这里的优先级只是提高了当前线程拥有CPU执行权的概率,并不能完全保证当前线程能够一定会占用更多的CPU时间片。线程的默认优先级为5。

    Thread[main,5,main]
    Thread[Thread-0,5,main]
    Thread[线程名, 优先级, 线程组名]

  • 线程中常见方法的测试:
    run()方法中不能抛出异常,只能使用 try-catch

package DemoDay24;

/**
 * @author GJXAIOU
 * @create 2019-07-24-21:23
 */
public class Demo2 extends Thread {

    public Demo2(String name) {
        super(name);//调用父类Thread的有参构造方法
    }

    
    @Override
    public void run() {
        //这里是Demo2线程对象的线程代码
        System.out.println("28:" + Thread.currentThread());

        for (int i = 0; i < 5; i++) {
            System.out.println("自定义线程");
		/*
		 在其他方法中, 使用sleep方法,可以抛出,可以捕获,
		 但是在run方法为什么只有捕获没有抛出?因为这是一个语法规则:
		 在Java中,重写父类的方法,要求和父类的方法声明一模一样,
		 在Thread类中,run方法没有抛出异常,所以在子类中,你也不能抛出异常,要和父类一致
		 */
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //这里是main线程
        Demo2 d = new Demo2("狗蛋");

        d.setName("狗娃");
        d.setPriority(10);
        d.start(); //开启自定义线程,执行自定义线程中的run方法里面的功能
        System.out.println("39:" + Thread.currentThread());

        for (int i = 0; i < 5; i++) {
            System.out.println("这里是main线程");
            sleep(100);
        }
    }
}


程序运行结果:

39:Thread[main,5,main]
这里是main线程
28:Thread[狗娃,10,main]
自定义线程
这里是main线程
自定义线程
自定义线程
这里是main线程
这里是main线程
自定义线程
自定义线程
这里是main线程

下面的 InterfaceA 和 testA 是为了测试什么时候是不能使用抛出异常,只能使用 try-catch

package com.qfedu.a_thread;

interface A {
	public void testA();
}

public class Demo2 extends Thread implements A{
	public Demo2(String name) {
		super(name);//调用父类Thread的有参构造方法
	}
	
	@Override
	public void testA() {
		//这里也无法抛出异常,两种处理方法,第一种,捕获异常,
		//第二种,在接口中声明方法部分,声明该异常
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
	}
//--------------------------------------------------	
//下面代码省略
}


二、线程的生命周期

[外链图片转存失败(img-j6ZAToze-1568986092781)($resource/%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.png)]


三、线程的共享资源问题

以动物园买票为示例:
一共有 50张票 3个窗口同时售卖
这里隐含多线程,这里可以把3个窗口看做是3个线程

  • 第一次问题:
    发现票每一张都被买了三次

    • 原因:
      因为Ticket是在每一个线程中run方法里面的一个局部变量,这个局部变量是每一个线程对象都拥有的,这里Ticket就是不在是一个共享资源
    • 处理方式:
      把Ticket变成成员变量
  • 第二次问题:
    发现貌似每一张票还都是卖了50次,而且这里还优化了售卖的算法

    • 原因:
      这里Ticket变成了一个成员变量,在每一个线程对象中,都拥有这个Ticket成员变量,每一个成员变量是一个独立的个体,不是共享资源
    • 处理方式:
      用static修饰ticket成员变量,变成一个存放在数据共享区的一个静态成员变量
  • 第三次问题:
    发现会出现几张票是买了多次的

    • 原因:
      因为窗口1在卖票的时候,还没有运行到ticket–这条语句的时候,下一个窗口2开始执行卖票算法
      这里窗口2卖的票是窗口1还没有ticket–的票
      [外链图片转存失败(img-ArlJjEsh-1568986092782)($resource/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98.png)]
      处理方式:
      上锁,锁门
  • Java中的线程同步机制
    方式1:
    同步代码块:

synchronized (锁对象) {
  //需要同步的代码;
}

同步代码块的注意事项:

  1. 锁对象,可以是任意的一个对象, 但是必须是同一个对象!!!不能在这里使用new 来创建匿名对象
  2. sleep() 不会释放锁对象,不会开锁。例如: 厕所有人关门睡着了
  3. 使用synchronized 同步代码块的时候,必须是真正意义上存在共享资源的线程问题,才会使用
    而且通常情况下,用synchronized锁住的代码越少越好,提高代码执行效率
package com.qfedu.a_thread;


class SaleTicket extends Thread {
  private static int ticket = 50;

	public SaleTicket(String name) {
		super(name);
	}

	@Override
	public void run() {

		while (true) {
			synchronized ("你好") { 
			//可以使用"你好 "的任何确定的对象 
			//不能使用new Demo3()创建不同对象,即对应不同的锁 
				if (ticket > 0) {
					System.out.println(Thread.currentThread().getName()+
					":卖出来第" + ticket+ "张票");
					try {
						sleep(500); //睡眠也不会释放锁对象
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} else {
					System.out.println("卖完了");
					break;
				}

				ticket--;
			}
		}
	}
}

public class Demo3 {
	public static void main(String[] args) {
		SaleTicket s1 = new SaleTicket("窗口1");
		SaleTicket s2 = new SaleTicket("窗口2");
		SaleTicket s3 = new SaleTicket("窗口3");

		s2.start();
		s1.start();
		s3.start();

	}
}

程序运行结果:

窗口1:卖出来第50张票
窗口1:卖出来第49张票
。。。。。
窗口3:卖出来第28张票
窗口3:卖出来第27张票
窗口3:卖出来第26张票
窗口3:卖出来第2张票
窗口3:卖出来第1张票
卖完了
卖完了
卖完了

(一)死锁

  • 出现死锁的原因:
    1.存在两个或两个以上的共享资源
    2.存在两个或者两个以上的线程使用这些共享资源
    下面的代码会出现死锁;
package com.qfedu.a_thread;
class DeadLock extends Thread {
	public DeadLock(String name) {
		super(name);
	}
	
	@Override
	public void run() {
		if (Thread.currentThread().getName().equals("小胖")) {
			synchronized ("电池") {
				System.out.println("小胖有电池,想要遥控器");
				
				synchronized ("遥控器") {
					System.out.println("小胖拿到了遥控器,打开了投影仪");
				}
			}
		} else if (Thread.currentThread().getName().equals("逗比")) {
			synchronized ("遥控器") {
				System.out.println("逗比有遥控器,想要电池");
				
				synchronized ("电池") {
					System.out.println("逗比拿到了电池,打开了投影仪");
				}
			}
		}		
	}
}

public class Demo4 {
	public static void main(String[] args) {
		DeadLock d1 = new DeadLock("小胖");
		DeadLock d2 = new DeadLock("逗比");
		
		d1.start();
		d2.start();		
	}
}

输出结果:

小胖有电池,想要遥控器
逗比有遥控器,想要电池

(二)自定义线程

Java语言是一种单继承,多实现【遵从】面向对象的语言

  • 自定义线程的方式:
    • 方式1:
      1.自定义一个类,继承Thread类
      2.重写Thread里面的run方法,把线程的功能代码放入到run方法中
      3.创建自定义线程类对象
      4.开启线程,使用start方法

      • 弊端:
        因为Java是一个单继承的语言,一旦某一个类继承了Thread类,就无法再继承其他类,或者说一个类继承了其他类,也就没有办法继承Thread类
    • 方式2: 强烈推荐
      【遵从】Runnable接口实现自定线程类
      1.自定义一个类,【遵从】Runnable接口
      2.实现Runnable接口中唯一要求的方法 Run方法,把线程的功能代码写入到run方法中
      3.创建Thread类对象,并且把【遵从】Runnable接口的自定义类对象,作为参数传入到Thread构造方法中
      4.调用Thread类对象的start方法,开启线程

package com.qfedu.a_thread;
import java.util.Arrays;
import java.util.Comparator;

class TestRunnable implements Runnable {

	//实现自定义线程类,遵从Runnable接口要求实现的Run方法,把线程代码写入到Runnable里面
	@Override
	public void run() {
		for (int i = 0 ; i < 10; i++) {
			System.out.println("当前线程为:" + Thread.currentThread());
		}
	}	
}

public class Demo5 {
	public static <T> void main(String[] args) {
		//创建Thread类对象,调用Thread构造方法中,需要传入Runnable接口实现类对象的方法~
		//方法一:
		Thread t1 = new Thread(new TestRunnable());//匿名对象

		//方法二:
		Thread t2 = new Thread(new Runnable() { //匿名内部类的匿名对象,不再需要定义上面的TestRunnable类 
			
			@Override
			public void run() {
				for (int i = 0; i < 10; i++) {
					System.out.println("匿名内部类的匿名对象,作为方法的参数,这里是作为线程对象的参数" + 
							Thread.currentThread());
				}
			}
		});
		
		t1.start();
		t2.start();
		
		/*
		target是在创建Thread类对象时候,传入的【遵从】Runnable接口的实现类,这个实现类中
		实现类【遵从】Runnable接口要求实现的run方法,在run方法中,就是定义的线程代码
		在Thread类中有一个成员变量
		//What will be run
		private Runnable target; 
		
		@Override
		public void run() {
			if (target != null) {
				target.run();
			}
		}
		*/
	}
}

三、守护线程(后台线程)

例如:
软件的Log日志文件,软件的自动更新,软件的自动下载

特征:
如果整个程序再运行过程中,只剩下一个守护线程,那么这个守护线程也就没有意义了,会自动停止

JVM的垃圾回收机制是守护线程。

这里当主线程停止,则下载也会自动停止;

package com.qfedu.a_thread;

public class Demo6 extends Thread {
	
	public Demo6(String name) {
		super(name);
	}
	
	//模拟后台下载更新的线程
	@Override
	public void run() {
		for (int i = 0; i <= 100; i++) {
			System.out.println("软件更新下载中………………" + i + "%");
			
			if (i == 100) {
				System.out.println("软件更新下载完成,是否安装~~");
			}
			
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Demo6 d = new Demo6("后台线程");
		
		//设置当前线程为守护线程或者是后台线程
		d.setDaemon(true);//true为守护线程
		
		//System.out.println(d.isDaemon());
		d.start();
		for (int i = 0; i <= 50; i++) {
			Thread.sleep(10);
			System.out.println("主线程:" + i);
		}
	}
}

四、线程通讯

  • 线程通讯:
    一个线程完成任务之后,通知另一个线程来完成该线程应该执行的任务

生产者和消费者问题:这里商品是两个线程直接的共享资源

wait(); 等待,如果一个线程执行了wait方法,那么这个线程就会进入临时阻塞状态,等待唤醒,这个唤醒必须其他线程调用notify() 方法唤醒;
notify(); 唤醒,唤醒线程池中进入【临时阻塞状态】的一个线程

  • 注意事项:
  1. wait()和notify()这两个方法都是Object类的方法
  2. 在消费者生产者模式下,锁对象只能是商品;
package com.qfedu.a_thread;

//两者之间的共享资源
class Product {
	String name; //商品的名字
	int price; //价格
	
	boolean flag = false; //产品是否生产成功,如果成功flag设置为true,消费者购买之后,设置为false
}

class Producer extends Thread {
	Product p;  //商品的类对象,是和消费者之间的共享资源
	
	public Producer(Product p) {
		this.p = p;
	}
	
	@Override
	public void run() {
		int count = 0;
		while (true) {
			synchronized (p) {
				try {
					sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				if (!p.flag) { //p.flag == false
					
					//商品不存在,生产过程
					if (count % 2 == 0) {
						p.name = "红辣椒擀面皮";
						p.price = 5;
					} else {
						p.name = "唐风阁肉夹馍";
						p.price = 10;
					}
					
					count++;
					System.out.println("生产者生产了:" + p.name + ":" + p.price);
					p.flag = true;
					//生产结束,唤醒消费者
					p.notify();
				} else {
					//商品存在,要求消费者来购买,生产者进入临时阻塞
					try {
						p.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} // try - catch
				}// if - else
			} // 同步代码块
		}// while (true)
	} //run()
}

class Customer extends Thread {
	Product p; //商品类对象,是和生产者之间的共享资源
	
	public Customer(Product p) {
		this.p = p;
	}
	
	@Override
	public void run() {
		while (true) {
			synchronized (p) {
				try {
					sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				if (p.flag) { //p.flag == true 
					//这里表示商品存在
					System.out.println("消费者购买了:" + p.name + ":" + p.price);
					
					p.flag = false; //表示消费者购买完毕,要求生产者生产
					//需要唤醒生产者
					p.notify(); //打开线程锁
				} else {
					try {
						//商品不存在,消费者进入临时阻塞
						p.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} // try - catch
				}// if - else 
			} //同步代码块
		} // while (true)
	} //run()
}

public class Demo7 {
	public static void main(String[] args) {
		Product product = new Product();
		
		Producer p = new Producer(product);
		Customer c = new Customer(product);
		
		p.start();
		c.start();
	}
}

[外链图片转存失败(img-c1SINa4C-1568986092783)($resource/%E5%8F%AF%E9%81%87%E4%B8%8D%E5%8F%AF%E6%B1%82%E7%9A%84%E9%94%99%E8%AF%AF.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值