今天的内容
进程
线程【重点】
今天学习的目标是要入门线程。知道线程是干嘛的!!!!!
1. 进程
是独立的一个应用程序
比如咱们电脑上面,一个软件就是一个应用程序,就是一个进程,idea、 QQ
window 系统会给进程分配当前电脑中资源,可以分享当前的网络,网卡,内存,显卡等
进程有些特性:
独立性
各个进程之间是相互独立的互不影响!!!
互斥性
每个软件 windows 系统会给他们分配唯一的端口号,如果一个软件启动了电脑会给分配一个独立的端口号,如果其他软件启动,电脑再给分配另一个端口号,端口号是唯一的。
比如 QQ 端口号是 97 idea 端口号也是 97. 意味着 QQ 启动之后 idea 无法启动,会报错,端口被占用的错误
2. 线程
进程是可以运行的,在运行的时候,线程是进程中最小的运行单位。每一个进程至少有一个线程
如果一个进程没有线程,那么这个进程就没有生命力。
进程包含了线程,线程是组成进程的最小的基本单位
线程特性:
抢占式运行【很重要今天能理解线程】
一个进程在执行的时候,靠获取 CPU 时间片来执行的。单位时间片是抢占式执行的。
比如一个进程里面有是三个线程在执行,同时执行的 ? 不是 三个线程先抢占
假如线程1先抢到以后,线程 1 先执行 4ms, 然后是释放资源。三个线程再抢 线程 2 再执行。执行 4ms
如此往复。抢占间隔的时间是比较短的!!所以你感觉这个应用程序一致在执行的!!!
资源共享性
线程之间可以共享网卡和 CPU 的
Java 程序:
比如咱们写过的 Demo1 main 主函数 就是一个进程,就是一个应用程序。
Demo1 这个 Java 应用程序中有几个线程?
两个:
main 主线程
JVM 垃圾回收线程
3. 并行和并发
并行:真正意义上的同时执行,我一边做饭一边看电视
并发:同时发生轮流交替执行,我看一会电视做一会饭
4. 创建线程的两种方式【重点】
一个线程干一个活,比如咱们可以让一个线程去打印 99 乘法变,让另一个线程打印等腰直角三角形!!!
创建线程的第一种方式:
创建一个新的执行线程有两种方法。一个是将一个类声明为 Thread 的子类,这个子类应该重写 Run 类的方法 Thread 。然后可以分配并启动子类的实例。
package com.study.a_thread;
/**
* @author big God
* @date 2023/1/2 10:23 * @version 1.0
*/
// 1.自己新建一个类声明为 Thread 子类
class MyThread1 extends Thread {
// 2. 重写 run 方法
@Override
public void run() {
// MyThread1 线程执行的任务
for (int i = 0; i < 500; i++) {
System.err.println("MyThread1 线程" + i);
}
}
}
class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("MyThread2 线程" + i);
}
}
}
public class Demo1 {
public static void main(String[] args) {
// 3. 实例化线程
MyThread1 myThread1 = new MyThread1();
// 4. 启动线程 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
myThread1.start();
new MyThread2().start();
}
}
练习:新建两个线程一个线程打印九九乘法表一个线程打印直角三角形 15分钟能不能写完
package com.study.a_thread;
/**
* @author big God
* @date 2023/1/2 10:42 * @version 1.0
*/
class Multiplication extends Thread {
@Override
public void run() {
for (int i = 1; i < 10; i++) {
for (int j = 1; j <= i; j++) {
System.out.print(i + "*" + j + " = " + i * j + "\t");
}
System.out.println();
}
}
}
class Triangle extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < i + 1; j++) {
System.out.print("* ");
}
System.out.println();
}
}
}
public class Demo2 {
public static void main(String[] args) {
new Multiplication().start();
new Triangle().start();
// 线程得到的东西不可预期,所以难!!! 每次执行的结果不一样
}
}
创建线程的第二种方式
另一种方法来创建一个线程是声明实现类 Runnable 接口。那个类然后实现了 run 方法。然后可以分配类的实例,在创建 Thread 时作为参数传递,并启动。
package com.study.b_thread;
/**
* @author big God
* @date 2023/1/2 11:16 * @version 1.0
*/
// 1. 实现 Runnable 接口
class MyThread1 implements Runnable {
@Override
// 2. 重写 run 方法
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyThread1 线程" + i);
}
}
}
class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyThread2 线程" + i);
}
}
}
public class Demo1 {
public static void main(String[] args) {
//3. 创建 MyThread1 的对象
//4. 在创建 Thread 对象的时候将线程对象作为参数传递进来
// Thread(Runnable target)
// 分配新的 Thread 对象。
// 5. 开启线程
new Thread(new MyThread1()).start();
// 匿名内部类实现 Runnable 接口
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.err.println("匿名内部类实现 Runnable接口 " + i);
}
}
}).start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程" + i);
}
// 启动 MyThread2 线程
// 匿名对象的写法
new Thread(new MyThread2()).start();
}
}
练习:新建两个线程打印九九乘法表一个线程打印直角三角形!!
使用第二种方式来写!!! 【匿名内部类也可以写】
package com.study.b_thread;
/**
* @author big God
* @date 2023/1/2 11:30 * @version 1.0
*/
public class Demo2 {
public static void main(String[] args) {
// 匿名内部类实现 Runnable 接口 九九乘法表
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 10; i++) {
for (int j = 1; j < i + 1; j++) {
System.out.print( i + "*" + j + "=" + i * j + "\t");
}
System.out.println();
}
}
}).start();
// 匿名内部类实现 Runnable 接口 直角三角形
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
for (int j = 0; j < i + 1; j++) {
System.out.print("* ");
}
System.out.println();
}
}
}).start();
}
}
开发中使用第二种方式,因为多个接口
5. 线程下面几个基础方法
构造方法】
线程下面的方法
package com.study.c_thread;
/**
* @author big God
* @date 2023/1/2 11:46 * @version 1.0
*/
class MyThread1 implements Runnable {
@Override
public void run() {
// currentThread() 获取当前线程的引用 当前 MyThread1 对象
System.out.println("Line7:" + Thread.currentThread());
// Thread-0 线程默认的名字
System.out.println("Line8:" + Thread.currentThread().getName());
}
}
public class Demo1 {
public static void main(String[] args) {
// 通过构造方法对线程起名字
Thread thread = new Thread(new MyThread1(),"线程1");
System.out.println("Line13:" + thread);
// 覆盖构造方法所起的名字
thread.setName("MyThread1 线程");
thread.start();
// 匿名内部类实现 Runnable 接口
// 通过构造方法对线程起名字
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
// currentThread() 获取当前线程的引用 当前 MyThread1 对象
// Thread-1 线程默认的名字
System.out.println("Line9:" + Thread.currentThread());
System.out.println("Line3:" + Thread.currentThread().getName());
}
}, "匿名内部类线程2");
thread1.start();
// 在 main 主函数中写 currentThread() 主线程 获取主线程的名字
// main 是主线程的默认名字
//
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName());
// 发现咱们的线程 JVM 都会给一个默认的值,咱们呢过不能自己给线程起名字 ? 可以的
}
}
package com.study.c_thread;
/**
* @author big God
* @date 2023/1/2 12:18 * @version 1.0
*/
class Mythread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("MyThread2 线程" + i);
}
}
}
public class Demo2 {
public static void main(String[] args) {
Thread myThread2 = new Thread(new Mythread2(), "MyThread2");
myThread2.setPriority(10);
Thread.currentThread().setPriority(1);
int priority = myThread2.getPriority();
// 线程优先级 默认是 5, 值可以设置为 1 ~ 10, 1 的优先级是最低的 10 的优先级最高的
// 优先级并不是真正的优先,优先级越高就增加执行的概率
System.out.println(priority); // 5
myThread2.start();
// main 主线程中优先级 5
System.out.println(Thread.currentThread().getPriority());
for (int i = 0; i < 100; i++) {
System.err.println("主线程" + i);
}
}
}
package com.study.c_thread;
/**
* @author big God
* @date 2023/1/2 12:30 * @version 1.0
*/
class MyThread3 implements Runnable {
@Override
public void run() {
// 让 Mythread3 睡一会儿 10000 毫秒
// 1 秒 = 1000 毫秒
try {
// 为啥只能 try - catch 不能 throws
/**
* run 方法是重写的方法,重写非常严格
* 在父类中 run 方法是没有 throws 的
* public abstract void run() throws Exception;
*/
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.err.println("MyThread3 线程:" + i);
}
}
}
public class Demo3 {
public static void main(String[] args) {
new Thread(new MyThread3()).start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程:" + i);
}
}
}
上午的内容
1. 创建线程的两种方式
继承 Thread 类,重写 run 方法
实现 Runnable 接口,重写 run 方法
2. 线程下面的几个方法
static void sleep(); 让线程睡一会儿
static Thread currentThread(); 获取当前线程对象的
setName() 设置线程名字的
getName() 获取线程名字的
6. 线程的同步和锁
当时用多个线程来访问同一个数据时,将会导致数据不准确,相互之间产生冲突,非常容易出现安全问题,比如多个线程都在操作同一个数据,都打算修改商品库存,这样就会导致数据不一致的问题。
线程同步的真实意思,其实是"排队":几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
所以我们用同步机制来解决这些问题,加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
三个人(三个线程)同时对共享资源(一个坑位)进行操作(抢占)。导致数据不安全的效果。咋解决?
同步机制,加锁。一个人上厕所,锁住,其他的两个人在外面等待。等待你解决完以后释放掉这个锁以后。其他的线程再进来抢占这个资源。
package com.study.d_thread;
/**
*卖票
* 有两个线程卖票 火车票 总共有 192 张票
* 一个线程 第 192 张票 另外一个线程 第 191 张票
* @author big God
* @date 2023/1/2 14:37 * @version 1.0
*/
class SaleTicket implements Runnable {
// ticket 共享资源 线程会操作这个共享的资源
private static int ticket = 192;
@Override
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + ticket + "票");
ticket--;
} else {
System.err.println("卖完了");
break;
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
// 两个线程访问同一个数据
SaleTicket saleTicket = new SaleTicket();
new Thread(saleTicket,"线程1").start();
new Thread(saleTicket,"线程2").start();
}
}
线程1卖出了第192票
线程1卖出了第191票
线程1卖出了第190票
线程1卖出了第189票
线程1卖出了第188票
线程1卖出了第187票
线程1卖出了第186票
线程1卖出了第185票 问题出现了
线程2卖出了第185票 问题出现了 为啥同时卖出去了?
线程2卖出了第183票
线程2卖出了第182票