线程学习(二)

线程学习(二)


案例:模拟售票员售票,四个窗口各卖100张票。

1.线程的实现
1.1线程创建方式有三种:

1 继承Thread类

2 实现Runnable接口

3 实现Callable接口(了解即可,此处不进行介绍)

1.2通过继承Thread类实现案例要求
class SellTickets extends Thread {
	//数据
    int ticket = 100;
	@Override
	public void run() {
		//循环售票
		while(true) {
          	 if(ticket<1){
                 break;
             }
			System.out.println(Thread.currentThread().getName() + "售出了第"+ticket+"张票");
			ticket--;
        }
	}
}


public static void main(String[] args) {
        //需求:模拟4个售票员各售100张票
        SellTickets w1 = new SellTickets();
        SellTickets w2 = new SellTickets();
        SellTickets w3 = new SellTickets();
        SellTickets w4 = new SellTickets();

        w1.start();
        w2.start();
        w3.start();
        w4.start();
    }
1.3Thread类实现步骤:
继承自Thread类,Thread类是所有线程类的父类,实现了对线程的抽取和封装

继承Thread类创建并启动多线程的步骤:
1.定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体
2.创建Thread子类的对象,即创建了子线程
3.用线程对象的start方法来启动该线程

注意:
	1 程序运行时会自动创建一个线程 ,这个线程叫主线程;可以通过主线程创建子线程。	
	2 启动线程使用start()方法,不要直接调用run()方法。
1.4通过实现Runnable接口实现案例要求
//票
public class TicketRes implements Runnable{
     	int ticket = 100;
		@Override
		public void run() {
			while(true) {
                 if(ticket<1){
                     break;
                 }
                System.out.println(Thread.currentThread().getName() + "售出了第"+ticket+"张票");
                ticket--;
       		 }
		}
}



public class ThreadTextDemo02 {
	public static void main(String[] args) {
      	// 票
         TicketRes res=new TicketRes();
      	//线程对象
		Thread t0 = new Thread(res);
		Thread t1 = new Thread(res);
		Thread t2 = new Thread(res);
		Thread t3 = new Thread(res);
		
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}
1.5Runnable接口 实现步骤
实现Runnable接口创建并启动多线程的步骤:
1.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行体
2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
3.调用线程对象的start方法来启动该线程    
1.6两种实现方式的比较
继承Thread类的方式:
1.没有资源共享,编写简单
	如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以使用getName()获取线程名字。
2.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】

实现Runnable接口的方式
1.可以多个线程共享同一个资源,所以非常适合多个线程来处理同一份资源的情况
2.资源类实现了Runnable接口。如果资源类有多个操作,需要把功能提出来,单独实现Runnable接口。
3.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()

总结:实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】
2.多线程访问临界资源
2.1 多线程访问临界资源时的数据安全问题

​ 临界资源 :多个线程同时访问的资源。
​ 产生原因:有多个线程在同时访问一个资源,如果一个线程在取值的过程中,时间片又被其他线程抢走了,临界资源问题就产生了

2.2 解决临界资源问题

​ 一个线程在访问临界资源的时候,如果给这个资源“上一把锁”,这个时候如果其他线程也要访问这个资源,就得在“锁”外面等待。

2.3同步代码块

​ 同步:Synchronized:有等待

​ 异步:Asynchronized:没有等待,各执行各的

语法:
	synchronized(锁) {
		//需要访问临界资源的代码段
	}

说明:
1.程序走到代码段中,就用锁来锁住了临界资源,这个时候,其他线程不能执行代码段中的代码,只能在锁外边等待
2.执行完代码段中的这段代码,会自动解锁。然后剩下的其他线程开始争抢cpu时间片
3.一定要保证不同的线程看到的是同一把锁,否则同步代码块没有意义
2.3.1同步代码块使用
public class Ticket implements Runnable{
     // 需求:100张
	// 临界资源
     private int ticket = 100;
      @Override
      public void run() {
        while (true) {
          //上锁
        synchronized(this){
            if (ticket < 1) {
              break;
            }
            System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
           ticket--;
       	  }
        }
      }
}
2.4 同步方法
2.4.1 同步非静态方法
public class Ticket implements Runnable{
     // 需求:100张
	// 临界资源
      private int ticket = 100;
      @Override
      public void run() {
        while (true) {
		   if(!sale()){
                break;
            }
        }
      }
  	 public synchronized boolean sale(){//锁是this
           if (ticket < 1) {
               return false;
            }
            System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
           ticket--;
       		return true;
     }
}
2.4.2 同步静态方法
public class Ticket implements Runnable{
     // 需求:100张
	// 临界资源
      private static int ticket = 100;
      @Override
      public void run() {
        while (true) {
		   if(!sale()){
                break;
            }
        }
      }
  	 public synchronized static boolean sale(){ //锁是 类.class
           if (ticket < 1) {
               return false;
            }
            System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
           ticket--;
       	   return true;
     }
}
2.5ReentrantLock类(可重入锁)jdk1.5

​ 从jdk1.5之后加入新的接口 Lock,ReentrantLock是Lock接口的实现类。
​ 通过显式定义同步锁对象来实现同步,同步锁提供了比synchronized代码块更广泛的锁定操
​ 注意:最好将 unlock的操作放到finally块中
​ 通过使用ReentrantLock这个类来进行锁的操作,它实现了Lock接口,使用ReentrantLock可以显式地加锁、释放锁。

public static void main(String[] args) {
	Ticket res=new Ticket();
	Thread t0 = new Thread(res, "喜羊羊");
	Thread t1 = new Thread(res, "沸羊羊");
	Thread t2 = new Thread(res, "灰太狼");
	Thread t3 = new Thread(res, "小灰灰");

	t0.start();
	t1.start();
	t2.start();
	t3.start();
}




//票类
public class Ticket implements Runnable{
     // 需求:100张
	// 临界资源
     private int ticket = 100;

	// 定义一个ReentrantLock类的对象
     ReentrantLock lock = new ReentrantLock();
      @Override
      public void run() {
        while (true) {
          //上锁
          lock.lock();
          if (ticket < 1) {
            break;
          }
          System.out.println("售票员" + Thread.currentThread().getName() + "售出第"+ticket+"张票");
  		 ticket--;
          //解锁
          // unlock()
          lock.unlock();

          //注意:lock()和unlock()都是成对出现的
        }
      }
}
3.线程池
3.1创建线程池的原因

​ 当有非常多的任务需要多线程来完成,且每个线程执行时间不会太长,这样会频繁的创建和销毁线程。频繁创建和销毁线程会比较耗性能。如果有了线程池就不要创建更多的线程来完成任务,因为线程可以重用。

​ 线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。

3.2线程池的接口

1 Executor:线程池的核心接口,负责线程的创建使用和调度的根接口

2 ExecutorService: Executor的子接口,线程池的主要接口, 提供基本功能。

3 ScheduledExecutorService: ExecutorService的子接口,负责线程调度的子接口。

3.3线程池的实现类

1 ThreadPoolExecutor:ExecutorService的实现类,负责线程池的创建使用。

2 ScheduledThreadPoolExecutor:继承 ThreadPoolExecutor,并实现 ScheduledExecutorService接口,既有线程池的功能,又具有线程调度功能。

3 Executors:线程池的工具类,负责线程池的创建。

​ newFixedThreadPool();创建固定大小的线程池。

​ newCachedThreadPool();创建缓存线程池,线程池大小没有限制。根据需求自动调整线程数量。

​ newSingleThreadExecutor();创建单个线程的线程池,只有一个线程。

​ newScheduledThreadPool();创建固定大小的线程池,可以延迟或定时执行任务。

3.4使用线程池实现案例
public class Ticket implements Runnable{
	private int ticket=100;
	@Override
	public void run() {
		while(true) {
			if(ticket<=0) {
				break;
			}
			System.out.println(Thread.currentThread().getName()+"卖第"+ticket+"张票");
			ticket--;
		}
	}
}
public static void main(String[] args) {
	Ticket ticket=new Ticket();
	ExecutorService threadPool = Executors.newFixedThreadPool(4);
	for(int i=0;i<4;i++) {
		threadPool.submit(ticket);
	}
	threadPool.shutdown();
	System.out.println("主线程执行完毕........");	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值