简单的线程总结

线程

创建线程的方式
  1. 继承Thread类,重写run方法

    public class demo extends Thread{
          
          @Override
    	public void run() {
          
          
    			//写线程操作的代码
    	}
    }
    
    //匿名方式创建
    new Thread() {
          
          
    			@Override
    			public void run() {
          
          	
            //写线程操作的代码
    			}
    	}.start();
    

  2. 实现Runable接口,实现run方法

    public class demo implement Runable{
          
          @Override
    	public void run() {
          
          
    			//写线程操作的代码
    	}
    }
    
    //匿名方式创建
    
    demo demo = new demo();
    		new Thread(demo) {
          
          
    			@Override
    			public void run() {
          
          	
            	//写线程操作的代码
    			}
    	}.start();
    

  3. 实现Callable接口,实现call方法

  4. 使用Exector的工具类去创建线程池,通过线程池获取线程

几个创建线程的区别

  1. java是单继承模式,接口可以实现多个接口,实现接口还可以继承类。推荐使用接口方式,使系统扩展更灵活。

  2. 实现Callable接口创建线程和实现Runable接口创建线程基本一样,区别在于call方法可以有返回值。

  3. 使用Callable接口或者Runable接口,比较容易实现多个线程共享一个Tagert对象,非常适合实现多线程处理同一份业务,

    形成清晰的代码逻辑,体现面向对象的思想。

  4. 线程实现Runable接口或者Callable接口,如果要访问当前线程,必须调用Thread.currentThread()方法,但是继承Thread类,

    可以使用this关键字实现对应的效果。

  5. 使用Callable或者Thread或者Runable实现线程的创建,如果频繁创建会消耗系统资源,影响性能,而使用线程池创建不适应线程

    的时候可以放回线程池,用的时候再从线程池中取,目前在项目之中使用线程池。

start方法和run方法的区别

  • start方法时开启进程,调用后线程会放到等待队列,等待JVM调度。等待CPU时间片再去执行
  • run方法用来封装子线程需要执行的业务代码,run方法执行结束,线程终止。
线程的状态:
  • NEW :创建一个新的线程
  • RUNABLE:线程运行状态
  • WAITING:线程休眠状态
  • TIME-WAITING:线程永久休眠
  • TERMIMATED:线程死亡状态
  • BLOCKED:线程为抢断到CPU资源,阻塞状态

在这里插入图片描述

new方法创建线程,调用start方法,线程去抢夺CPU资源,如果得到CPU时间片,处于Runable状态,没有则进入blocked阻塞状态;运行状态可以和阻塞状态进行互换,就是抢夺CPU资源。线程执行完run方法之后,线程结束,进入死亡状态;如果线程在运行状态被停止(wait方法),则进入等待状态,两者互换是用Object.wait方法和Object.notify方法;当然线程处于等待状态时候等待时间结束,就可以进入与阻塞状态进行与其他线抢夺CPU资源。

线程安全

使用线程的情况下必须考虑线程安全的情况,否则会导致数据的错误,等待或者其他的严重的错误。

在不进行安全设置之前,示例一个如下的代码:

测试一个多线程进行买票的操作:

public class sale implements Runnable{
    
    
	private int ticketNums = 6;
	public static void main(String[] args) {
    
    
		
		sale s = new sale();//一个共享资源
		
    //创建三个线程   使用同一个资源
		new Thread(s,"线程一").start();
		new Thread(s,"线程二").start();
		new Thread(s,"线程三").start();
	
	}

	@Override
	public void run() {
    
    
		while(true) {
    
    
			if(ticketNums<0) {
    
    
				break;
			}
			try {
    
    
				Thread.sleep(200);//为了体现出错误的代码
			} catch (InterruptedException e) {
    
    
				
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"->>"+ticketNums--);
		}
	}
}


//结果   当然每次运行的结果不同  因为三个线程对资源的抢夺说不定
线程三->>6
线程二->>5
线程一->>5
线程三->>4
线程二->>2
线程一->>3
线程一->>1
线程三->>0
线程二->>-1

结果出现了错误数据(0和-1),和重复数据(5)

错误数据是因为当还有一张票的时候,如果线程一抢夺到资源,进入if语句,进入睡眠状态,失去CPU的执行权;此时线程二抢夺到资源,进入if语句,进入睡眠状态,失去CPU的执行权;此时线程二抢夺到资源,进入if语句,进入睡眠状态,失去CPU的执行权。此时,如果这三个线程逐次睡醒,则对于这个一个票进行减少,则会出来不正确的数据。

重复数据是因为当三个线程同时进行处理买票,则会出现相同数据。

处理线程安全的三种方式

  1. synchronized代码块

    使用锁对象进行资源限制 锁对象声明到外面,否则每个线程都会创建一个锁对象

    Object obj = new Objcet();
    synchronized(obj){
          
          
      	//操作共享资源的代码
    }
    
  2. synchronized方法

    对于非static方法,同步锁就是this。

    对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

    public synchronized void test() {
          
          
    		if (ticketNums <= 0) {
          
          
    			flag = false;
    			return;
    		}
    		try {
          
          
    			Thread.sleep(200);
    		} catch (InterruptedException e) {
          
          
    			e.printStackTrace();
    		}
    		System.out.println(Thread.currentThread().getName() + "->>" + ticketNums--);
    	}
    
  3. 使用Lock锁
    java.util.concurrent.locks.Lock接口
    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
    Lock接口中的方法:
    void lock()获取锁。
    void unlock() 释放锁。
    java.util.concurrent.locks.ReentrantLock implements Lock接口

    在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
    在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

     public void run() {
          
          
       //在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
       l.lock();
       if (ticketNums <= 0) {
          
          
    			flag = false;
    			return;
    		}
    		try {
          
          
    			Thread.sleep(200);
          System.out.println(Thread.currentThread().getName() + "->>" + ticketNums--);
    		} catch (InterruptedException e) {
          
          
    			e.printStackTrace();
    		} finally {
          
          
         //在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
         l.unlock();//无论程序是否异常,都会把锁释放
               }
            }
        }
    

注意

同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法

Obejct类中的方法
void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待
void notify()
唤醒在此对象监视器上等待的单个线程。
会继续执行wait方法之后的代码

进入到TimeWaiting(计时等待(毫秒))有两种方式
1.使用sleep(long m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

线程通信

等待与唤醒机制:有效的利用资源

使用资源的状态进行判断

notify()方法和 notifyAll()方法和wait()方法

唤醒单个线程和唤醒全部线程和等待的线程

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对 象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继 承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
//产品
public class goods {
    
    
	private String a;
	private String b;
	private Boolean status = false;
	
	public String getA() {
    
    
		return a;
	}
	public Boolean getStatus() {
    
    
		return status;
	}
	public String getB() {
    
    
		return b;
	}
	public void setPi(String a) {
    
    
		this.a = a;
	}
	public void setStatus(Boolean status) {
    
    
		this.status = status;
	}
	public void setXian(String b) {
    
    
		this.b = b;
	}
}

//消费者
public class buyer extends Thread{
    
    
	
	private goods goods;
	public buyer(goods goods){
    
    
		this.goods=goods;
	}
	
	public buyer(){
    
    
		
	}
	@Override
	public void run() {
    
    
		 
		while(true) {
    
    
			synchronized (goods) {
    
    
				if(goods.getStatus()) {
    
    
					System.out.println("买货物"+goods.getA()+goods.getB()+"两秒");
					try {
    
    
						Thread.sleep(2000);
					} catch (InterruptedException e) {
    
    
						e.printStackTrace();
					}
					goods.setStatus(false);
					System.out.println("买完了"+goods.getA()+goods.getB());
					goods.notify();
				}else {
    
    
					try {
    
    
						goods.wait();
					} catch (InterruptedException e) {
    
    			
						e.printStackTrace();
					}
				}
			}					
		}
	}
}


//生产者
public class shop extends Thread{
    
    
	
	private goods goods;
	public shop(goods goods){
    
    
		this.goods=goods;
	}
	
	public shop(){
    
    
		
	}
	@Override
	public void run() {
    
    

		while(true) {
    
    		
			 synchronized (goods) {
    
    
				 if(!goods.getStatus()) {
    
    						
						try {
    
    
							goods.setA("1one");
							goods.setB("2one3one");
							System.out.println("生产货物中3秒"+goods.getA()+goods.getB());
							Thread.sleep(3000);
						} catch (InterruptedException e) {
    
    
							e.printStackTrace();
						}
						goods.setStatus(true);
						System.out.println("好了"+goods.getA()+baozi.getB());
						goods.notify();
				}else {
    
    
						try {
    
    
							goods.wait();
						} catch (InterruptedException e) {
    
    
							e.printStackTrace();
						}	
				}
			}
		}	
	}
}

//测试类
public class test {
    
    
	public static void main(String[] args) {
    
    
		goods goods = new goods();
		shop shop = new shop(goods);
		buyer buyer = new buyer(goods);

		shop.start();
		buyer.start();
	}
}


在这里插入图片描述

线程池

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间。

在这里插入图片描述

线程池就是一个容纳很多线程的容器,一般有HashMap,LinkedList,ArrayList,HashSet

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

线程池的使用:

java的线程池顶级接口是import java.util.concurrent包的Executors接口 ,可以使用newFixedThreadPool方法去创建线程池可以自定义线程数量。返回类型是ExecutorService接口;(体现的是面向接口编程)

创建一个是实现了Runable接口的实现类

然后执行ExecutorService接口的submit方法传入实现类就可以

最后是ExecutorService接口的shutdown方法,这个是摧毁线程池的操作(不建议使用),线程池可以重复利用。

public class RunableImpl implements Runnable{
    
    
	@Override
	public void run() {
    
    
		System.out.println("使用了线程池"+Thread.currentThread().getName());	
	}
}



// submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中

public class pool {
    
    
 public static void main(String[] args) {
    
    
	ExecutorService es =  Executors.newFixedThreadPool(3);
	es.submit(new RunableImpl());
	es.submit(new RunableImpl());
	es.submit(new RunableImpl());
	es.submit(new RunableImpl());
	es.shutdown();
	
	}
}



//这里创建了三个线程大小的线程池,而有四个任务,当有归还线程的就会执行后面的任务
//使用了线程池pool-1-thread-1
//使用了线程池pool-1-thread-2
//使用了线程池pool-1-thread-3
//使用了线程池pool-1-thread-3

_Lambda表达式

标准格式

()->{}

猜你喜欢

转载自blog.csdn.net/weixin_44641846/article/details/114625242