JAVA 基础语法备忘录 - 多线程

  • 简介概念
  • 线程的实现(重点)
  • 线程状态
  • 线程同步(重点)
  • 线程通信
  • 高级相关


线程

例如: 


一、线程是什么?

 电脑有多个应用,可以看成有多个进程,进程可以看做事程序(指令+数据)的一次执行过程(程序进入内存,通过CPU处理)。每个进程又有多个线程(进程的一个个任务,由CPU调度和执行)。main函数也是一个主线程

二、线程的使用

1.线程的实现

Thread     Runnable       Callable

线程基本是通过继承 Thread  类  和  实现 Runnable 接口 Callable(了解即可)

  • 继承 Thread 实现多线程:

代码如下(示例):

package priv.practice.thread;

public class CreateThread extends Thread{
//1.继承 Thread 类  2.重写run() 方法  3.调用 start() 方法
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<5;i++){
			System.out.println(" extends Thread get multiThread!");
		}

	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		CreateThread createThread=new CreateThread();
		createThread.start();//打印5次
		for(int i=0;i<500;i++){//主线程打印500次
			System.out.println(" main method!"+i);
		}
		
	}

}

运行结果如下: 

extends Thread get multiThread!
 extends Thread get multiThread!
 main method!0
 extends Thread get multiThread!
 extends Thread get multiThread!
 extends Thread get multiThread!
 main method!1
 main method!2
 main method!3   

......

显然,两个线程是交替执行的。


  •  通过 实现 Runnable 接口,重写 run方法,再使用new Thread(MyThread).run() 实现多线程,

class MyThread implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		
		for(int i=0;i<900;i++){
			System.out.println(i+"  Runnable interface!");
		}
	}
	
}

在main方法中进行如下应用:


		// TODO Auto-generated method stub
		Thread thread = new Thread(new MyThread());
		thread.start();
		for(int i=0;i<900;i++){
			System.out.println(i+"  main thread !");
		}
	

 可见,MyThread 类 在基础 接口后,若想启动 start方法,要把自己new出来的对象作为参数传到Thread类中(这不是装饰器模式,实际上是代理模式),再经过Thread启动run方法。

   


public class TicketsSale implements Runnable{
	private int ticketsNum = 10;
	

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		TicketsSale  tickets = new TicketsSale();
		new Thread( tickets,"tom").start();
		new Thread( tickets,"jack").start();
		new Thread( tickets,"ds").start();
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			if(ticketsNum<=0){
				break;
			}
			System.out.println(Thread.currentThread().getName()+"  -->拿到了第"+(ticketsNum--)+"张票被获取");
		  //延时
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}

}

会出现多个人拿到同一张票。  

并发 (complicated) e.g. 火车票出售

  • Callable 接口 (了解)

实现 Callable接口,实现其中的 call();方法,该方法有返回值。需要抛出异常。该种方式需要开启与 关闭服务,

2.静态代理模式

真实对象与代理对象 都要实现同一个接口,代理对象要代理真实的角色,就要将真实对象作为构造函数的参数传入,之后代理对象就可以调用原对象的方法,也可以独立做一些其他的额外的事情,做真实对象做不了的事情。

package priv.pattern.proxy;

//代理模式-静态代理
interface Marry {
	void HappyMarry();
}


class Person implements Marry {

	@Override
	public void HappyMarry() {
		// TODO Auto-generated method stub
		System.out.println("this person is happy!");
	}
} 

class WeddingCompany implements Marry {
//WeddingCompany 婚庆公司
	//婚庆公司 构造方法传入 Marry 类型对象,返回该类型对象,写入私有Marry
	private Marry targetMarry;
	public   WeddingCompany (Marry targetMarry){
		this.targetMarry=targetMarry;
	}
	@Override
	public void HappyMarry() {
		before();
		// 调用 Person(Marry)类中的方法
		this.targetMarry.HappyMarry();
		after();
	}
	
	private void after() {
		System.out.println("after marry mad!");
	}
	private void before(){
		System.out.println("before marry happy!");
	}
	
}

public class StaticProxy {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		WeddingCompany company = new WeddingCompany(new Person());
		company.HappyMarry();
	}

}

通过多线程技术进行改造,如下:

new Thread(new Runnable(){

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("replace by lamada");
            }
            
        }).start(); 
        --------------------------------------------------------------------------
        WeddingCompany company = new WeddingCompany(new Person());
        company.HappyMarry();

显然,线程的实现就是使用了静态代理方式,Thread是代理类,Runnable是真实类(接口),代理类通过重写了真实接口的方法才可以启动。  

  • lamda表达式

实现lamda表达式要满足接口只有一个而且接口只能有一个方法,参考静态内部类-->局部内部类 -->匿名内部类  的演化,实现lamda表达式。(Runnable接口内部仅有一个run方法,它就是函数式接口,在多线程中实现 lamda表达式非常重要且常用)。

new Thread(new Runnable(){

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("replace by lamada");
            }
            
        }).start(); 

//lamda  style

new Thread( () ->System.out.println("replace by lamada");).start(); 

3. 线程的状态 

 sleep(每个对象都有一把锁,sleep不会释放锁)

join(插队)

yield(不一定成功) wait  notify  

线程状态转换示例图

     


线程同步(重点)

 多个线程操作同一个资源(并发),既多个线程操作同一个资源。(每个对象都有一把锁,sleep不会释放锁)

通常使用线程队列(排队)+锁 的方式实现 同步(synchronized) 解决线程不安全的问题.。

但是这种方式会带来性能问题,多线程竞争会导致较多的 上下文切换 与 调度延时,优先级高的线程等待优先级低的线程也会引起优先级倒置,导致性能问题。

........................

int ticketsNum = 10;
    Object lock = new Object();
    
    @Override
    public   void run() {
        while(true){
            
            //延时
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            synchronized (lock) {

................

以上代码是售票例子中关于同步的应用,显然,synchronized 修饰的代码块就是要同步的部分,同时在()中的lock对象就是同步监视器。它的作用就是当线程访问synchronized修饰的代码块时,如果发现lock(同步监视器)已锁定,就等待,反之,即可访问该代码块。

写这段代码块时,开始将票的总数(tickets)写到了run()方法中,这样新建一个线程,该线程获取同步代码块运行,tickets 的原始值 没开启一个新线程就更新一次。synchronized修饰方法此处参考 多线程懒汉式单例模式(以后填坑....)。

  • 死锁

死锁就是同步代码块和同步方法的嵌套。双方争夺对方持有的资源(互斥使用)。

死锁产生应满足4个条件:  

  1. 资源互斥使用
  2. 不可抢占资源
  3. 占有且等待
  4. 循环等待

为避免死锁,破坏以上2.3.4条件均可破坏以避免死锁(参考银行间算法)。

main 方法 .......
		// 首先建立两个资源 A  B 
       Object A = new Object();
       Object B = new Object();
       //thread 01  先拿 A资源 再拿B资源
       new 	Thread(()->{
    	   synchronized (A) {
			  System.out.println("thread 01 get A! ");
			  
			  try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			  //thread 01 get B
			  synchronized (B) {
				  System.out.println("thread 01 get B! ");
			}
		}
       }
    		   ).start();
       
       
       //thread 02  先拿 B资源 再拿A资源
       new 	Thread(()->{
    	   synchronized (B) {
			  System.out.println("thread 01 get B! ");
			  
			  try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			  //thread 01 get B
			  synchronized (A) {
				  System.out.println("thread 01 get A! ");
			}
		}
       }
    		   ).start();
		
	...

  • 多线程通信

多个线程运行,任务不同,处理的资源一样。(与买票的多线程例子相比 ,多个线程任务相同(买票),处理的资源(车票)也相同)。 多线程通信实现对资源的处理,可以理解为一个线程进行输入,另一个线程进行输出。为了保证二者资源的一致性,资源类在外部建立后,可以通过使用构造方法传参的方式传入 输入/输出 线程中,线程再通过内部的run方法改变资源。为了线程安全问题,需要在输入输出的线程处理资源的代码块外都增加同步方法(synchronized)。需要注意的是由于输入输出的线程不是同一个线程,锁需要将 object 替换为  Resourse.class 或者 Resourse r 中 的 r。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值