Day20 多线程创建、生命周期、锁

程序、进程、线程

程序:静态概念,某种语言编写的指令的集合,一段静态的代码
进程:程序的执行过程或正在执行的程序,动态过程,有生命周期
线程:进程细化为线程,是一个程序内部的执行路径
并发与并行:并发轮流执行,并行同时执行

单核CPU和多核CPU

Day20 多线程创建、生命周期、锁_第1张图片

并行和并发

在这里插入图片描述

多线程优点

Day20 多线程创建、生命周期、锁_第2张图片

创建线程

创建线程有两种方式,但是启动线程 只能一种方式调用Thread类对象中的start方法
创建线程的第一种方式 创建类 继承 Thread类 覆写 run()方法,run方法就等于是新线程中的main方法,是程序执行的起点和终点
启动线程 手动调用当前线程对象的 start()方法 !!! 不是run方法

		// 创建线程类对象
		Thread t = new Test_01();
		// 启动线程
		t.start();

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

第二种方式 实现Runnable接口,覆写run方法 启动线程 调用Thread的start方法

	// 创建线程类对象
		Thread t = new Thread(new Test_02());
		// 启动线程
		t.start();
		
class Test_02 implements Runnable {
     
	@Override
	public void run() {
     
		for (int i = 0; i < 10; i++) {
     
			System.out.println("run->" + i);
		}
	}
}

Start() : 启动线程
setName() : 设置线程的名字,默认是Thread-0 , Thread-1 以此类推
getName() : 获取线程的名字
setPriority() : 设置优先级 , 1-10 , 10个级别,1最小,10最高
getPriority() : 获取优先级
Static currentThread() : 获取当前线程的内存地址(线程对象)
Static sleep() : 让当前线程进入睡眠状态,参数是毫秒数

		Thread t1 = new Test_03();
		Thread t2 = new Test_03();
		// 设置名字,最好在启动之前设置,默认Thread-0 以此类推
		t1.setName("t1");
		t2.setName("t2");
		// 启动
		t1.start();
		t2.start();
		// 设置优先级
		t2.setPriority(10);
		t1.setPriority(1);
		Thread.currentThread().setPriority(10);
		for (int i = 0; i < 10; i++) {
     
			// currentThread 获取当前线程对象,静态方法,意味着,和谁调用没有关系,写在哪个线程就获取哪个线程对象
			System.out.println(Thread.currentThread().getName() + "->" + i);
			try {
     
				// sleep 让当前线程睡眠,静态方法,和谁调用无关,写在哪个线程,就睡眠哪个线程
				Thread.sleep(1000);
			} catch (InterruptedException e) {
     
				e.printStackTrace();
			}
		}


class Test_03 extends Thread {
     
	@Override
	public void run() {
     
		for (int i = 0; i < 10; i++) {
     
			// 获取名字
			System.out.println(this.getName() + "->" + i);
			try {
     
				// sleep 让当前线程睡眠,静态方法,和谁调用无关,写在哪个线程,就睡眠哪个线程
				Thread.sleep(1000);
			} catch (InterruptedException e) {
     
				e.printStackTrace();
			}
		}
	}
}

生命周期

JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五 种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
Day20 多线程创建、生命周期、锁_第3张图片

线程控制

Day20 多线程创建、生命周期、锁_第4张图片

Thread t = new Test_004();
		Thread t2 = new Test_004();
		t.setName("t1");
		t2.setName("t2");
		t.start();
		t2.start();
		// 当前线程 需要等待t线程执行完之后 再继续执行
		t.join();
		for (int i = 0; i < 10; i++) {
     
			System.out.println(Thread.currentThread().getName() + " -> " + i);
		}

sleep() 让当前线程进入睡眠,静态方法,传入睡眠毫秒数
interrupt() : 强制唤醒正在睡眠的这个线程,会抛异常

Thread t1 = new Thread(new Test_04());
		t1.start();
		try {
     
			Thread.sleep(1000);
			// 唤醒t1线程
			t1.interrupt();
		} catch (InterruptedException e) {
     
			e.printStackTrace();
		}
Thread t = new Test_004();
		Thread t2 = new Test_004();
		t.setName("t1");
		t2.setName("t2");
		t.start();
		t2.start();
		// 当前线程 需要等待t线程执行完之后 再继续执行
		t.join();
		for (int i = 0; i < 10; i++) {
     
			System.out.println(Thread.currentThread().getName() + " -> " + i);
		}

Thread.yield() : 静态方法,让出当前执行的时间片,让其他线程执行
1 静态方法,写在哪里,哪里让位
2 相同优先级让位,不同优先级不让位
优先级默认是 5 , 子类继承父类优先级

	Thread t = new Thread(new  Test_05());
		t.setName("t1");
		t.start();
		for (int i = 0; i < 10; i++) {
     
			// 让位
			Thread.yield();
			System.out.println(Thread.currentThread().getName() + "->" + i);
		}

结束线程

stop : 终止一个线程,容易导致死锁,所以已经过时,不推荐使用
推荐使用标识符的方式来结束

		Test_06 t = new Test_06();
		t.start();
		try {
     
			Thread.sleep(5000);
			// t.stop();
			t.flag=false;
		} catch (InterruptedException e) {
     
			e.printStackTrace();
		}

	//  标志符,false就终止
	boolean flag = true;

	@Override
	public void run() {
     
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
		while (flag) {
     
			System.out.println(sdf.format(new Date()));
			try {
     
				Thread.sleep(1000);
			} catch (InterruptedException e) {
     
				e.printStackTrace();
			}
		}
	}

线程同步-锁

线程同步 : 当多个线程有可能操作同一个数据的时候,为了保证数据的一致性,需要对当前操作进行同步处理
线程同步本质是数据的同步,是一种安全机制
尤其是对数据进行更改操作,必须要使用同步机制,如果只是查询,就无所谓了
异步编程
线程之间完全是独立的,同时进行,但相互不影响
同步编程
线程之间不是独立的,同时进行,但是相互有影响,所以这时需要某个线程单独执行完某个操作之后,再让其他线程执行
同步条件
1 必须多线程
2 多个线程必须有可能同时操作某一个共享数据
3 主要是涉及数据的更改操作
方法锁 : synchronized 修饰符,修饰的方法,不能同时被多个线程执行
如果访问某一个对象中 加锁的 成员方法,则该对象中所有加锁的成员方法,都不能被其他线程访问
如果访问一个类中 加锁的静态方法,则该类中所有加锁的静态方法 都不能被其他线程访问
Day20 多线程创建、生命周期、锁_第5张图片
Day20 多线程创建、生命周期、锁_第6张图片

	// 取钱
	public void withDraw(double money) {
     

		System.out.println(Thread.currentThread().getName());
		try {
     
			Thread.sleep(2000);
		} catch (InterruptedException e) {
     
			e.printStackTrace();
		}
		// 锁当前对象,当前对象中,所有的加锁成员方法也会被锁住
		// 如果把整个方法锁住,导致 多个线程不能同时进入方法
		// 如果我们方法中,很多代码,但是只有两句需要同步,效率就太低了
		// 这样的话,多个线程依旧可以同时进入该方法,但是 语句块是不能同时执行的
		synchronized (this) {
     
			// 类锁
			// synchronized (类名.class) {}
			balance -= money;
			System.out.println(Thread.currentThread().getName()
					+ "--> 取钱成功,剩余 : " + getBalance());
			try {
     
				Thread.sleep(2000);
			} catch (InterruptedException e) {
     
				e.printStackTrace();
			}
			// 加锁
			// xxxx
			// 解锁
		}
	}

锁对象

	// 锁对象
	Lock lock = new ReentrantLock();
	// 取钱
	public void withDraw(double money) {
     

		System.out.println(Thread.currentThread().getName());
		try {
     
			Thread.sleep(2000);
		} catch (InterruptedException e) {
     
			e.printStackTrace();
		}
		// 加锁
		lock.lock();
		try {
     
			balance -= money;
			System.out.println(Thread.currentThread().getName()
					+ "--> 取钱成功,剩余 : " + getBalance());
		} finally {
     
			// 解锁
			lock.unlock();
		}

Day20 多线程创建、生命周期、锁_第7张图片

守护线程

守护线程 : 又称为兜底线程
每个程序运行当中,都会默认开启一个守护线程用于监听我们的正常程序
当没有任何一个线程执行的时候,JVM就需要退出了,这个时候守护线程也会退出
Thread类中 提供了 setDaemon() 方法 可以设置某个线程为守护线程

// 设置t1为守护线程
		t1.setDaemon(true);

定时器

	// 1 创建定时器
		Timer timer = new Timer();
		String str = "2021/04/16 15:51:12 000";
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");
		Date date = sdf.parse(str);
		// 1 要执行的任务对象,任务类需继承TimerTask类,并覆写run方法,里面是要完成的事情
		// 2 执行的开始时间,可以传入时间对象(到了指定时间开始执行), 也可以传入毫秒数(多久之后执行)
		// 3 间隔时间
		timer.schedule(new logTimerTask(), date, 1000);

你可能感兴趣的:(笔记,java)