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) 设置优先级
五,线程的调度
- 常见的线程调度模型:
- 抢占性调度模型: 
  - 抢占式调度模型让线程去抢夺cpu时间片,线程的优先级越高,获得时间片的概率就越高.
 
- 均分式调度模型 
  - 平均分配时间片,每个线程占用的时间片长度一样.
 
Java 是抢占式调度模型!!!
java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU。一个线程会因为以下原因而放弃CPU。一, java虚拟机让当前线程暂时放弃CPU,转到就绪状态,使其它线程或者运行机会。二,当前线程因为某些原因而进入阻塞状态。三,线程结束运行。
六, 线程的安全问题:
- 什么时候数据在多线程并发的环境下存在安全性问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,
三个条件:
条件一:多线程并发
条件二:有数据共享
条件三:共享数据有修改的行为
满足以上三个条件之后,就会存在线程安全问题。
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();
	}
}
 
                    
                     
                    
                 
                    
                

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号