三、多线程

线程概念

1.进程与线程
进程:正在执行的程序为一个进程。进程负责了内存的划分。

windows号称多任务的操作系统,那么windows是同时运行多个程序么?
从宏观角度:windows同时执行多个任务。
从微观角度: cpu执行多个进程时,做了快速切换动作,即按照一定的算法切换执行进程。用户察觉不到。与其说是进程做资源抢夺动作,不如说是线程在做资源抢夺。

线程:线程在一个进程中负责代码的执行,即进程中的一个执行路径。

多线程:在一个进程中,有多个线程执行不同的任务。

2.多线程的好处与弊端

多线程的好处:

  • 解决了一个进程可以执行多个任务。
  • 提高了资源利用率

多线程的弊端:

  • 降低一个进程中线程的执行概率
  • 增加了cpu的负担
  • 会引发线程安全问题

线程的创建

方式一:继承Thread类,重写run方法。
方式二:实现Runnable接口,重写run方法。然后将对象传给Thread对象执行。
推荐使用第二种,java只支持单继承,允许多继承。

注意事项:
1.Runnable的实现类是线程对象么?
===》只有Thread或其子类才是线程对象。

2.为什么要把Runnable实现类的对象作为实参传给Thread对象呢?作用是什么》
===》Thread类把Runnable实现类中的run方法作为线程的任务代码来执行。

this和currentThread在下面代码中不是同一个对象。this在代码中指的的Runnale实现类的当前对象。Thread,currentThread()指的是线程对象。
在这里插入图片描述

线程的生命周期

1。创建(NEW)
创建线程对象后,该对象处于线程的新建状态,JVM只是为其对象分配了对象。没有线程的任何动态性能。

2。可运行状态(Runnable)
线程对象调用start()方法后,线程就会进入可运行状态。此时获得了CPU的等待资格,但没有获取CPU的执行权,线程能否执行还需要CPU进行资源调度。

3.运行状态(Running)
线程获取CPU的执行权,此时进入运行状态。由于cpu不断做出切换动作,所以,此时线程会在运行状态与可运行状态之间来回切换。

4.阻塞状态(Blocked)
一个正在执行的线程在做耗时的IO流操作、wait()、sleep()等阻塞方法时,会进入阻塞状态(Blocked)

-----线程在获取一个对象的同步锁时,如果该锁没有被别的线程释放,则当前线程进入阻塞状态。只有当同步锁被释放后,该线程才会重新进入可运行状态。

-----线程在执行耗时的IO流操作时,会进入阻塞状态。当IO流结果有返回,才会重新进入可运行状态。

-----线程执行Thread.sleep(1000)方法时,会进入阻塞状态。只有当休眠时间结束,才会进入可运行状态。

-----当线程调用了某个对象的wait()方法时,也会使线程进入阻塞状态,notify()方法唤醒。

——一个线程调用了另一个线程的join()方法时,当前线程进入阻塞状态。等新加入的线程运行结束后会结束阻塞状态,进入就绪状态。

线程从阻塞状态只能进入就绪状态,而不能直接进入运行状态,即结束阻塞的线程需要重新进入可运行池中,等待系统的调度。

5.死亡状态(Terminated)
线程的run()方法正常执行完毕或者线程抛出一个未捕获的异常(Exception)、错误(Error),线程就进入死亡状态。一旦进入死亡状态,线程将不再拥有运行的资格,也不能转换为其他状态。

创建(new) —》可运行状态(Runnable)–》(《–》阻塞状态–》 )—》 运行状态 (Running) --》死亡状态(Terminated)

在这里插入图片描述

线程常用方法

Thread(String name) 重写Thread的带参构造方法,指定线程名称。

t.setName(String name) 设置线程名称

t.setPriority(int i) 设置线程的优先级,设置数字越大,优先级越高 【1-10】
t.getPriority() 获取线程的优先级。(线程的优先级默认为5)

Thread.sleep(1000) 让线程休眠进入阻塞状态。这个方法由哪一个线程来执行,哪一个线程就休眠。不会释放锁资源

Thread.currentThread() 这个方法由哪一个线程来执行,就返回哪一个线程对象。

public static void main(String[] args) throws IOException, InterruptedException, ParseException {
		
		App2 a=new App2("看片线程");
		a.start();
		System.out.println("看片线程优先级:"+a.getPriority());
		
		System.out.println("主线程优先级:"+Thread.currentThread().getPriority());

	}

线程安全问题

例子:
三个窗口同时卖票,会出现票据卖出重复问题。
在这里插入图片描述

出现线程安全问题根本的原因:

1.存在多个线程,并且线程之间共享资源。
2.有多个语句操作共享资源

线程安全问题解决方案

sun公司提供线程同步机制解决线程安全问题。
同步机制:

  • 同步代码块
  • 同步方法

同步代码块注意事项:
1.任意一个对象都可以作为锁对象。因为所有对象内部都维护了一个状态位,java同步机制就是使用了对象中的状态位作为锁标识。
2.在同步代码块中调用Thread.sleep()方法,并不会释放锁资源。【上卫生间关门】
3.同步机制影响执行效率,所以只有在真正线程安全问题下才使用。
4.多线程操作的锁对象必须是唯一共享的。

同步方法注意事项:
静态的同步方法,锁对象是字节码文件。
普通的同步方法,锁对象是this对象。
同步方法的锁是固定的。

总结:
1.同步代码块的锁可以是任意的对象,同步方法的锁是固定的。静态方法的锁对象是字节码文件,普通方法的锁对象是this对象。

2.锁对象必须是线程共享的,否则锁不住。

3.在同步代码块或同步方法中,调用Thread.sleep()方法,是不会释放锁对象的。调用wait()方法是会释放锁对象的。

售票问题代码实现:

package com.gupao.demo;

import java.util.concurrent.CountDownLatch;

public class Window  extends Thread{
	
	
	public Window(String name) {
		super(name);
	}
	
	public static int tick_num=50;
	
	@Override
	public void run() {
		while(true) {
				
			synchronized ("锁") {
				
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				if(tick_num>0) {
					tick_num--;
					System.out.println(Thread.currentThread().getName()+"卖出一张票,还剩:"+tick_num);					
				}else {
					System.out.println("没票了");
					return;
				}
			
			 }		
	}
	
	public static void main(String[] args) {		
		Window a1=new Window("窗口1");
		Window a2=new Window("窗口2");
		Window a3=new Window("窗口3");
		
		a1.start();
		a2.start();
		a3.start();
	}
}

死锁

线程同步机制引发了死锁问题。

死锁问题出现的根本原因:
存在两个或两个以上的线程
存在两个或两个以上的共享资源

死锁问题的解决方案: 没有方案,只能尽量的去避免

代码模拟问题:
张三李四都去争抢遥控器和电池

public class App2  extends Thread{
	
	public App2(String name) {
		super(name);
	}
	
	
	
	@Override
	public void run() {
		if("张三".equals(this.getName())) {
			synchronized ("电池") {
				System.out.println("张三拿到了电池,准备去拿遥控器");
				synchronized ("遥控器") {
					System.out.println("张三拿到了电池和遥控器");
				}
				
			}
		}
		
		if("李四".equals(this.getName())) {
			synchronized ("遥控器") {
				System.out.println("李四拿到了遥控器,准备去拿电池");
				synchronized ("电池") {
					System.out.println("李四拿到了电池和遥控器");
				}
				
			}
		}
	}



	public static void main(String[] args) throws IOException, InterruptedException, ParseException {
		App2 a=new App2("张三");
		App2 a2=new App2("李四");

		a.start();
		a2.start();
		
	}
	

}

线程通信

wait()
一个线程执行wait方法,那么该线程会进入以锁对象为标识符的的线程池中。并且会释放锁资源

notify()
唤醒线程池中任何一个线程,那么该线程会唤醒以锁对象为标识符的线程池中的任意一个线程。

notifyAll(),唤醒锁对象中所有的线程。

注意事项:
wait和notify方法是属于Object对象的。
wait与notify方法必须在同步代码块或同步方法中使用。
wait和notiry必须由锁对象进行调用。

生产者生产产品,消费者去消费
代码实现:

public class Product {
	String name;
	double price;
	
	public static void main(String[] args) {
		Product t=new Product();
		
		Customer c=new Customer(t);
		Producter p=new Producter(t);
		c.start();
		p.start();
		
	}
}

//生产者
class Producter extends Thread{
	
	Product p;
	
	public Producter(Product p) {
		super();
		this.p = p;
	}
	@Override
	public void run() {
		
		int i=0;
		while(true) {
			synchronized (p) {
				if(i%2==0) {
					p.name="苹果";
					p.price=12;
				}
				if(i%2!=0) {
					p.name="香蕉";
					p.price=5;
				}
				i++;
				System.out.println("生产者生产了"+p.name +",价格为"+p.price+"钱");
				try {
					p.notify();
					p.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		
	}
}

//消费者
class Customer extends Thread{
		Product p;
	
		
		public Customer(Product p) {
			super();
			this.p = p;
		}



		@Override
		public void run() {
			while(true) {
				 synchronized (p) {
					 System.out.println("消费者买了"+p.name+",花了"+p.price+"钱");
					 try {
						p.notify();
						p.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				 }
			}
		}
		
		
}

停止线程

t.stop() 停止线程,该方法已经过时,不安全的方法。
t.interrupt() 唤醒线程池中的线程,并且线程会接收到一个InterruptException 异常。

线程的停止:
停止一个线程一般通过变量去控制。
如果要停止一个阻塞状态的线程,还要使用变量配合notify()或interrupt()方法来控制。

package com.gupao.demo;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class Demo  implements Runnable{

	boolean flag=true;
	@Override
	public synchronized void run() {
		int i=0;
		while(flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				System.out.println(e);
			}
			System.out.println(Thread.currentThread().getName()+":"+i);
			i++;
		}
		
		
	}
	 
	
	
	public static void main(String[] args) {
		Demo r=new Demo();
		
		Thread t=new Thread(r,"线程1");
		
		t.start();
		
		for(int i=0;i<1000000;i++) {
			if(i==100000) {
				r.flag=false;
				//方式一:使用notify,必须在同步方法中由锁对象调用
//				synchronized (r) {
//					r.notify();
//				}
				
				//方式二:使用interrupt
				t.interrupt();
			}
			
		}
		
	
}

守护线程
守护线程(后台线程):在一个进程中,如果只剩下守护线程,那么守护线程也会死亡。

t.setDaemon(true) 设置该线程为守护线程。然后调用start()开启线程。【设置守护线程动作必须在start方法之前】

t.setDaemon(true)
t.start() 先设置守护标志,在开启

开启主线程和守护线程,此时JVM中自定义线程之后主线程和守护线程,当主线程执行完毕就会销毁,此时只剩下守护线程,然后守护线程也会死亡。

package com.gupao.demo;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;

public class Demo  extends  Thread{
	
	@Override
	public void run() {
		while(true) {
			System.out.println(Thread.currentThread().getName());
		}
	}
	
	public static void main(String[] args) {
		
		Demo d=new Demo();
		d.setDaemon(true);

		d.start();
	
		
		for(int i=0;i<10000;i++) {
			System.out.println(Thread.currentThread().getName());
		}
		
	}
	
}

join线程
t2.join() 加入。一个线程执行该方法,那么就会有的线程加入。执行该语句的线程必须先让给新的线程执行。新的线程执行完毕才能继续执行。【线程让步】

t2.start();
t2.join(); //先开启线程在加入线程

package com.gupao.demo;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;


public class App6{
	
	 
	 public static void main(String[] args) {
		
		 Mother m=new Mother();
		 m.start();
		 
	}
}

class Mother extends  Thread{
	
	@Override
	public void run() {
		System.out.println("老妈做饭");
		System.out.println("老妈发现没有酱油,通知儿子下楼买酱油");
		
		try {
			Son  s=new Son();
			s.start();
			
			s.join();
			
			
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.out.println("老妈得到酱油,继续做饭");	
		System.out.println("香喷喷的饭来了。。。");	

	}
	
}
 class Son  extends  Thread{
	
	@Override
	public void run() {
		System.out.println("儿子下楼");
		System.out.println("儿子买酱油");
		System.out.println("儿子买完酱油,儿子上楼");		
	}
	
}
 
 

 

控制台打印:

老妈做饭
老妈发现没有酱油,通知儿子下楼买酱油
儿子下楼
儿子买酱油
儿子买完酱油,儿子上楼
老妈得到酱油,继续做饭
香喷喷的饭来了。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值