JavaSE之多线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_1018944104/article/details/82888669

一、线程与进程

  • 进程:内存中正在运行的一个应用程序。
  • 线程:进程中的一个执行流程。
  • 多线程:进程中有两个或以上的线程在轮询的执行。轮询的速度特别快,人感觉不到,所以多个线程就像并行执行一样。

二、线程的状态

1.线程的5种状态

  • New(新建)
  • Runnable(就绪)
  • Running(运行)
  • Blocked(阻塞)
  • Dead(死亡)

2.线程状态图解

3.线程生命周期

  • 新建:创建了一个线程对象,比如 Thread thread = new Thread();
  • 就绪:等待CPU调用执行,比如 thread.start();
  • 运行:CPU调用了处于就绪状态的一个线程,即执行了run();方法。
  • 阻塞:处于运行状态的线程,由于某种原因暂停了CPU的调用执行。
    • sleep() 和 join()阻塞:
      • 线程A调用sleep()方法,则线程A处于阻塞状态,等待超时才能恢复到就绪状态。
      • 线程A调用线程B的join()方法,则线程A等待线程B执行完毕再恢复到就绪状态。
    • 同步阻塞
    • wait() 阻塞
  • 死亡:线程正常结束或异常退出。

三、创建线程的方式

1.主线程

  • 当Java程序启动时执行。
  • 主线程是产生其他子线程的线程。
  • 主线程的任务就是主方法。
  • 启动一个Java程序至少需要启动几个线程?两个,主线程和自动垃圾回收线程。

2.创建线程的两种方式:继承Thread和实现Runnable

2.1.继承Thread

示例:

class MyThread extends Thread{
	MyThread(){}
	MyThread(String name){
		super(name);
	}
	@Override
	public void run() {
		for (int i = 0; i < 4; i++) {
			System.out.println(Thread.currentThread());
		}
	}
}
public class TestThread {
	public static void main(String[] args) {
		//创建了一个子线程
		MyThread t = new MyThread("线程一");
		//启动线程
		t.start();
		//运行时异常IllegalThreadStateException,start()方法只能调用异常,也就是子线程只能启动一次
//		t.start();
		//不能这样启动子线程。因为线程是main,这是主线程里面的任务,跟子线程没有关系
//		t.run();
	}
}

2.2.实现Runnable

示例:

class ThreadDemo implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}
public class TestThread2 {
	public static void main(String[] args) {
		//注意:这不是一个线程对象
		ThreadDemo demo = new ThreadDemo();
		//可以给线程起名
		Thread t =new Thread(demo,"张三");
		//启动线程时,就能调用demo对象中的run()方法了。
		t.start();
	}
}

2.3.两种创建方式的区别

  • 继承Thread类后就是线程类,而实现Runnable接口不是线程类;
  • 继承Thread类的所有功能,实现接口只有run()方法;
  • 实现接口利于资源共享。

四、线程的优先级

1.什么是线程的优先级?有什么用处?取值范围是什么?

  • 理论上,优先级高的线程比优先级低的线程获得更多的CPU时间,相等优先级的线程有同等的权利使用CPU。
  • 实际上,线程获得CPU的时间通常由包括优先级在内的多个因素决定。
  • 线程优先级从低到高的范围是1-10。

2.如何设置和查看线程的优先级?

示例:

class SubThread2 implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(Thread.currentThread().getName() + ":玩儿游戏");
		}
	}
}
public class TestSubThread2 {
	public static void main(String[] args) {
		SubThread2 th = new SubThread2();
		Thread zhangsan = new Thread(th,"zhangsan");
		Thread lisi = new Thread(th,"lisi");
//		zhangsan.setPriority(1);
//		lisi.setPriority(10);
		zhangsan.setPriority(Thread.MAX_PRIORITY);
		lisi.setPriority(Thread.MIN_PRIORITY);
		zhangsan.start();
		lisi.start();
		System.out.println(zhangsan.getPriority());
		System.out.println(lisi.getPriority());
	}
}

五、线程相关方法

常见方法:

  • sleep():阻塞线程,参数是long类型,表示当前线程被阻塞的毫秒数。在当前线程中执行Thread.sleep(2000);代码即可。
  • join():线程A调用线程B的join()方法,则线程A等待线程B执行完毕再恢复到就绪状态。
  • wait():放弃CPU时间,放弃锁,进入阻塞状态。调用方式:在当前线程中直接执行 waite()即可。可以带参或不带参。
  • notify()/notifyAll():唤醒通过waite()阻塞的线程,进入同步阻塞状态。调用方式:在当前线程中直接执行notify()即可。
  • interrupt():线程A中调用线程B的interrupt()方法,那中么线程B被断进入到异常处理流程,但前提是线程B必须处于sleep或join状态,否则不会引发这个异常。
  • yield():线程让步,让同等优先级或高优先级的线程获取CPU时间。在当前线程中执行Thread.yield();代码即可。

sleep() 与 wait() 的区别:

  • sleep() 必须指定参数,wait()参数是可选的;
  • sleep()放弃了CPU的时间但不放弃锁,wait()放弃了CPU的时间也放弃了锁。

示例:

class MyThread3 implements Runnable{
	private int i = 10;
	@Override
	public void run() {
		synchronized(this){
			String threadName = Thread.currentThread().getName();
			for (; i >= 0; i-- ){
				if (i == 5 && threadName.equals("t1")) {
					try {
//						Thread.sleep(1000);
						wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println(threadName + ":" + i);
				if (i == 0) {
					System.out.println("我醒了");
					notify();
				}
			}
		}
	}
}
public class TestSleepAndWait {
	public static void main(String[] args) {
		MyThread3 th = new MyThread3();
		Thread t1 = new Thread(th, "t1");
		Thread t2 = new Thread(th, "t2");
		t1.start();
		t2.start();
	}
}

六、线程的同步

1.什么是线程同步?

  • 当两个或两个以上的线程需要共享资源,它们需要某种方法来确定资源在某一刻仅被一个线程占用。达到此目的的过程叫做同步(synchronized)
  • 在资源共享的情况下,线程A访问某对象的同步资源获得同步锁,那么其他线程就不能再访问此对象的同步同步资源了,但是可以访问其他对象的资源方法,或者访问此对象的非同步资源。
  • 当一个线程需要锁定,它必须进入管程。管程是使用资源独占的一种权利

2.为什么使用同步?

保证对共享资源的读写一致性。

3.如何使用线程同步?

3.1.synchronized关键词

语法:

  • 同步方法:synchronized 方法名(){ }
  • 同步代码块:synchronized(对象){ //同步块 }
  • 以上二者方式锁的都是对象

获得锁和释放锁的标志:(没有明确的获得锁或释放锁的标志,但是可以推断出下面这些情况!)

  • 获得锁:当线程执行同步代码块或同步方法中的代码时,需要获得对象锁。
  • 释放锁:
    • 同步代码块或同步方法中的代码执行完毕;
    • return,break结束了方法;
    • 同步代码块或同步方法中出现了没有处理的异常;
    • 线程执行了wait()方法。

示例:同一银行账户存钱

class Bank implements Runnable{
	private int money;
	//同步方法
	/*private synchronized void setMoney(){
		this.money += 100;
		System.out.println(Thread.currentThread().getName() + "存了100元,余额" + this.money + "元");
	}*/
	private void setMoney(){
		this.money += 100;
		System.out.println(Thread.currentThread().getName() + "存了100元,余额" + this.money + "元");
	}
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			//同步代码块
			synchronized(this){
				setMoney();
			}
		}
	}
}
public class TestBank {
	public static void main(String[] args) {
		Bank bank = new Bank();
		Thread zhangsan = new Thread(bank,"张三");
		Thread lisi = new Thread(bank,"李四");
		zhangsan.start();
		lisi.start();
	}
}

3.2.ReentrantLock锁(公平锁)

语法:

try{
    //加锁lock()
}finally{
    //释放锁unlock()
}

优势:

  • 可以显示的加锁和释放锁。
  • 是一种公平锁,CPU会把锁让给等待时间最长的那个线程。

示例:多线程实现5个小朋友报数,每间隔一秒报数一次。

import java.util.concurrent.locks.ReentrantLock;
class Person implements Runnable{
	private int num = 1;
	//创建公平锁对象
	private ReentrantLock lock = new ReentrantLock();
	private void askNum(String name){
		try {
			//启动锁功能
			lock.lock();
			System.out.println(name + "\t" + num++);
		} finally {
			//finally保证一定释放锁
			lock.unlock();
		}
	}
	@Override
	public void run() {
		askNum(Thread.currentThread().getName());
	}
}
public class TestAskNum {
	public static void main(String[] args) {
		Person p = new Person();
		Thread t1 = new Thread(p, "郭靖");
		Thread t2 = new Thread(p, "杨康");
		Thread t3 = new Thread(p, "欧阳克");
		Thread t4 = new Thread(p, "周伯通");
		Thread t5 = new Thread(p, "黄蓉");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}
}

4.线程同步的特点

  • 每个对象只有一个锁(lock)与之关联。
  • 实现同步要很大的系统开销作为代价,甚至可能造成死锁,所以要尽量避免无谓的同步控制。

七、线程死锁

1.什么是死锁?

多个线程进入到了循环等待的状态,就造成了死锁。

2.死锁的特点?

  • 死锁是很难调试的错误,只有两个线程的时间段刚好符合时才能发生。
  • 我们在编写多线程并含有同步方法调用的程序中要格外小心,避免死锁发生。

3.如何写一个死锁?

示例:两个线程相互拿着对方需要的锁,你不给我,我就没法释放你需要的锁。

class ZS{
	public void say(){
		System.out.println("张三:你给我书,我就给你画");
	}
	public void get(){
		System.out.println("张三获得了书");
	}
}
class LS{
	public void say(){
		System.out.println("李四:你给我画,我就给你书");
	}
	public void get(){
		System.out.println("李四获得了画");
	}
}
class MyThread2 extends Thread{
	static ZS zhangsan = new ZS();
	static LS lisi = new LS();
	boolean tag = false;
	@Override
	public void run() {
		if(tag == true){
			//相互拿着对方需要的锁,你不给我,我就没法释放你需要的锁
			synchronized(zhangsan){
				zhangsan.say();
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (lisi) {
					zhangsan.get();
				}
			}
		} else {
			//相互拿着对方需要的锁,你不给我,我就没法释放你需要的锁
			synchronized(lisi){
				lisi.say();
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized(zhangsan){
					lisi.get();
				}
			}
		}
	}
	
}
public class TestLock {
	public static void main(String[] args) {
		MyThread2 zhangsan = new MyThread2();
		zhangsan.tag = true;
		MyThread2 lisi = new MyThread2();
		zhangsan.start();
		lisi.start();
	}
}

八、线程协作操作

1.多线程

示例:

class SubThread1 implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			System.out.println(Thread.currentThread().getName() + ":玩儿游戏");
		}
	}
}
public class TestSubThread1 {
	public static void main(String[] args) throws InterruptedException {
		//注意:这不是一个线程对象
		SubThread1 sub1 = new SubThread1();
		//创建线程对象
		Thread zhangsan = new Thread(sub1, "张三");
		Thread lisi = new Thread(sub1, "李四");
		zhangsan.start();
		lisi.start();
		//毫秒 等待足够长的时间
//		Thread.sleep(1000);
		//线程是否运行,是true
		/*if (zhangsan.isAlive() || lisi.isAlive()) {
			Thread.sleep(10);
		}*/
		//join() 让调用该方法的线程先执行,执行完了主线程再进入就绪状态。
		zhangsan.join();
		lisi.join();
		System.out.println("程序结束!");
	}
}

2.生产与销售模式

示例:实现生产包子和销售包子

class QingFeng {
	private int count;
	private boolean tag = false;
	synchronized public void put(int count){
		//true
		if (tag == true) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.count  = count;
		System.out.println("生产:" + this.count);
		tag = true;
		notify();
	}
	
	synchronized public void get(){
		//没有包子
		if (tag == false) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("消费:" + this.count);
		tag = false;
		notify();
	}
}
//生产
class Productor implements Runnable{
	private QingFeng qingfeng;
	public Productor(){}
	public Productor(QingFeng qingfeng){this.qingfeng = qingfeng;}
	@Override
	public void run() {
		for (int i = 1; i <= 5; i++) {
			qingfeng.put(i);
		}
	}
}
//销售
class Customer implements Runnable{
	private QingFeng qingfeng;
	public Customer(){}
	public Customer(QingFeng qingfeng){this.qingfeng = qingfeng;}
	@Override
	public void run() {
		for (int i = 1; i <= 5; i++) {
			qingfeng.get();
		}
	}
}
public class TestQingFeng {
	public static void main(String[] args) {
		QingFeng qf = new QingFeng();
		Productor p = new Productor(qf);
		Customer c = new Customer(qf);
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}

猜你喜欢

转载自blog.csdn.net/qq_1018944104/article/details/82888669
今日推荐