JavaSE-多线程

1. 多线程

         1.1 程序,进程,线程

                 程序 (program) : 是一组指令和数据的集合,用来实现特定的功能,它是静态的代码,存储在磁盘或其他存储设备中 。

                 进程 (process) : 是程序的一次执行过程,是操作系统分配资源和调度的基本单位,它是动态的,有自己的生命周期 。

                 线程 (thread) : 是进程内的一个执行单元,是操作系统调度和执行的最小单位,它是轻量级的进程,可以共享所属进程的资源 。

                 对于java来说,当我们的程序开始执行时,也就是main方法开始执行时,就会在栈内存中开辟以main方法为栈底元素的栈帧。此时,这个这个以main方法为栈底元素的链式栈帧调用,就称为主程序。

                 CPU时间片: 把CPU执行5次的时间作为一个借本单位,称为一个时间片,然后将时间片分配给每个线程,分配方式由操作系统决定,一般按优先级发放。

         1.2 并发和并行

                 并发: 同时发生,指的是多个任务在同一时间段内交替执行,同一时刻只有一个任务在执行。

                 并行: 同时进行,指的是多个任务在同一时刻同时执行,不需要交替或切换。

         1.3 单核CPU和多核CPU

                 1个CPU同时只能做一件事,所以没有办法并行执行,但是可以处理并发

                 单核CPU: 是指只有一个核心的中央处理器,他只能同一时刻执行一个任务,是假的多线程。

                 多核CPU: 有两个及以上核心的中央处理器,他可以同一时刻执行多个任务或线程,能有效的发挥多线程的效率。

         1.4 多线程的优缺点和使用场景

                 多线程的优缺点:

                优点:

                     1. 提高应用程序的响应,对图形化界面更有意义,可增强用户体验。

                     2. 提高计算机系统CPU的利用率

                     3. 改善程序结构,将既长又复杂的程序分为多个线程,独立运行,利于理解和修改。

                缺点:

                     1. 多线程程序涉及到线程同步、数据共享等复杂问题,编写和调试相对困难。

                     2. 多个线程访问共享数据时可能导致竞争条件,需要谨慎处理,否则可能引发错误。

                     3. 如果线程之间存在循环等待资源的情况,可能导致死锁,使程序无法继续执行。

               应用场景:

                     1. 程序需要同时执行两个或多个任务

                      2. 程序需要实现一些需要等待的任务的时候,入用户输入,文件读写操作,网络操作, 搜索等。

                      3. 需要一些后台运行的程序时。

         1.5 线程的创建

                 两种方式:

                 1. 继承Thread类,并覆写run()方法

                 2. 实现Runnable接口,并覆写run()方法

                 启动线程的方式只有一种,就是调用start()方法*

                1.5.1 Thread
// 通过继承创建线程
public class Thread_00 {
	public static void main(String[] args) {
		// 创建线程对象
		Thread t1 = new Process();
		// 启动线程
		t1.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main线程:" + i);
		}
	}
}

class Process extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("测试线程:" + i);
		}
	}
}
                1.5.2 Runnable

// 通过实现接口创建线程
public class Thread_01 {
	public static void main(String[] args) {
		// 创建线程对象
		Thread t1 = new Thread(new Process_01());
		// 启动线程
		t1.start();
		for (int i = 0; i < 100; i++) {
			System.out.println("main线程:" + i);
		}
	}
}

class Process_01 implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("测试线程:" + i);
		}
	}
	
}
                 1.5.3 通过继承和实现创建线程的区别

                         使用Runnable接口更具灵活性,因为它避免了Java单继承的限制,并且允许多个线程共享相同的Runnable实例。通常推荐使用实现Runnable接口的方式创建线程。

         1.6 优先级和常用方法

                 1.6.1 优先级概述

                         java中共提供了1~10 ,10个优先级

                         Thread中提供了三个优先级常量,MIN_PRIORITY代表1,NORM_PRIORITY代表5,MAX_PRIORITY代表10。

                         默认优先级为5,子类从父类继承优先级

                 1.6.2 常用方法

                         setPriority 设置优先级

                         getPriority 获取优先级

                         setName 设置线程名称,如果不设置线程名称,默认从Thread_0开始,依此类推

                         getName 获取线程名称

                         static currentThread 获取当前线程对象

                         static sleep 让当前线程进入睡眠状态,参数为毫秒

                 1.6.3 使用方式
// 使用方式
public class Thread_02 {
	public static void main(String[] args) {
		// 创建线程
		Thread t1 = new Process_02();
		Thread t2 = new Process_02();
		// 设置线程名
		t1.setName("t1");
		t2.setName("t2");
		// 设置线程优先级
		t1.setPriority(1);
		t2.setPriority(10);
		// 启动线程
		t1.start();
		t2.start();
		
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + i);
		}
		
	}
	
	
}

class Process_02 extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + i);		
			try {
				// 线程睡眠时间
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
// sleep方法
public class Thread_03 {
	public static void main(String[] args) {
		// 创建线程
		Thread t1 = new Process_03();
		Thread t2 = new Process_03();
		// 设置线程名
		t1.setName("t1");
		t2.setName("t2");
		// 设置线程优先级
		t1.setPriority(1);
		t2.setPriority(10);
		// 启动线程
		t1.start();
		t2.start();
		// 线程睡眠
		// sleep为静态方法,写在哪里,哪个线程就会进入睡眠,和调用没有关系,因为最后都是通过类名调用的
		// 当前睡眠的是main线程
		try {
			t1.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + i);
		}
		
	}
	
	
}

class Process_03 extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + i);		
		}
	}
}
// 打断睡眠抛异常
public class Thread_04 {
	public static void main(String[] args) {
		Thread t1 = new Thread(new Process_04()) ;
		t1.start();
		try {
			Thread.sleep(3000);
			// 三秒后叫醒线程t1,会抛出异常
			t1.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class Process_04 implements Runnable{

	@Override
	public void run() {
		try {
			Thread.sleep(1000000000);
			System.out.println("睡眠结束了");
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.out.println("睡眠被打断");
		}
		System.out.println("测试线程结束");
	}
	
}

  

       1.7 生命周期

JavaSE-多线程_第1张图片

         1.8 线程控制方法

                 isAlive() 判断当前线程是否还"活着",即线程是否还未终止

                 getPriority() 获取当前线程优先级数值

                 setPriority() 设置当前线程优先级数值

                 Thread.sleep() 将当前线程睡眠指定毫秒数

                 join() 调用某线程的该方法,将当前线程于该线程合并,即等待该线程结束,回复当前线程运行

                 yield() 让出CPU,当前线程进入就绪队列等待调度

                 wait() 当前线程进入对象的wait pool

                 notify()/notifyAll() 唤醒对象的wait pool中的一个或多个线程

                 1.8.1 线程终止
// 线程停止
public class Thread_05 {
	public static void main(String[] args) {
		Process_05 t1 = new Process_05();
		t1.start();
		try {
			Thread.sleep(3000);
			// stop方法结束t1线程,不推荐使用,因为会造成死锁
			// t1.stop();
			t1.run = false;
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class Process_05 extends Thread{
	boolean run = true;
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			if (!run) {
				return;
			}
			System.out.println(Thread.currentThread().getName() + "->" + i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}
                 1.8.2 线程合并
// 线程合并
public class Thread_06 {
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Process_06());
		Thread t2 = new Thread(new Process_06());
		t1.start();
		t2.start();
		// join写在main线程中,main线程执行到这里,等待t1线程执行完毕,在继续执行
		// 相当于合并了main和t1线程,不会对t2线程的执行造成影响
		t1.join();
		
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"->"+i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

class Process_06 implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "->" + i);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}
                 1.8.3 Yield
/*
 * Yield 线程让位
 * 暂停当前正在执行的线程,执行其它线程
 * Thread.yield为静态方法,写在哪个线程就作用于哪个线程
 * 让位只存在于同优先级之间
 */
public class Thread_07 {
	public static void main(String[] args) {
		Thread t1 = new Thread(new Process_07());
		// 设置t1线程优先级
		t1.setPriority(2);
		// 设置main线程优先级
		Thread.currentThread().setPriority(2);
		t1.start();
		for (int i = 0; i < 10; i++) {
			if (i % 2 == 0) {
				// main线程让位
				Thread.yield();
			}
			System.out.println(Thread.currentThread().getName() + "->" + i);
		}
	}
}

class Process_07 implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName() + "->" + i);
		}
	}
	
}

         1.9 线程同步

                 1.9.1 概述

                 问题: 多个线程执行的不确定性引起执行结果的不确定性

                          多个线程对同一个数据或对象的共享会造成操作的不完整性,会破坏数据

                 线程同步:

                 当多个线程同时操作一个数据的时候,为了保证数据的一致性和正确性,需要使用线程同步,线程同步是一种数据的安全机制。

                 线程同步的原因:

                 同步是指数据的同步,为了数据的安全,在同时操作数据时必须等莫格线程对数据操作完,再让其它线程进行操作。同步可以看作是将多线程转为单线程。

                 线程同步的条件:

                 1. 必须是多线程多并发的情况下,才有可能出现。

                 2. 多个线程有可能同时操作同一个数据或同一个对象是,尤其是修改操作。

                 1.9.2 不同步带来的问题
// 线程同步(未同步状态)
/*
 * 输出结果:
 * t1取钱完成,剩余1000.0
 * t2取钱完成,剩余1000.0
 * 
 * t1取钱完成,剩余2000.0
 * t2取钱完成,剩余1000.0
 * 
 * t1取钱完成,剩余2000.0
 * t2取钱完成,剩余2000.0
 * 
 * ......
 * 
 */
public class Thread_08 {
	public static void main(String[] args) {
		// 创建银行卡对象
		BackCard card = new BackCard("1001A",3000);
		// 创建两个进程,同时操作同一个银行卡对象
		Thread t1 = new Thread(new Process_08(card));
		Thread t2 = new Thread(new Process_08(card));
		
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		t2.start();
	}
}

class Process_08 implements Runnable{
	// 声明银行卡对象
	BackCard card;
	@Override
	public void run() {
		// 从银行卡中取出1000
		card.modifyBalance(1000);
		// 打印剩余余额
		System.out.println(Thread.currentThread().getName() + "取钱完成,剩余" + card.getBalance());
	}
	public Process_08(BackCard card) {
		this.card = card;
	}
}


// 实体类
class BackCard {
	// 卡号
	private String cardNo;
	// 余额
	private double balance;
	// 取出余额
	public void modifyBalance(double money) {
		balance = balance - money;
	}
	
	public BackCard() {
		super();
	}
	public BackCard(String cardNo, double balance) {
		super();
		this.cardNo = cardNo;
		this.balance = balance;
	}
	public String getCardNo() {
		return cardNo;
	}
	public double getBalance() {
		return balance;
	}
	
	
}
                 1.9.3 解决方案
                         1.9.3.1 方法锁
// 线程同步(添加方法锁)
/*
 *  运行结果
 *  t1
 *  t2
 *  t1取钱完成,剩余2000.0
 *  t2取钱完成,剩余1000.0
 *  
 *  t2
 *  t1
 *  t2取钱完成,剩余2000.0
 *  t1取钱完成,剩余1000.0
 */

public class Thread_09 {
	public static void main(String[] args) {
		// 创建银行卡对象
		BackCard_01 card = new BackCard_01("1001A",3000);
		// 创建两个进程,同时操作同一个银行卡对象
		Thread t1 = new Thread(new Process_09(card));
		Thread t2 = new Thread(new Process_09(card));
		
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		t2.start();
	}
}

class Process_09 implements Runnable{
	// 声明银行卡对象
	BackCard_01 card;
	@Override
	public void run() {
		// 从银行卡中取出1000
		card.modifyBalance(1000);
		// 打印剩余余额
		System.out.println(Thread.currentThread().getName() + "取钱完成,剩余" + card.getBalance());
	}
	public Process_09(BackCard_01 card) {
		this.card = card;
	}
}


// 实体类
class BackCard_01 {
	// 卡号
	private String cardNo;
	// 余额
	private double balance;
	// 取出余额
	public synchronized void modifyBalance(double money) {
		// synchronized 使用该修饰符后,该方法只能有一个线程进入
		// ,其它线程必须等这个线程执行完毕,弹栈后才能进入
		System.out.println(Thread.currentThread().getName());
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		balance = balance - money;
	}
	
	public BackCard_01() {
		super();
	}
	public BackCard_01(String cardNo, double balance) {
		super();
		this.cardNo = cardNo;
		this.balance = balance;
	}
	public String getCardNo() {
		return cardNo;
	}
	public double getBalance() {
		return balance;
	}
	
	
}
                         1.9.3.2 语句块锁
// 线程同步(语句块锁)
public class Thread_10 {
	public static void main(String[] args) {
		// 创建银行卡对象
		BackCard_02 card = new BackCard_02("1001A",3000);
		// 创建两个进程,同时操作同一个银行卡对象
		Thread t1 = new Thread(new Process_10(card));
		Thread t2 = new Thread(new Process_10(card));
		
		t1.setName("t1");
		t2.setName("t2");
		
		t1.start();
		t2.start();
	}
}

class Process_10 implements Runnable{
	// 声明银行卡对象
	BackCard_02 card;
	@Override
	public void run() {
		// 从银行卡中取出1000
		card.modifyBalance(1000);
		// 打印剩余余额
		System.out.println(Thread.currentThread().getName() + "取钱完成,剩余" + card.getBalance());
	}
	public Process_10(BackCard_02 card) {
		this.card = card;
	}
}


// 实体类
class BackCard_02 {
	// 卡号
	private String cardNo;
	// 余额
	private double balance;
	// 取出余额
	public void modifyBalance(double money) {
		// synchronized 使用该修饰符后,该方法只能有一个线程进入
		// ,其它线程必须等这个线程执行完毕,弹栈后才能进入
		System.out.println(Thread.currentThread().getName());
		// 语句块锁,因为方法会把整个方法锁住,而有的时候只需要同步几行代码
		// 采用语句块锁只会锁住需要同步的代码,不会对方法内其它代码进行同步
		synchronized (this) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			balance = balance - money;
		}
		
	}
	
	public BackCard_02() {
		super();
	}
	public BackCard_02(String cardNo, double balance) {
		super();
		this.cardNo = cardNo;
		this.balance = balance;
	}
	public String getCardNo() {
		return cardNo;
	}
	public double getBalance() {
		return balance;
	}
	
}
                 1.9.4 Synchronized

                         访问一个加锁的成员方法/成员语句块锁时,该对象中所有的成员方法和对象语句块锁都会被锁定。

                         访问一个加锁的静态方法/静态语句块锁时,该类中所有加锁的静态方法和静态语句块锁都会被锁定。

                         synchronized(对象){} 对象语句块锁

                         synchronized(类名.class){} 类语句块锁

         1.10 Lock

                 1.10.1 概述

                 从jdk1.5开始,java提供了更强大的线程同步机制,通过显示定义同步锁对象来实现同步,同步锁使用Lock对象充当。

                 java.util.concurrent.locks.Lock 接口是控制多线程对共享资源进行访问的工具。锁提供了对共享资源的独占空间,

                 每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

                 ReentranLock类实现了Lock,它拥有和synchronized相同的并发性和内存语义,在实现线程安全控制中,比较常用的是ReentranLock,可以直接显式加锁,释放锁。

                1.10.2 使用
// 创建锁对象
	Lock lock = new ReentrantLock();
	// 取出余额
	public void modifyBalance(double money) {
		// 开启锁
		lock.lock();
		System.out.println(Thread.currentThread().getName());
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		balance = balance - money;
		// 释放锁
		lock.unlock();
	}
                1.10.3 优缺点

                 Lock是显式锁,需要手动开启和关闭,并且JVM将花费较少的时间来完成线程调度,具备更好的扩张性。

         1.11 守护线程

/ 守护线程
/*
 * 守护线程: 每一个main主线程的开启,都会同时开启一个守护线程,来监听我们的正常程序执行
 * 		   当没有线程执行的时候,守护线程也就终止了
 */
public class Thread_12 {
	public static void main(String[] args) {
		Thread t1 = new Thread(new Process_12());
		// 设置为守护线程
		t1.setDaemon(true);
		t1.start();
		for (int i = 0; i < 6; i++) {
			try {
				Thread.sleep(1000);
				System.out.println("main线程:" + i);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

// 创建线程
class Process_12 implements Runnable{
	@Override
	public void run() {
		int i = 0;
		// 死循环,测试守护线程随被守护的线程的关闭而关闭
		while(true) {
			try {
				Thread.sleep(1000);
				System.out.println(i++);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

         1.12 定时器任务

                 1.12.1 概述

                 定时器: 在某些指定时间或按照指定规律执行的任务,即计划任务,只要有一个计划任务,就会开启一个线程,进行计时监听。

                 1.12.2 使用
// 定时器任务
public class Thread_13 {
	public static void main(String[] args) {
		// 创建定时器
		Timer t = new Timer();
		// 开启定时器
		// 第一个参数为TimerTask子类对象,也就是要执行的任务
		// 第二个参数为开始时间,传入毫秒数,代表多久后开始在执行,也可以传入Date对象
		// 第三个参数为每次执行的间隔时间
		t.schedule(new LogTimeTask(),5000,1000);
	}
}

class LogTimeTask extends TimerTask{
	int i = 0;
	@Override
	public void run() {
		// 要执行的任务
		System.out.println(i++);
	}
}

你可能感兴趣的:(开发语言,java,eclipse)