文档章节

线程Thread

皮蛋瘦肉粥里没有粥
 皮蛋瘦肉粥里没有粥
发布于 2017/05/01 10:57
字数 3966
阅读 32
收藏 3

基本概念:

       线程是一个程序内部的顺序控制流,一个进程相当于一个任务,一个线程相当于任务的一个执行路径。

       进程是一个操作系统执行的任务,一般都是.exe文件。一个进程中可以运行着多个线程。

线程和进程的相似性在于它们都是单一顺序控制流。

       多进程就是一个操作系统运行着多个任务。

       多线程就是一个程序内部运行着多个顺序控制流。

       每个Java运行程序至少有一个主线程,例如:public static void main(String[] args){}就是一个主线程。

 

运行方式:

       通过start()方法启动一个线程。

       通过run()来执行一个线程,它是线程的主体部分。

       通过sleep(long millis)方法来使当前线程休眠一段时间,当过了mills时间后再恢复到可运行态,不是运行状态。因此,sleep()方法不能保证该线程到期后就立马开始运行。sleep()是静态方法,只能控制当前正在运行的线程,因此,休眠期间不影响其他线程的运行。

       通过yield()方法暂停当前执行的线程,让同优先级的线程轮询执行一段时间。

       通过手动调用stop()方法来结束一个线程(或者执行到run()方法的末尾,或者抛出未经处理Exception/Error,这两都是程序自动结束线程)。

       通过join()方法来让线程A加入到线程B的尾部,在B执行完之前A不能工作。

       通过notify() 唤醒在此对象监视器上等待的单个线程。

       通过notifyAll()  唤醒在此对象监视器上等待的所有线程。

       通过wait() 导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。

如何创建和启动线程:

       两种方式:extends Thread类、implements Runnable接口。

       Thread类:也是实现了Runnable接口。调用方式:继承Thread类后的子类生成对象后调用start()方法,如:new MyThread().start()。

class MyThread extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			System.out.println("i="+i);
		}
	}
}

        Runnable接口:只有一个run()方法来定义线程运行体。使用Runnable接口可以为多线程提供共享数据。调用方式:将子类对象通过Thread的构造器来执行start()方法,如:new Thread(new MyRunnable ()).start()。

class MyRunnable implements Runnable{
	String name ="xiaom";
	@Override
	public void run() {
		System.out.println("name:"+name);
	}
}

线程状态:

    ①  新线程态(New Thread)

        产生了一个Thread对象就生成一个新线程。当线程处于“新线程”状态时,仅仅只是一个空线程对象,系统还没有给它分配资源。因此,只能start()或者stop()操作,除此之外的操作都会引发异常。

    ②  可运行态(Runnable)

        start()方法产生线程运行时所需的资源,调度线程执行,并且调用run()方法时候,线程处于“可运行”状态。之所以不称为“运行态”是因为它不总是一直占用处理器。特别是对只有一个处理器的PC而言,任何时刻只有一个可运行状态的线程占用。Java通过调度来实现多线程对处理器的共享。

    ③  非运行态(Not Runnable)

        当sleep()、wait()等方法被调用或者线程出于I/O等待时称为“非运行”状态。

    ④  死亡态(Dead)

        当run()方法返回,或者别的线程调用stop()方法时,线程进入“死亡”状态。

 

线程优先级:

       当PC只有一个处理器时,以某种顺序在单处理器的情况下执行多线程被称为”调度”。Java采用的是固定优先级调度,根据处于可运行状态线程的优先级来调度。

       当线程产生时,它继承原线程的优先级,必要的时候可以通过Thread.setPriority()方法对优先级更改。如果有多个线程等待运行,系统选择优先级别最高的可运行线程运行。只有当它停止、自动放弃、或者由于某种原因成为非运行态时低优先级的线程才能运行。如果两个线程具有相同的优先级,那么它们会被交替运行。

        在任何时刻,如果一个比其他线程优先级都高的线程的状态变为“可运行“,那么实时系统将选择该线程来运行。

 

线程组:

       每个Java线程都是某个线程组的成员。线程组提供一种机制,将多个线程集于一个对象内,能对它们进行整体操作。譬如,你能用一个方法调用来启动或挂起组内的所有线程。Java线程组由ThreadGroup类实现。当线程产生时,可以指定线程组或由实时系统将其放入某个缺省的线程组内。线程只能属于一个线程组,并且当线程产生后不能改变它所属的线程组。

 

多线程同步:

       因为并发性,当多个线程同时操作一个可共享资源时易产生数据不一致,因此加入同步锁来避免该线程没有完成操作之前有其他线程进入,从而保证数据的唯一性和准确性。

       使用synchronized关键字修饰。

①  修饰方法:

    private synchronized void put(){}

    Java中每个对象都有一个内置锁,用此关键字修饰方法时,内置锁会保护整个方法。在调用方法前,需要获得内置锁,否则会造成阻塞。

    synchronized还可以修饰static方法,当静态方法被调用时,锁住的就是整个Class。

//修饰方法
private synchronized void _outTicket(){
	if(total>0){
		System.out.println(Thread.currentThread().getName()+ "出票" + this.total);
		this.total--;
	}
}

② 修饰代码块:

    synchronized(this){}

    被关键字修饰的语句块会自动加上内置锁,从而实现同步。

class MoreThread implements Runnable {
	private int total = 10;// 总票数

	@Override
	public void run() {
	for (int i = 0; i < 20; i++) {//线程运行数
		try {
			Thread.sleep(1000);
		} catch (Exception e) {
			e.printStackTrace();
		}
		outTicket();
	}
}

	// 出票方法
	private void outTicket() {
		synchronized(this){//修饰代码块
			if(total>0){
			System.out.println(Thread.currentThread().getName()+ "出票" + this.total);
				this.total--;
			}
		}
	}
}
public class MyThreadTest {
	public static void main(String[] args) {
		// 多线程同步
		MoreThread t = new MoreThread();
		new Thread(t,"线程1").start();
		new Thread(t,"线程2").start();
		new Thread(t,"线程3").start();
	}
}

    同步是一种高开销的操作,因此应该尽量减少同步内容。通常没必要同步整个方法,使用synchronized代码块同步关键代码即可。

 

线程锁:

①  原理:

    Java中每个对象都有一个内置锁。当程序运行到synchronized修饰的非静态方法上时会自动获得当前执行代码的对象的锁。

    一个对象只有一个锁,如果一个线程获得当前对象锁,其他线程就不能再获得该对象锁,除非该锁已被释放。

    释放锁是指持锁对象已退出了synchronized修饰的方法或者代码块。

 

②  锁和同步的要点:

    a)  只能同步方法,不能同步变量和类。

    b)  不必同步类的所有方法,类可以同时拥有同步方法合非同步方法。

    c)   如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。

    d)  如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。

    e)  线程睡眠时,锁不会释放。

    f)  线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。

 

向线程传递数据:

①  通过构造器传递

class MyRunnable implements Runnable {
    String name ;
	MyRunnable(String name){
		this.name = name;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("name:" + name+i);
		}
	}
}

调用方式:

new Thread(new MyRunnable("Xiaoming")).start();

②通过变量的setters方法传递

class MyRunnable implements Runnable {
	String name ;
	public void setName(String name) {
		this.name = name;
	}


	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("name:" + name+i);
		}
    }
}

调用方式:             

 MyRunnable myRunnable = new MyRunnable();
 myRunnable.setName("Xiaoming");
 Thread thread = new Thread(myRunnable);
 thread.start();

③ 通过回调函数传递

class Data {
		public int i =100;//初始数据
}

class Work{
	public void process(Data data, int random){
		System.out.println("data.i="+data.i+" random="+random);
		data.i*=random;
	}
}

class NewThread implements Runnable{
	private Work work;
	NewThread(Work work){
		this.work = work;
	}
	@Override
	public void run() {
		Data data = new Data();
		work.process(data, new Random().nextInt(10));//回调函数
		System.out.println("result:"+data.i);
	}
	public static void main(String[] args) {
		new Thread(new NewThread(new Work())).start();
	}
}

线程池:

    线程池作用就是限制系统中执行线程的数量。

    根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

    ①  减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

    ②  可根据系统的承受能力,调整线程池中线程的数目,防止因为消耗过多的内存,而把服务器累趴(每个线程大约需要1MB内存,线程开的越多内存消耗就越大)。

 

  池的创建方式:

       Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

       Executors提供以下几个方法来创建线程池:

        ①  newFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池。

代码:

ExecutorService pool =Executors.newFixedThreadPool(3);
pool.execute(new Thread(new MoreThread(),"线程1"));
pool.execute(new Thread(new MoreThread(),"线程2"));
pool.execute(new Thread(new MoreThread(),"线程3"));
			//关闭线程池
pool.shutdown();

        ② newSingleThreadExecutor():创建一个单线程的线程池。线程池只有一个线程在工作,如果这个线程出现异常,会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务提交的顺序执行。

代码:

ExecutorService pool = Executors.newSingleThreadExecutor();

        ③ new CachedThreadPool():创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

代码:

ExecutorService pool = Executors.newCachedThreadPool();

        ④  newScheduledThreadPool(int corePoolSize):创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行。

代码:

ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
//使用延迟,隔1、3、5秒执行当前线程
pool.schedule(new Thread(new MoreThread(),"线程1"), 1000, TimeUnit.MILLISECONDS);
pool.schedule(new Thread(new MoreThread(),"线程2"), 3000, TimeUnit.MILLISECONDS);
pool.schedule(new Thread(new MoreThread(),"线程3"), 5000, TimeUnit.MILLISECONDS);
		
//关闭线程池
pool.shutdown();

自定义线程池ThreadPoolExecutor:

    ThreadPoolExecutor的构造器:

ThreadPoolExecutor(int corePoolSize ,int maximumPoolSize
,long keepAliveTime ,TimeUnit unit
,BlockingQueue<Runnable> workQueue
,ThreadFactory threadFactory
,RejectedExecutionHandler handler);

参数:

       corePoolSize:池中所保存的线程数,包括空闲线程。

       maximumPoolSize:池中允许的最大线程数。

       keepAliveTime:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

       unit:keepAliveTime的时间单位。

       workQueue:执行前用于保持任务的队列。此队列仅保持由execute方法提交的Runnable任务。

       threadFactory:执行程序创建新线程时使用的factory。

       handler:由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。

 

并发机制-锁:

       在Java5中,专门提供了锁对象,利用锁可以方便的实现资源的封锁,利用控制对竞争资源并发访问的控制,这些内容主要在java.util.concurrent.locks包中,里面有三个重要的接口:

  •  Condition:将Object监视器方法(wait、notify、notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合适用,为每个对象提供多个等待。
  •  Lock:提供了比使用synchronized方法和语句更广泛的锁定操作。
  •  ReadWriteLock:维护了一组相关的锁定,一个只读操作,一个写入操作。

 

直接提供一段Lock代码:

/**
 * 线程锁的机制
 * @author admin
 */
public class ThreadLockTest {
	public static void main(String[] args) {
		Ticket ticket = new Ticket(30);//创建总票数
		TicketWindow wind = new TicketWindow();//售票窗口随机
		Lock lock = new ReentrantLock();//创建锁
		ExecutorService pool = Executors.newCachedThreadPool();//线程池
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("张力",2), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("李兴",1), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("刘兰兰",5), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("兔兔",2), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("张力",2), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("姚丽丽",1), ticket, lock)));
		pool.execute(new Thread(new Tecketing(wind, new Buyyer("姚明明",3), ticket, lock)));
		pool.shutdown();
	}
}

/**
 * 售票窗口类
 * @author admin
 */
class TicketWindow{
	//随机返回一个窗口
	public int wind(){
		return (new Random().nextInt(5)+1);
	}
}

/**
 * 总票数类
 * @author admin
 */
class Ticket{
	private int total;
	//构造器
	public void setTotal(int total) {
		this.total = total;
	}
	public Ticket(int total) {
		this.total = total;
	}
	public int getTotal() {
		return total;
	}
}

/**
 * 售票机制
 * @author admin
 */
class Tecketing implements Runnable{
	private TicketWindow wind; //窗口
	private Buyyer user;
	private Ticket ticket; //总票数
	private Lock lock;
	
	public Tecketing(TicketWindow wind, Buyyer user, Ticket ticket, Lock lock) {
		this.wind = wind;
		this.user = user;
		this.ticket = ticket;
		this.lock = lock;
	}

	@Override
	public void run() {
		//获取锁
		lock.lock();
		if(ticket.getTotal()>0){
			System.out.print(user.getName()+"在"+wind.wind()+"窗口购买了"+user.getTicketCount()+"张票,");
			ticket.setTotal(ticket.getTotal()-user.getTicketCount());
			System.out.println("目前剩余票数:"+ticket.getTotal());
		}else{
			System.out.println("今日票已全部售罄!");
		}
		//释放锁,否则其他线程无法执行
		lock.unlock();
	}
}

/**
 * 购票者类
 */
class Buyyer{
	private String name; 
	private int ticketCount; //购票数量
	public Buyyer(String name, int ticketCount) {
		this.name = name;
		this.ticketCount = ticketCount;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getTicketCount() {
		return ticketCount;
	}
	public void setTicketCount(int ticketCount) {
		this.ticketCount = ticketCount;
	}
}

ReadWriteLock机制的锁,读写锁分离,灵活性更好。代码:

/**
 * ReadWriterLock接口读写时分别用锁
 * @author admin
 */
public class ReadWriterLokTest {
	public static void main(String[] args) {
		Ticket ticket = new Ticket(30);//创建总票数
		TicketWindow wind = new TicketWindow();//售票窗口随机
		ReadWriteLock lock = new ReentrantReadWriteLock();//创建锁
		ExecutorService pool = Executors.newCachedThreadPool();//线程池
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("窗口",0), ticket, lock, false)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("张力",2), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("李兴",1), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("刘兰兰",5), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("兔兔",2), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("张力",2), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("姚丽丽",1), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("姚明明",3), ticket, lock, true)));
		pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("窗口",0), ticket, lock, false)));
		pool.shutdown();
	}
}

/**
 * 售票机制
 * @author admin
 */
class Tecketing_1 implements Runnable{
	private TicketWindow wind; //窗口
	private Buyyer user;
	private Ticket ticket; //总票数
	private ReadWriteLock lock;
	private boolean isCheck;//是否查询
	private int solded =0;//已售卖
	
	public Tecketing_1(TicketWindow wind, Buyyer user, Ticket ticket, ReadWriteLock lock,boolean isCheck) {
		this.wind = wind;
		this.user = user;
		this.ticket = ticket;
		this.lock = lock;
		this.isCheck = isCheck;
	}

	@Override
	public void run() {
		if(isCheck){
			//获取写锁
			lock.writeLock().lock();
			if(ticket.getTotal()>0){
				System.out.print(user.getName()+"在"+wind.wind()+"窗口购买了"+user.getTicketCount()+"张票,");
				ticket.setTotal(ticket.getTotal()-user.getTicketCount());
				System.out.println("目前剩余票数:"+ticket.getTotal());
			}else{
				System.out.println("今日票已全部售罄!");
			}
			//释放
			lock.writeLock().unlock();
		}else{
			//获取读锁
			lock.readLock().lock();
			solded +=user.getTicketCount();
			ticket.setTotal(ticket.getTotal()-solded);
			System.out.println("窗口"+wind.wind()+" 正在查询剩余票数:"+ticket.getTotal());
			//释放
			lock.readLock().unlock();
		}
	}
}

 

© 著作权归作者所有

皮蛋瘦肉粥里没有粥
粉丝 11
博文 58
码字总数 20373
作品 0
朝阳
后端工程师
私信 提问
Redis做分布式无锁CAS的问题

因为Redis本身是单线程的,具备原子性,所以可以用来做分布式无锁的操作,但会有一点小问题。 public interface OrderService { } public class OrderRedisServiceImpl implements OrderSer...

算法之名
2018/07/15
0
0
C#多线程编程(整理)

Donet基础类库的System.Threading命名空间提供了大量的类和接口支持多线程。这个命名空间有很多的类,下面是Thread的资料。 System.Threading.Thread类是用来创建并控制线程,设置其优先级并...

冰点沐雪
2012/07/22
0
0
gdb多线程调试1

gdb对于多线程程序的调试有如下的支持: 线程产生通知:在产生新的线程时, gdb会给出提示信息 (gdb) r Starting program: /root/thread [New Thread 1073951360 (LWP 12900)] [New Thread 1...

长平狐
2012/09/03
143
0
Java中通过Executors调用静态方法来提供四种线程池介绍

Java中的线程池用ThreadPoolExecutor类来表示,ThreadPoolExecutor这个类继承自抽象类AbstractExecutorService,AbstractExecutorService又实现了ExecutorService接口,ExecutorService接口又...

hello菜bird
2018/02/07
5
0
Java中的ThreadGroup线程组

ThreadGroup概述 在java中为了方便线程管理出现了线程组ThreadGroup的概念,每个ThreadGroup可以同时包含多个子线程和多个子线程组,在一个进程中线程组是以树形的方式存在,通常情况下根线程...

长头发-dawn
2018/09/03
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Python应用:python链表示例

前言 python链表应用源码示例,需要用到python os模块方法、函数和类的应用。 首先,先简单的来了解下什么是链表?链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是...

python小白1
28分钟前
1
0
Source Insight加载源码

Source Insight是一个图形化的源代码查看工具(当然也可以作为编译工具)。如果一个项目的源代码较多,此工具可以很方便地查找到源代码自建的依赖关系。 1.创建工程 下图为Snort源代码的文件...

天王盖地虎626
29分钟前
0
0
nginx-rtmp-module的缺陷分析(二)

nginx-rtmp-module使用指令push和pull来relay媒体流数据,以便分布式部署服务。 当nginx-rtmp-module作为边缘服务器(一般不会向边缘服务器推流)时,使用pull从源服务器获取媒体流数据,俗称...

YoungSagit
30分钟前
0
0
代理模式

保护代理和虚拟代理 虚拟代理 把开销大的操作等到需要执行创建的时候再去执行创建 var myImage = +function () { var imgNode = document.createElement('img'); docume......

莫西摩西
37分钟前
2
0
从国企到互联网,程序员六年四段工作经历,一份被很多 HR都 刷掉的简历!

程序员,六年,四段工作经历,这也许是一份会被很多 HR 刷掉的简历。 从学生时代至今,我经历了两次大的方向转型和一次大的技术转型: 从偏理论推导的数学科学到重工程实践的计算机学科,从「...

我最喜欢三大框架
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部