多线程

一、概述

多线程相较于单线程任务,CPU调度更加灵活,比如在进行耗时的I/O任务时,会向磁盘读取内容,耗时较长,在这段时间内,如果是单线程任务,CPU就会处于空闲状态,而多线程CPU就可以在等待磁盘读写的时间内进行其他任务了。

CPU分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

CPU抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度非常快,看上去就是在同一时刻运行。

多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

多线程的好处

线程的五个状态:
    创建 创建一个线程对象时就绪,此时线程不可运行,Thread thread = new Thread();
    就绪 线程对象创建完毕后,调用start()方法,进入线程队列随时准备运行(等待获取到cpu资源)
    运行 start()方法线程运行,线程执行的是run()方法中的内容
    堵塞 sleep()、suspend()方法,堵塞时线程不进入线程队列
    死亡 stop()方法( 过时)或run()方法执行完毕

线程安全:在并发的情况下,该代码经过多线程使用,线程的调度顺序不影响任何结果,线程不安全指代码可能会因调度顺序出现问题。

二、实现多线程的两种方式

开始之前,先贴一篇文章--->多线程的应用场景

1.Thread类实现多线程

Thread类是一个抽象类,继承了Runnable接口,类中为操作线程的方法,每个Thread类的实例代表一个线程对象。

class MyThread extends Thread{
	private String name;
	public MyThread(String name) {
		this.name=name;
	}
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(name+"运行,i="+i);
		}
	}
}
public class ThreadDemo1{
	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程A");
		MyThread mt2 = new MyThread("线程B");
		mt1.start();
		mt2.start();
	}
}

 2.Runnable接口实现多线程

Runnable接口中只有一个run()方法。接口主要是为了避免Java的单继承性,即一个子类不能有多个父类。

class MyThread2 implements Runnable{
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}
public class ThreadDemo2 {
	public static void main(String[] args) {
	MyThread2 mt1 = new MyThread2();
	MyThread2 mt2 = new MyThread2();
	new Thread(mt1,"线程A").start();
	new Thread(mt2,"线程B").start();
	}
}

两种方法的区别:

区别在于Runnable()能够实现数据共享,也就是允许多个线程同时处理一份数据,而Thread()则不能,且在Thread()中,一个线程对象不能重复调用start()方法,否则会抛出IllegalThreadStateException()异常。在源码中对start()方法有以下说明:

 /** *It is never legal to start a thread more than once.
     * In particular, a thread may not be restarted once it has completed
     * execution.
 **/

为什么不直接调用run()方法,而是调用start()?

run()方法只是一个普通方法,而start()方法中才是多线程操作,如果直接调用run(),只是当前线程执行run(),并非多线程操作。在start()方法中有一条add()语句,add()即是将线程加入到thread group中。

还可通过Callable 和 Future 创建线程。

线程操作方法:
join()可让线程强制运行,当此线程运行完毕(死亡后),其他线程才可以运行可指定时间,在指定时间内等待线程完成,单位毫秒    
sleep()线程休眠,停止运行一段时间后继续运行
interupt()中断线程,中断join()wait()sleep()的线程会清空线程的interrupt status并抛出InterruptedException
setPriority() 线程优先级,优先级高的会优先调度,可从1到10,MAX_PRIORITY=10 MIN_PRIORITY=1 NORMAL_PRIORITY=5,线程默认的优先级为NORMAL
yield()    线程的礼让,调用yield()的线程不执行,让其他线程执行,这个方法源码中有句注释/*It is rarely appropriate to use this method. It may be useful for debugging or testing purposes*/。。。。

三、同步和死锁

当多个线程操作同一份数据时,可能会出现问题。以下面的代码为例:

class Ticket implements Runnable {
	public int ticket = 5;
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if (ticket > 0) {
				try {
					Thread.sleep(300);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "  " + ticket--);
			}
		}
	}
}
public class Thread01 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket();
		new Thread(ticket, "线程A").start();
		new Thread(ticket, "线程B").start();
		new Thread(ticket, "线程C").start();
	}
}

会出现票数为0和-1的情况,这样的原因是当ticket为1时,某一线程在ticket--之前休眠了300ms,在这段时间内有其他线程也来处理这一数据,执行了ticket--的操作,这样上一线程再处理时ticket成为0,在ticket--操作就变为负数。

可以用同步的方法解决,加入synchronized代码块或定义synchronized方法。

过多的同步可能会造成死锁,尤其在同步锁中嵌套同步锁。死锁就是两个线程陷入互相等待的境地,使程序不能向下进行。

综上,多个线程共享同一资源时需要同步,但要注意过多的同步会带来死锁问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值