JAVA线程基础知识

一, 概念

  • 进程: 指可执行程序并且存放在计算机存储器的一个指令的序列,他是一个动态执行的过程
  • 线程: 每个运行的程序都是一个进程,在一个进程中还可以有多个执行单元同时运行,这些执行单元可以看作程序执行的一条条线索, 被称为线程.

1.1, 线程的生命周期

在这里插入图片描述

二, 线程的两种创建方式:

2.1, 第一种: 继承Thread类,改写run()

Thread类中的方法:
构造方法:
在这里插入图片描述

常用方法:
在这里插入图片描述

  • 示例代码
//继承Thread类,重写run方法
public class ThreadTest extends Thread{
	public void run() {
		System.out.println(getName()+"线程开始执行");
	}
///测试类
public static void main(String[] args) {
	System.out.println("主线程开始执行");
	ThreadTest tt = new ThreadTest();
	tt.start();
	System.out.println("主线程还在执行");
	}
}

上述代码执行结果:
在这里插入图片描述

如果多加了一个 tt.start();
在这里插入图片描述

特点:

  • main()也是一个线程,最先执行,叫主线程;
  • 一个线程不能多次使用start(),否则会抛出IlligalThreadStateException;
  • 线程的运行(即获得cpu的使用权)是随机

2.1, 第二种: 实现Runnable接口,重写run()

  • 特点:
  • Runnable是Java中用以实现线程的接口;
  • 任何实现线程功能的类都必须实现该接口;
  • Runnable接口中只有一个run()方法;

Q: 为什么推荐使用这种方式?

  • Java不支持多继承(继承了Thread类就不能再继承其他类了)
  • 不打算重写Thread类的其他方法(Thread类中还有其他方法,比如获得线程的标识符,状态,名称等等.)
  • 另外,在用这种方法创建线程对象时,我们不会使用start()开启线程,而是借用Thread 有参构造方法 Thread(Runnable target),也可以使用Thread的另一个带参构造 Thread(Runnable target, String ThreadName)为线程赋值噢

示例代码:

public class RunInterfaceTest implements Runnable{
	@Override
	public void run() {
		System.out.println("线程执行了");
		System.out.println(Thread.currentThread().getName());
	}
	public static void main(String[] args) {
		RunInterfaceTest rtTest = new RunInterfaceTest();
		创建Thread对象,然后把rtTest作为Thread的参数  
		Thread thread = new Thread(rtTest);
		thread.start();
	}
}   

三, 线程的常用方法(▷):

3.1, sleep方法

  • 格式: public static void sleep(long millis)
  • 作用: 让正在执行的线程休眠(暂停执行)若干毫秒 ,线程调用sleep()后会进入阻塞状态.
  • 注意: 使用sleep方法必须用try..catch处理, 应对线程执行中断的情况. 发生异常的类型是
    InterruptedException.

3.2, join方法

  • 格式1: public final void join()
  • 格式2: public final void join(long miles) -等待线程死亡millis毫秒
  • 作用: 抢占cpu, 等待调用该方法的线程结束后才能执行
  • 注意: 使用joint方法必须用try…catch处理,应对cpu被抢占的情况. 发生异常的类型是InterruptedException.

3.3, yield方法

  • Thread的静态方法
  • 格式: Thread.yield();
  • 作用: 当前占用cpu运行的线程使用了yield方法后,会主动把cpu让出,而且与sleep不同的是,使用yield()不会阻塞该进程,只是把该进程转换为就绪状态.
  • 当线程调用yield()方法后,只有与当前线程优先级相同或更高的线程才能获得执行的机会.

四, 线程的优先级

  • Java为线程类提供了十个优先级. 优先级可以用1-10的范围来表示,超过范围会被抛出异常,主线程默认优先级为5
  • 优先级常量:
     MAX_PRIORITY 优先级为10
     MIN_PRIORITY 优先级为1
     NORM_PRIORITY 优先级为5
  • 设置优先级(拿设置最高优先级为例)
     xx.setPriority(Thead.MAXPRIORITY);
     xx.setPriority(10);
  • 优先级相关的方法
     public int getPriority() 获取线程优先级
     public void setPriority(int newPriority) 设置优先级

五,线程的调度

  • 常见的线程调度模型:
  1. 抢占性调度模型:
    • 抢占式调度模型让线程去抢夺cpu时间片,线程的优先级越高,获得时间片的概率就越高.
  2. 均分式调度模型
    • 平均分配时间片,每个线程占用的时间片长度一样.

Java 是抢占式调度模型!!!
java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。一个线程会因为以下原因而放弃CPU。一, java虚拟机让当前线程暂时放弃CPU,转到就绪状态,使其它线程或者运行机会。二,当前线程因为某些原因而进入阻塞状态。三,线程结束运行。

六, 线程的安全问题:

解决Java线程安全的几种手段

  • 什么时候数据在多线程并发的环境下存在安全性问题呢?
    当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,
    三个条件:
    条件一:多线程并发
    条件二:有数据共享
    条件三:共享数据有修改的行为
    满足以上三个条件之后,就会存在线程安全问题。

6.1, Java三种变量与线程安全

实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中

以上三大变量中:
局部变量永远都不会存在线程安全问题
因为局部变量永远都不会共享。(一个线程一个栈)
局部变量在栈中,所以局部变量永远不会共享。

实例变量和静态变量都可能存在线程安全问题。

局部变量 + 常量 不会有线程安全问题
成员变量可能会有线程安全问题

6.2, 用同步去解决线程安全

  • 前言: 线程安全问题实际上就是多个线程同时处理共享资源导致的,为了使得处理共享资源的代码一个时刻仅有一个线程访问,Java提供了同步机制.
  • synchronized关键词用在:
     成员方法
     静态方法
     语句块

线程同步,实际上就是线程不能并发,线程必须排队执行。

6.2.1, 同步代码块:

  • 当多个进程使用同一个共享资源,可以将处理共享资源的代码放置在一个代码块中,并使用synchronized去修饰,成为同步代码块.
  • 线程同步格式:
 synchronized(lock){
		操作共享资源的代码块
}  
  • 代码示例:
实现Runnable接口
public class RunInterfaceTest implements Runnable {
三窗口售票的实现  
	private int ticketNum = 10;
	///生成锁对象
	Object lock = new Object();

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (true) {
			synchronized (lock) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.getMessage();
				}
				if (ticketNum > 0) {
					System.out.println(Thread.currentThread().getName() + "卖出一张票, 此时余票: " + --ticketNum);
				} else {
					break;
				}

			}
		}

	}

}
测试类  
public class runThreadTest {
	public static void main(String[] args) {
		RunInterfaceTest runInterfaceTest = new RunInterfaceTest();

		for (int i = 1; i <= 3; i++) {
			Thread th = new Thread(runInterfaceTest, "线程" + i);
			th.start();
		}

	}
}

5.2.2, 同步方法:

  • 同步代码块可以有效的解决线程的安全问题,当把共享资源的操作放在synchornized定义的区域内,便为这些操作加了同步锁. 在方法前面同样可以使用 synchronized关键字来修饰, 被修饰的方法为同步方法
//格式:  
	synchronized 返回值类型 方法名(参数列表)
  • 示例代码:
///含tun()的线程类
public class SynMethod implements Runnable {
	private int ticketNum = 100;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (true) {

			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			if (ticketNum < 0)
				break;
			if (ticketNum > 0)
				ticketSale();

		}

	}

	private synchronized void ticketSasle() {
		ticketNum--;
		System.out.println(Thread.currentThread().getName() + "卖出了第" + ticketNum + "张票");

	}

///测试类  
public class SynTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SynMethod syn = new SynMethod();
		
		for(int i=1; i<=3; i++) {
			Thread th = new Thread(syn);
			th.start();
		}
	}
	
}  
  • 同步代码块的锁是自己定义的任意类型的对象,而对于同步方法来说,它的锁就是当前调用该方法的对象!也就是this指向的对象. 这样做的好处: 同步方法被所有线程共享,方法所在的对象相对所有线程是唯一的,从而保证了锁的唯一性
  • 同步解决了多个线程访问共享数据时的数据安全问题,只要加上同一个锁,在同一时间内只能有一条线程执行,但是, 线程在执行时每次都要判断所的状态,消耗资源且效率低下.
  • 死锁:

七, Java中线程的分类 (▷)

7.1, 定义

  • Java中有两种线程, 用户线程和守护线程(后台线程),
  • 举个栗子, main()就是用户线程, 而垃圾回收线程属于守护线程.
  • 对Java程序来说, 只要还是有一个前台线程还在运行,这个线程就不会结束. 但是如果程序中只有后台线程运行,这个进程就会结束.
  • 这里提到的前台和后台进程是个相对的概念,新创建的线程默认都是前台线程,如果某个线程在启动之前调用 setDaemon(true)语句,这个线程就会变为一个后台线程.

某个线程启动之前就是说,在start()方法之前调用setDaemon()

后台进程示例代码:


八, 多线程通信(待强化) (▷)

线程通信用到的方法:
 wait() 中断方法的执行,使线程等待;
 notify() 唤醒处于等待的某一个线程,使其结束等待
 notifyAll() 唤醒所有等待的进程

  • 生产者和消费者问题
    示例代码:
///含

public class Queue {
	private int n;
	boolean flag=false;
	
	public synchronized int get() {
		if(!flag){
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("消费:"+n);
		flag=false;//消费完毕,容器中没有数据
		notifyAll();
		return n;
	}

	public synchronized void set(int n) {
		if(flag){
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		System.out.println("生产:"+n);
		this.n = n;
		flag=true;//生产完毕,容器中已经有数据
		notifyAll();
	}
	
}
consumer
public class Consumer implements Runnable{
	Queue queue;
	Consumer(Queue queue){
		this.queue=queue;
	}

	@Override
	public void run() {
		while(true){
			queue.get();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
///producer
public class Producer implements Runnable{
	Queue queue;
	Producer(Queue queue){
		this.queue=queue;
	}

	@Override
	public void run() {
		int i=0;
		while(true){
			queue.set(i++);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}

}
test类
public class Test {

	public static void main(String[] args) {
		Queue queue=new Queue();
		new Thread(new Producer(queue)).start();
		new Thread(new Consumer(queue)).start();
	}

}
posted @   青松城  阅读(77)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示