Java之简单线程学习

  • 什么是进程?

在多任务操作系统之中,一个后台正在运行的应用就是一个进程。

通俗定义而言,就是一个应用程序就是一个进程。

  • 进程与线程的关系?

一个进程需要完成多个任务,这时候利用线程进行任务操作,可以极大提高CPU的利用率。

一个进程至少有一个线程,一个线程只能属于一个进程。

  • 线程的生命周期。

线程按照其创建、运行以及结束几个阶段,划分为六个生命周期。分别为:

新建状态:jvm为创建的线程对象分配推空间和相关资源,当线程被创建完成时即为新建状态;

可运行状态:该状态实际包含两种状态,分别是就绪状态和运行状态。当创建的线程对象执行start方法时,则会进入就绪状态;当进入就绪状态的线程争抢到cpu资源时,会执行run方法,进入运行状态。

阻塞状态:正在运行的线程对象由于缺乏相应的资源,无法继续执行后续任务,此时会让出cpu进入阻塞状态,当获取到需要的资源时,会重新进入就绪状态争抢cpu资源。线程阻塞原因较为常见的有:

  1. 当前正在运行的线程,需要访问同步锁资源,当同步锁资源被其他线程占有,只能等待其他线程释放资源才能执行后续任务;
  2. 当前线程发出I/O请求。

等待状态:正在运行的线程通过调用无时间参数限制的方法后,进入等待状态,只有被重新唤醒才能进入就绪状态。比如线程调用wait()、join()等方法,从而进入等待状态,只有其他线程通过notify或者notifyAll方法唤醒后才能进入就绪状态(join方法只能等待插入的线程执行完毕之后才能进入就绪状态)。

定时等待状态:与定时状态的区别在于,其含有时间参数,在指定的时间参数结束后或者其他线程唤醒后,线程自动进入就绪状态。

终止状态:线程的run方法或者call方法执行完毕之后或者捕捉到程序异常或者错误时,线程进入终止状态,即线程死亡。                                                                                                                                

  • 单线程程序与多线程程序。

单线程程序:一般只含有主程序,即一个程序按照顺序运行。较为脆弱和效率过低。

多线程程序:将一个程序划分不同任务给线程,每一个线程都能独立完成任务,以此提高cpu的利用效率。

前台和后台:每一个运行的线程都默认为前台,前台线程运行结束以后,后台程序自动结束。前台线程也可以转化为后台线程。

线程的优先级:线程抢占cpu资源的方式一般有两种。一为通过时间片原则抢占cpu资源,即划分执行时间,每一个线程在执行时间结束后释放cpu资源;二为通过抢占式的方法,各线程竞争cpu资源,该方式也为jvm默认的线程调度方式。在抢占式方法中,可以通过设置线程的优先级提高线程的抢占率,但并不是优先级高的一定能够抢占到cpu资源。

  • 创建线程对象的方法。
  1. Thread。通过继承Thread类,重写run方法,定义一个子类。通过该子类创建实例对象,即创建了线程对象。
  2. Runnable。Runnable是一个接口,构造该接口的实现类,重写run方法,实例化对象,再将runnable子类实例化的对象作为参数传入Thread类的构造方法之中。
  3. Callable。该类也为接口类,通过构造该类的实现类,重写call实现线程创建。调用时,实例化子类对象,通过Future类封装子类对象,再将封装后的FutureTask对象作为参数传入Thread的构造方法之中。
  4. 通过线程池创建以上三种线程对象。实际上,线程类型同以上三种,只是线程池提供了对线程对象的管理,使线程更加安全、高效的运行。
  • 各种方法之间的区别。

Thread:该继承方法是线程实现的基础,但由于java中只支持单继承,若一个类已经有父类,需要在使用线程时,便无法通过此种方式创建线程;

Runnable:该类为接口类,java中支持多实现,解决了已有父类无法创建线程的问题。

Callable:该类为接口类,在runnable接口的基础上,实现了对线程执行结果的获取,即线程执行结束之后可以返回一个执行结果。

  • 线程同步。
  1. 线程安全:当多个线程对同一个资源进行请求时,由于线程的延迟,会导致被请求资源出现错误,如数据不一致、数据出现不应有的改变等。
  2. 实现线程安全的方法:
  1. 同步代码块:由于线程安全问题是由多个线程共享同一资源而导致的,要解决线程安全问题,必须保证任意时刻,共享资源的代码块只能被一个线程访问。
  2. 实现方式:使用synchronized关键字修饰共享资源的代码块。实质上为将共享资源的代码块上同步锁,当有线程在访问时,锁定程序代码块,只有访问的线程执行结束后,才能重新释放代码块。
  3. 基本格式为:synchronized(lock){...;//资源代码块}
  4. Lock为锁对象,可以是任意类型。但是不同线程共享的锁对象必须保持一致,如此才能使锁的标志位对所有线程而言都是一样的。
  5. 同步方法:在方法的修饰符后面使用synchronized关键字,可以将方法进行锁定,一次只能允许一个线程访问该方法。此时,该方法的锁对象是方法所在的对象本身;若为静态方法,锁对象为方法所在的类的class对象。

优点:解决了多个线程同时访问共享数据时的线程安全问题。

缺点:线程在执行同步代码时每次都会判断锁的状态,非常消耗资源,效率较低。

  • 同步锁。

背景:同步代码块和同步方法的使用是一种封闭的锁机制,使用简单,也能较好的解决线程安全问题,但是该方法无法中断等待锁的进程不在等待,也无法通过轮询的方式得到锁,需要一直等待才能得到锁。

Lock锁:jdk5开始时引入,功能与synchronized相似,但是可以让线程持续获取同步锁失败后返回,不再继续等待,同时使用也更加灵活。

Lock是一个接口类,其有多个实现类,其中最为常用的有ReentrantLock类,常用的方法有:

Lock():在线程获取锁时如果锁已被其他线程获取,则进行等待,是最初级的获取锁的方法。Lock不会自动释放锁,哪怕程序异常也不会自动释放,只能手动释放锁。

tryLock() 用来尝试获取锁,如果当前锁没有被其他线程占用,则获取成功,返回 true,否则返回 false,代表获取锁失败。相比于 lock(),这样的方法显然功能更强大,我们可以根据是否能获取到锁来决定后续程序的行为。

Unlock():释放锁。一般使用try-catch模块对程序异常进行捕获,并且在finally中使用该方法释放锁,确保锁一定会在程序执行完后被释放。

lockInterruptibly()这个方法的作用就是去获取锁,如果这个锁当前是可以获得的,那么这个方法会立刻返回,但是如果这个锁当前是不能获得的(被其他线程持有),那么当前线程便会开始等待,除非它等到了这把锁或者是在等待的过程中被中断了,否则这个线程便会一直在这里执行这行代码。一句话总结就是,除非当前线程在获取锁期间被中断,否则便会一直尝试获取直到获取到为止。

顾名思义,lockInterruptibly() 是可以响应中断的。

  • 线程通信。

背景:

若一个线程执行run方法时,对于不同的方法需要获取不同的锁,并且要在执行的方法之中获取下一步的锁才能继续执行,此时若有多个线程同时执行run方法,分别获得不同方法的锁,此时,在每个线程执行完当前方法后,想要继续执行后续方法,获取锁时都是锁定状态,只能不断等待下去,便造成了线程之间的死锁问题。

线程通信:

解决死锁问题,可以通过线程之间通信,释放彼此需要的锁来进行。线程通信即通过信号量机制实现。

  • 信号量机制。

意义:实现线程通信,解决线程堵塞问题。

实际应用:实现资源能被有限个线程使用。

特点:

信号量机制不同于互斥锁,其可以指定某一个资源能够被访问的线程最大数量,当有一个线程访问该资源时,信号量减一,当线程完成资源访问释放时,信号量加一,故信号量也可看作一个计数器。

Java中的信号量对象:Semaphore。其构造时可以传入一个整型参数,表示对应资源可接受线程的最大访问数量。

 CountDownLatch:实现线程等待,即需要指定个数的线程到达某一个点时才进行下一步操作。

  • 线程池。

优点:

1.降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;

2.提高系统响应速度,当有任务到达时,无需等待新线程的创建便能立即执行;

3.方便线程并发数的管控,线程若是无限制的创建,不仅会额外消耗大量系统资源,更是占用过多资源而阻塞系统或oom等状况,从而降低系统的稳定性。线程池能有效管控线程,统一分配、调优,提供资源使用率;

4.更强大的功能,线程池提供了定时、定期以及可控线程数等功能的线程池,使用方便简单。

常用线程池:

  1. Executor(通用线程池):

时间:jdk5被引入,在java.util.concurrent包中,其为接口类,常用的子接口有ExecutorService,通过该接口可以很好的进行线程管理。

  1. Executor实现线程管理池的主要步骤:

(1)创建线程实现类对象。

(2)使用Executor线程执行器类创建线程池。

(3)使用ExecutorService执行器服务类的submit()方法将线程实现类对象提交到线程池管理。

(4)线程任务执行完后,使用shutdown()方法关闭线程池。

  1. CompletableFuture(函数式异步编程辅助类):

背景:jdk8时引入,实现了Future接口和CompletionStage接口

(jdk8时引入的线程任务完成接口),对Future接口进行了强大的扩展,同时简化了异步编程的复杂性,其实际上是为了解决callable实现接口时,使用FutureTask进行封装的不足。

实际代码演示:

  1. 创建线程
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class Threads extends Thread{
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+"为通过继承Thread所创建的线程!");
	}
}
class Runnables implements Runnable{

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+"为通过实现Runnable所创建的线程!");
	}
	
}
class Callables implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+"为通过实现Callables所创建的线程!");
		return 0;
	}
	
}
public class CreateThreads {
	
	public static void main(String[] args){
		
		Threads threads = new Threads();
		threads.setName("线程一");
		threads.start();
		
		Runnables runnables = new Runnables();
		Thread temp = new Thread(runnables);
		temp.setName("线程二");
		temp.start();
		
		Callables callables = new Callables();
		FutureTask<Integer> future = new FutureTask<Integer>(callables);
		Thread temps=new Thread(future);
		temps.setName("线程三");
		temps.start();
		try {
			int ans=future.get();
			System.out.println("Callable返回的结果为:"+ans);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ExecutionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}

运行结果:

2.同步代码块

/*
 * 该代码为同步静态资源和方法,需要注意,同步块中的锁对象需要为静态变量
 * 保证线程访问资源都共用同一个锁对象。
 * 若共享成员变量,则创建线程时,使用同一个实体类对象作为参数。
 * */
class ThreadTest extends Thread{
	
	public static int sum=20;
	public static int ans=20;
	private static Object ob =  new Object();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		
			while(sum>0) {
		
			synchronized (ob) {
				
				if(sum>0) {
					try {
						sleep(1000);
					} catch (Exception e) {
						// TODO: handle exception
					}
					System.out.println(currentThread().getName()+"消耗资源"+sum--);
				}
			}				
				
			}
			while(ans>0) {
				try {
					sleep(1000);
				} catch (Exception e) {
					// TODO: handle exception
				}
				this.Spend();
			}
		
	}
	public static synchronized void Spend() {
		
		if(ans>0) {
			System.out.println(currentThread().getName()+"通过同步方法消耗资源"+ans--);
		}
	}
}
public class SynchronizedBlock {
	
	public static void main(String[] args) {
		
		ThreadTest test1 = new ThreadTest();
		test1.setName("线程一");
		
		ThreadTest test2 = new ThreadTest();
		test2.setName("线程二");
		
		ThreadTest test3 = new ThreadTest();
		test3.setName("线程三");
		
//		ThreadTest test4 = new ThreadTest();
//		test4.setName("线程四");
//		
//		ThreadTest test5 = new ThreadTest();
//		test5.setName("线程五");
		
		test1.start();
		test2.start();
		test3.start();
//		test4.start();
//		test5.start();
	}
}

运行结果为:

 

3.lock锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Runnablel implements Runnable{

	private int socre = 20;
	private Lock lock = new ReentrantLock();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(this.socre>0) {
			
			lock.lock();
				try {
					
					if(this.socre>0) {
						Thread.sleep(1000);
						System.out.println(Thread.currentThread().getName()+"正在消耗资源"+this.socre--);
					}
				} catch (Exception e) {
					// TODO: handle exception
				}
				finally {
					
					lock.unlock();
				}
		}
	}
	
}
public class RunnableLock {
	
	public static void main(String[] args) {
		
		Runnablel runnables = new Runnablel();
		Thread test1 = new Thread(runnables,"线程1");
		Thread test2 = new Thread(runnables,"线程2");
		Thread test3 = new Thread(runnables,"线程3");
		test1.start();
		test2.start();
		test3.start();
		
	}
}

 运行结果

4.线程通信

import java.util.concurrent.Semaphore;

class Runnablec implements Runnable {
	
	private int resource = 20;
	
	//创建信号量
	private Semaphore A = new Semaphore(1);
	private Semaphore B = new Semaphore(1);
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(resource>0) {
			try {
				A.acquire();
				if(resource>0) {
					Thread.sleep(1000);
					this.display();
					B.acquire();
					this.showInfo();
				}
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			finally {
				
				B.release();
			}
		}
		
		
	}
	public void showInfo() {
		
		System.out.println(Thread.currentThread().getName()+"获取了信号量B释放了信号量A");
		this.A.release();
	}
	public void display() {
		
		System.out.println(Thread.currentThread().getName()+"获取了信号量A消耗资源"+this.resource--);
	}
	
}
public class ThreadCommunication {
	
	public static void main(String[] args) {
		
		Runnablec rc = new Runnablec();
		Thread t1 = new Thread(rc,"线程1");
		Thread t2 = new Thread(rc,"线程2");
		Thread t3 = new Thread(rc,"线程3");
		t1.start();
		t2.start();
		t3.start();
	}
	
}

运行结果部分截图:

 

5.线程池使用

参考博客:Java 多线程:彻底搞懂线程池_孙强 Jimmy的博客-CSDN博客_java 多线程池

补充说明:

  1. 创建和使用线程时,容易引发oom问题,故需要对线程进行控制,尤其对于线程池而言,直接使用Executors创建线程容易引发以上问题;
  2. 针对不同的并发场景,使用不同的线程池进行线程管理可极大节约资源。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值