多线程基础知识及使用

多线程

含义:

多线程,说白了就是多条执行路径,原来是一条路径,就主路径(main),现在是多
条路径。

常见概念:

程序:
Java源程序和字节码文件被称为“程序” ,是一个静态的概念。
进程:
执行中的程序叫做进程,是一个动态的概念。

  • 进程是程序的一次动态执行过程, 占用特定的地址空间。
  • 每个进程由3部分组成:cpu,data,code。每个进程都是独立的,保有自己的cpu时间,代码和数据,即便用同一份程序产生好几个进程,它们之间还是拥有自己的这3样东西。
  • 多任务(Multitasking)操作系统将CPU时间动态地划分给每个进程,操作系统同时执行多个进程,每个进程独立运行。以进程的观点来看,它会以为自己独占Cpu的使用权。
  • 进程查看方式:①Windows系统: Ctrl+Alt+Del ②Unix系统: ps or top\

线程:
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

  • 一个进程可拥有多个并行的(concurrent)线程
  • 一个进程中的线程共享相同的内存单元/内存地址空间可以访问相同的变量和对象,而且它们从同一堆中分配对象通信、数据交换、同步操作
  • 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。

注:
程序是指令的集合,代码的集合 ; 而进程是动态的概念,当程序在执行时,系统分配进程 ; 多线程是在同一进程下,充分利用资源 ,多条执行路径,共享资源 (cpudata code) 。
优点:
资源利用率更好;程序设计在某些情况下更简单;程序响应更快。
缺点
设计更复杂。

创建多线程方法
方法一: 继承 Thread类 +重写 run()方法

public class TestThread {
	public static void main(String[] args) {	----------->(主线程)main方法
	// 创建线程类对象
	SomeThread oneThread = new SomeThread();	----------->new 创建对象
	// 启动线程
	oneThread.start();							----------->调用start()方法启动另一个线程
	}
}
// 创建线程类
class SomeThead extends Thread{
	@Override
	public void run(){
	System.out.println("我是重写的run方法");
	}
}

方法二: 实现Runnable 接口的实现类 + 重写run()方法

public class TestThread2 implements Runnable {
	public static void main(String[] args) {
		//创建线程对象
		SomeRunnable r1 = new SomeRunnable()----------->创建线程对象
		Thread thread1 = new Thread(r1);		----------->目地是为了使用它的start方法
		//开启线程
		thread1.start();						----------->调用start()方法
		//创建线程对象
		Thread thread2 = new Thread(new SomeRunnable());----------->创建线程对象
		//开启线程
		thread2.start();						----------->调用start()方法
	}
}
// 创建Runnable子类
class SomeRunnable implements Runnable{
	@Override
	public void run(){
	System.out.println("我是重写的run方法");
	}
}

方法三: 创建实现Callable接口的实现类 + 重写 call()方法 (了解)

public class CallAbleTest {
	public static void main(String[] args) throws Exception{
		MyCallable callable = new MyCallable();
		// 将Callable包装成FutureTask,FutureTask也是一种Runnable
		FutureTask<Integer> futureTask = new FutureTask<>(callable);
		// 将FutureTask包装成Thread
		new Thread(futureTask).start();
		System.out.println(futureTask.isDone());
		System.out.println(futureTask.get());
	}
}
class MyCallable implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = 0; i <= 100000; i++) {
		sum += i;
		}
		return sum;
	}
}
  • Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
  • .Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
  • Callable: ThreadRunnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
  • 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
  • hread类是实现RunnableCallable封装成FutureTaskFutureTask实现RunnableFutureRunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现

线程池

  • 线程池就是首先创建一些线程,它们的集合称为线程池。

线程池的工作机制

  • 在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。
  • 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。

使用线程池的原因

  • 多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。这时,线程池就是最好的选择了

线程的五种状态
线程的五种状态图
新建状态
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
**就绪状态 **
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
**运行状态 **
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

  1. 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
  2. 同步阻塞:线程在获取 synchronized同步锁失败(因为同步锁被其他线程占用)。
  3. 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O处理完毕,线程重新转入就绪状态。

停止线程
死亡状态是线程生命周期中的最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它的全部工作;另一个是线程被强制终止,如通过执行 stop或 destroy 方法来终止一个线程。但是,不要调用 stop,destory 方法 ,太暴力,一盆冷水让其停止。
在这里插入图片描述

public class TestThreadCiycle implements Runnable {
		String name;
	boolean live = true;
	public TestThreadCiycle(String name) {
	super();
	this.name = name;
	}
	public void run() {
		int i=0;
		while(live){

	System.out.println(name+(i++));
		}
	}
	public void terminate(){
		live = false;
	}
	public static void main(String[] args) {
		TestThreadCiycle ttc = new TestThreadCiycle("线程A:");
		Thread t1 = new Thread(ttc); //新生状态
			t1.start(); //就绪状态
		for(int i=0;i<1000;i++){
			System.out.println(i);
		}
		ttc.terminate();
		System.out.println("ttc stop!");
	}
}

有三种方法可以让我们暂停Thread执行:
1.sleep方法:需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行的机会,也可以让低优先级的线程得到执行机会。但是sleep() 方法不会释放“锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据。
2.yield方法:yield() 方法和 sleep() 方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield() 方法只是使当前线程重新回到可执行状态,所以执行yield() 的线程有可能在进入到可执行状态后马上又被执行。让出CPU的使用权,从运行态直接进入就绪态。让CPU重新挑选哪一个线程进入运行状态。
3.join方法:方法会使当前线程等待调用 join() 方法的线程结束后才能继续执行

线程中常用到的方法:
线程中常用到的方法
线程优先级范围从 1 到 10
注意:
优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用
优先级低的线程。

线程同步和死锁问题
线程安全:
在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就有可能造成数据的不准确
线程同步 synchronized
即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。
是指协同、协助、互相配合。在Java里面,通过 synchronized 进行同步的保证。
它包括两种用法:synchronized 方法synchronized 块

synchronized 方法
通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。

public synchronized void accessVal(int newVal);

synchronized 方法控制对类成员变量的访问:每个对象对应一把锁,每个
synchronized 方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻
塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的
线程方能获得 该锁,重新进入可执行状态。
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效
率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生
命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不
会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明
为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更
好的解决办法,那就是 synchronized 块。
synchronized 块
在代码块前加上 synchronized 关键字,并指定加锁的对象

synchronized(syncObject){
//允许访问控制的代码
}
/**
* 测试同步问题
* @author Administrator
*
*/
public class TestSync {
	public static void main(String[] args) {
		Account a1 = new Account(100,"高");
		Drawing draw1 = new Drawing(80,a1);
		Drawing draw2 = new Drawing(80,a1);
			draw1.start(); //你取钱
			draw2.start(); //你老婆取钱
	}
}
/*
* 简单表示银行账户
*/
class Account {
		int money;
		String aname;
	public Account(int money, String aname) {
		super();
		this.money = money;
		this.aname = aname;
	}
}
/**
* 模拟提款操作
* @author Administrator
*
*/
class Drawing extends Thread{
		int drawingNum; //取多少钱
		Account account; //要取钱的账户
		int expenseTotal; //总共取的钱数
public Drawing(int drawingNum,Account account) {
		super();
	this.drawingNum = drawingNum;
	this.account = account;
}
@Override
public void run() {
	draw();
}
void draw(){
	synchronized (account) {
		if(account.money-drawingNum<0){
			return;
		}
		try {
			Thread.sleep(1000); //判断完后阻塞。其他线程开始运行。
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			account.money-=drawingNum;
			expenseTotal+=drawingNum;
		}
	System.out.println(this.getName()+"--账户余
	额:"+account.money);
	System.out.println(this.getName()+"--总共取
	了:"+expenseTotal);
	}
}
上面这种方式叫做:互斥锁原理。利用互斥锁解决临界资源问题。

死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

class Lipstick{
}
class Mirror{
}
class Makeup extends Thread {
	int flag;
	String girl;
	static Lipstick lipstick=new Lipstick();
	static Mirror mirror= new Mirror();
	@Override
	public void run() {
		// TODO Auto-generated method stub
		doMakeup();
	}
	void doMakeup(){
		if(flag==0){
			synchronized (lipstick) {
				System.out.println(girl+"拿着口红!");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (mirror) {
					System.out.println(girl+"拿着镜子!");
				}
			}
		}
		else{
			synchronized (mirror) {
				System.out.println(girl+"拿着镜子!");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				synchronized (lipstick) {
					System.out.println(girl+"拿着口红!");
				}
			}
		}
	}
}
public class TestDeadLock {
	public static void main(String[] args) {
		Makeup m1 = new Makeup(); m1.girl="大丫"; m1.flag=0;
		Makeup m2 = new Makeup(); m2.girl="小丫"; m2.flag=1;
		m1.start();
		m2.start();
	}
}

如何解决死锁问题:

  1. 往往是程序逻辑的问题。需要修改程序逻辑。
  2. 尽量不要同时持有两个对象锁。 如修改成如下:
void doMakeup(){
if(flag==0){
	synchronized (lipstick) {
		System.out.println(girl+"拿着口红!");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	synchronized (mirror) {
		System.out.println(girl+"拿着镜子!");
	}
}
else{
	synchronized (mirror) {
		System.out.println(girl+"拿着镜子!");
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		synchronized (lipstick) {
			System.out.println(girl+"拿着口红!");
		}
	}
}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值