JavaSE之线程

线程概述

进程
进程就是操作系统运行的一个程序
线程
线程就是进程的一个执行单元,一条执行路径
如启动360安全卫士就是打开一个进程, 它的电脑体检/木马查杀/系统清理等就是这个进程的几个执行单元, 每个执行单元就是一个线程
迅雷就是一个进程, 可以同时下载多部电影, 每一部电影的下载就是一条执行路径,就是迅雷进程的一个线程
一个进程至少有一个线程, 如果进程有多个线程,则它就是多线程应用程序
每个线程都有独立的栈空间
在这里插入图片描述

主线程
JVM启动主线程,主线程运行main方法
用户线程
开启的新的线程, 也称子线程
守护线程
守护线程是为其他线程提供服务的线程,不能独立运行,当JVM中只有守护线程时,JVM会退出. 垃圾回收器就是一个守护线程

创建线程的方式

创建线程依赖java.lang.Thread类, Thread就是一个线程类

方式一:继承Thread

java.lang

  1. 定义类继承Thread
  2. 重写run()方法, run()方法体中的代码就是子线程要执行的代码
  3. 创建线程对象
  4. 开启新的线程

示例:

//1) 定义类继承Thread
public class SubThread extends Thread {
	//2)重写run()方法, run()方法体中的代码就是子线程要执行的代码
	@Override
	public void run() {
		//在子线程,打印100行字符串
		for(int i = 1; i<101; i++){
			System.out.println("sub thread : " + i);
		}
	}
}
public class Test01 {

	public static void main(String[] args) {
		//3)创建线程对象
//		SubThread t1 = new SubThread();
		Thread t1 = new SubThread();
		//4)开启新的线程
		t1.start(); 	//启动的新的线程, 会执行run()方法
//		t1.run();		//就是普通的实例方法的调用, 不会开启新的线程
		
		//当前线程是main线程
		for(int i  =1; i <=100; i++){
			System.out.println("main--> " + i);
		}
		
		/*
		 * 当前程序有main线程和t1线程在同时执行, 每次运行的结果可能不一样
		 * 多线程程序中的多个线程, 同时争抢CPU执行权, 抢到CPU执行权后才会执行
		 * 
		 * 	在单核CPU中, 某一时刻CPU只能执行一个任务, 因为CPU执行速度非常 快, 可以快速的在各个线程之间进行切换
		 * 	对于用户来说,感觉是多个线程在同时执行
		 * 
		 */
	}

}
方式二:实现Runnable接口

java.lang
有两种实现方式:
第一种:

  1. 定义类实现Runnable接口
  2. 重写Runnable接口中的抽象方法run();
  3. 创建线程对象
  4. 开启线程
//1) 定义Runnable接口的实现类
public class Prime implements Runnable {
	//2)重写run()方法, run方法体就是用户线程执行的代码
	@Override
	public void run() {
		for(int i = 1;  i<=100; i++){
			System.out.println("sub thread : " + i);
		}
	}
}
public static void main(String[] args) {
		//3)创建线程对象
		Prime p = new Prime(); 			//Runnable接口实现类对象
		//Thread(Runnable)构造方法的形参是接口, 在调用方法时传递接口的实现类对象
		Thread t2 = new Thread(p);		
		//4)开启线程
		t2.start();		
		//可以使用实现类对象创建很多线程
		Thread t3 = new Thread(p);
		t3.start();					
		//main线程
		for(int i = 1 ; i<=100; i++){
			System.out.println( "main--> " + i);
		}
	}

第二种:匿名内部类

public static void main(String[] args) {
		//在调用方法时,实参是接口匿名内部类对象
		Thread t22 = new Thread(new Runnable() {
			//在匿名内部类中重写接口抽象方法
			@Override
			public void run() {
				//指定子线程要执行的代码
				for(int i = 1;  i<=100; i++){
					System.out.println("sub thread : " + i);
				}
			}
		});
		t22.start();
		// main线程
		for (int i = 1; i <= 100; i++) {
			System.out.println("main--> " + i);
		}
	}

当接口的实现类只使用一次时, 或者抽象类的子类只使用一次时, 就不需要单独的再定义一个类,可以直接使用匿名内部类

方式三:实现Callable接口

在创建线程中,一般不用这种方式,主要用在线程池
java.util.concurrent.Callable
与Runnable接口相比,Runnable接口中的run()方法没有返回值且不会抛异常 , Callable接口的call()有返回值还会抛异常
Callable接口中的call()有返回值, 通过Callable泛型 指定返回值的类型

示例:

import java.util.Random;

import java.util.concurrent.Callable;
//1) 定义类实现Callable接口
//Callable接口中的call()有返回值,  通过Callable泛型 指定返回值的类型
//Runnable接口中的run()方法没有返回值 , Callable接口的call()有返回值
public class Prime3 implements Callable<Integer> {
	//2)重写抽象方法call()方法  
	@Override
	public Integer call() throws Exception {
		int  result = new Random().nextInt(100);
		System.out.println("执行子线程, 完成某个计算,  结果为: " + result);
		return result;
	}

}
public static void main(String[] args) throws InterruptedException, ExecutionException {
		//3)创建线程对象
		Prime3 prime3 = new Prime3() ;		//创建Callable接口实现类对象		
		FutureTask<Integer> task = new FutureTask<>(prime3);
		//FutureTask实现了RunnableFuture<V>接口,该接口继承了Runnable接口, FutureTask类就是Runnable接口实现类
		Thread t3 = new Thread(task);
		
		//4)开启新的线程
		t3.start();

		//
		System.out.println("当前线程是main线程,可以获得子线程的执行结果:");
		System.out.println("result=" + task.get());
		
	}

线程的基本操作

static int activeCount() 当前活动线程的数量
static Thread currentThread() 返回当前线程
ClassLoader getContextClassLoader() 线程的上下文类加载器.
long getId() 返回线程的ID,每个线程都有唯一的id.
String getName() 返回线程名称, 默认Thread-0
int getPriority() 返回线程优先级
Thread.State getState() 返回线程状态
void interrupt() 中断线程
static boolean interrupted() 测试线程是否被中断
boolean isAlive() 测试线程是否结束, 线程的run()运行结束,该线程就终止
boolean isDaemon() 是否守护线程.
boolean isInterrupted()测试线程是否被中断.
void join() 线程合并(加入)
void run()
void setDaemon(boolean on) 设置线程为守护线程,当JVM只有守护线程时,JVM会退出
void setName(String name) 设置线程名称
void setPriority(int newPriority) 设置优先级
static void sleep(long millis) 线程休眠
void start() 开启线程
void stop() 终止线程
String toString()
static void yield() 线程让步

线程生命周期

在这里插入图片描述

线程调度

线程优先级

线程优先级越高, 获得CPU执行权的机率越大
取值范围: 1 ~ 10
默认值: 5
setPriority() / getPriority()

线程睡眠

Thread.sleep( millis )
静态方法,由类名调用
睡眠的单位是毫秒
有受检异常需要预处理(因为run方法定义时没有抛出异常,所以重写时也不能抛出异常,只能捕获)
sleep()方法所在的线程睡眠

线程中断

t1.interrupt()
一般情况下,是把处于睡眠/等待中的线程给中断,sleep()方法需要异常处理,这个异常就是当睡眠状态被打断时,会抛出异常

线程让步

Thread.yield(), 当前线程释放CPU执行权,把运行的线程转为就绪状态,重新抢CPU执行权

线程合并

t1.join() 在当前线程中加入t1线程, 当前线程转为等待状态, 等到t1执行完后当前线程转为就绪状态

线程终止

t1.stop(), 已过时
一般通过修改线程标志的方式让线程运行结束 ,以下提供了两种方法:

package thread;

public class Test05 {
	public static void main(String[] args) {
		SubThread1 t1 = new SubThread1();
		t1.setName("t1");
		t1.start();
		
		Prime1 prime = new Prime1();
		new Thread(prime,"t2").start();
		
		//main线程
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + "-->" + i);
		}
		t1.running = false;
		prime.stoping = true;
	}
}

class SubThread1 extends Thread {
	boolean running = true;
	
	@Override
	public void run() {
		for(int i = 0; running && i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + "-->" + i);
		}
	}
}

class Prime1 implements Runnable {
	boolean stoping = false;
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			if (stoping) {
				return;
			}
			System.out.println(Thread.currentThread().getName() + "-->" + i);
		}
	}
	
}

线程同步

  1. 线程安全问题
    当多个线程同时操作堆区或者方法区的某个数据时, 可能会出现数据不一致 的现象, 称为线程安全问题

2 出现线程安全问题怎么办?
每个线程都 访问自己的局部变量
如果多个线程必须同时操作实例变量/静态变量时, 可以采用线程同步技术

3 线程同步技术解决什么问题?
当一个线程在操作期间,不允许其他的线程加入
某一段代码在某一时刻只能由一个线程执行

4 如何同步?
语法:
synchronized( 锁对象 ) {
同步代码块
}
工作原理:
1)线程要执行同步代码块, 必须先获得锁对象
2)任意对象都可以作为锁对象, 每个对象有一个内置锁
3)某一时刻, 锁对象最多只能被一个线程持有
4)如果线程获得了锁对象后,会一直持有, 直到执行完同步代码块后才释放

场景描述: 假设有线程A和线程B两个线程都想要执行同步代码块
1)线程A获得CPU执行权, 获得了锁对象后, 开始执行同步代码块
2)线程A在执行同步代码块期间, CPU执行权被线程B抢走了, 线程A转为就绪状态
3)线程B获得CPU执行权, 也想要执行同步代码块, 必须先获得锁对象, 现在锁对象被线程A持有, 线程B转到等待锁对象池中进行阻塞
4)线程A重新获得CPU执行权, 执行完同步代码块后释放锁对象
5)等待锁对象池中的线程B获得了锁对象,转为就绪状态

5 同步代码块
同步代码块想要实现同步,必须使用同一个锁对象
只要使用了同一个 锁对象的同步代码块就可以实现同步

经常定义一个常量 对象作为锁对象
有时使用this对象作为锁对象
有时也使用当前类的运行时类对象作为锁对象, 如 Test07.class, 有人称它为类锁

6 同步方法
直接使用synchronized修饰的方法, 把整个方法体都作为同步代码块
修饰实例方法, 默认的锁对象是this对象
修饰静态方法, 默认的锁对象是当前类的运行时类, 如Test07.class

7死锁
在线程同步时, 由于获得锁对象的顺序不一致导致线程出现相互等待的情况,称为死锁

如何避免出现死锁?
保证获得 锁对象的顺序一致即可

生产者消费者设计模式

设计模式就是别人总结的一套解决方案,这大解决方案被大多数人熟知与认可.

生产者消费者设计模式解决了数据的平衡问题
在这里插入图片描述

生产者消费者问题实现

仓库定义

public class MyStorage {
	LinkedList<Object> list = new LinkedList<>(); 		//定义集合,作为存储产品的容器
	private static final int MAX_CAPACITY = 100;

	//向仓库中存储产品 
	public synchronized void store(String  product) {
		//如果仓库已满 ,生产者等待
		while ( list.size() > MAX_CAPACITY) {
			try {
				this.wait(); 		//wait()/notify()这两个需要在同步代码块中,通过锁对象调用
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		System.out.println( Thread.currentThread().getName() + " +++++++++++++++存储了:" + product);
		list.offer(product);	 	//把产品 存储到后面
		//通知消费者消费
		this.notifyAll();
	}
	
	//从仓库中取产品 
	public synchronized void take() {
		//判断仓库是否已空, 当仓库为空时,需要等待
		while ( list.size() <= 0 ) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		Object product = list.poll(); 		//把前面的产品取出来
		System.out.println( Thread.currentThread().getName() + " ------消费了:" + product);
		
		//通知生产者继续生产
		this.notifyAll();
	}
}

生产者线程定义

public class ProducerThread extends Thread {
	private MyStorage storage; 			//仓库	
	public ProducerThread(MyStorage storage) {
		super();
		this.storage = storage;
	}
	@Override
	public void run() {
		//每个月工作30天,每天存1次
		for( int i = 1;  i <=  30 ; i++){
			String product = "产品编号:" + new Random().nextInt(100);
			storage.store(product);
		}
	}
}

消费者线程定义

public class ConsumerThread  extends Thread{
	private MyStorage storage; 			//仓库
	
	public ConsumerThread(MyStorage storage) {
		super();
		this.storage = storage;
	}

	@Override
	public void run() {
		//从仓库中取产品
		for( int i = 1; i <=30 ; i++){
			storage.take();
		}
	}
}

Timer定时器

public class Test {

	public static void main(String[] args) throws InterruptedException {
		System.out.println( "main....begin....");
		
		//创建定时器对象
//		Timer timer = new Timer();
		Timer timer = new Timer( true ); 		//把定时器设置守护线程
		
//		timer.schedule(task, time); 		//在指定的时间time, 执行任务task
//		timer.schedule(task, delay);  		//延迟delay毫秒后, 执行task任务
//		timer.scheduleAtFixedRate(task, firstTime, period);  	//指定task任务第一次执行的时间, 以后每隔period毫秒再执行一次
		
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				System.out.println( new Date() );
			}
		}, 3000, 1000);
		
		//main线程睡眠10秒
		Thread.sleep(10000);
		System.out.println( "main....end....");
		
	}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值