Java多线程机制和用法

一、线程的引入和实现

    在传统的操作系统中,作为拥有资源的基本单位和独立调度、分派的基本单位都是进程,也因此导致线程在创建、撤销和切换中都会占用OS较大的时空资源。但是也正因如此,在OS中所设置的进程,数目不宜过多,切换频率不宜过快,这也就限制了OS并发程度的进一步提高。所有后来人们提出了线程的概念,为了进一步提高OS的并发程度和系统吞吐量。线程是比进程更轻量级的调度执行单位,线程的引入,可以把一个进程的资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O等),又可以作为CPU的基本调度单位。

    在Java语言中提供了在不同硬件和OS下对线程操作的统一处理,每个已经执行start()且还未结束的java.lang.Thread类的实例都代表了一个线程。如果查看Thread类的源码会发现,它所有的关键方法都是native方法。也就意味着此方法使用平台相关的技术实现的,比如由C++语言编写的。线程的实现由3种方式:使用内核线程实现、使用用户线程实现和前两者混合实现。而JVM采用的第一种实现方式,系统内核线程实现,这种线程由底层系统内核来完成线程切换和调度。

二、线程调度和状态转换

    线程调度是指系统为线程分配CPU使用权的过程。主要调度方式有两种,协同式调度(Cooperative Threads-Scheduling)和抢占式调度(PreemptiveThreads-Scheduling)。协同式多线程系统中,线程的执行时间由线程自己控制,线程把自己执行完了,再通知系统切换到其他线程。优点就是,实现简单,多线程顺序执行,也没有什么线程同步问题。缺点也很明显,执行时间对系统来说不可控制,也相当不稳定如果一个线程阻塞了,可能导致整个系统崩溃。抢占式多线程系统,每个线程的执行时间有系统分配,线程的切换调度不由线程本身决定(Java 中可以用Thread.yield()让出执行权,但是要获取执行权,线程本身没有办法,只能等待系统调度)。Java采用的第二种抢占式,虽然由系统调用,但是Java也可以给OS提出建议,比如给某个线程多点执行时间,通过设置线程的优先级完成。Java中,共设置了10个线程优先级,优先级越高越容易被OS选中执行。不过,由于Java的线程是映射到OS的原生线程上的,所以线程调度最终还是由OS 决定,在Windows OS中共设置了7个优先级,所以会造成优先级相同的情况,比如Java中的优先级4和5映射到Windows上可能是同一个优先级。Thread类提供了三个常量MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY分别是1、5、10,优先级跨度大还有点作用。

Java中共有5种线程状态,任意时刻,一个线程只能处于一种状态。

 

  • 新建(New):创建后尚未启动的线程处于此状态。
  • 运行(Runnable):此状态包含了OS线程中的Running和Ready两个状态,也就是说,处于此状态的线程可能正在运行,也有可能正在等待CPU。
  • 等待(Waiting):此状态的线程不会被分配CPU执行时间,需要被其他线程显示的唤醒或由系统唤醒,调用没有设置Timeout的wait()方法的线程必须调用notify()唤醒。而调用Thread.sleep(timeout)、Object.wait(timeout)、Thread.join(timeout)等方法,timeout时间结束后由系统自动唤醒。
  • 阻塞(Blocked):阻塞态和等待态的区别是:阻塞状态是在等待一个对象锁,知道其他线程放弃这个锁,而等待状态则在一段时间后,由系统或唤醒动作唤醒。在程序等待进入临界区(同步区域)的时候,线程会进入此状态。
  • 结束(Terminated):又称dead线程。已终止的线程进入此状态。

线程状态的转换图如下:

三、Java中创建线程的方法

创建一个线程有两种方法:直接继承Thread类和实现Runnable接口。具体用法如下:

//一个线程输出大写字母 ,一个线程输出小写字母
public class ThreadTest {
	public static void main(String[] args) {
		//创建线程时,传一个Runnable的实现类,就会执行重写的run方法
		//线程创建后,并不会直接执行,只有调用start方法后,才会被系统执行
		new Thread(new PrintUpper()).start();
		//PrintLower继承自Thread
		//创建对象就是创建了一个线程
		new PrintLower().start();
	}
}
/**
 * 打印大写字母
 * 当只需要覆写run方法时,优先选择实现Runnable接口
 * 因为接口更灵活,一个类可以实现多个接口
 * @author guoke
 *
 */
class PrintUpper implements Runnable{
	//线程运行时,系统会自动调用run方法
	@Override
	public void run() {
		for(char c='A';c<='Z';c++){
			System.out.print(c);
			/**
			 * 打印一个字母,睡眠10ms
			 * 睡眠期间,线程会放弃cpu
			 * 睡眠结束后进入等待队列.重新获得cpu后继续执行
			 * 本方法必须try-catch
			 */
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}
/**
 * 打印小写字母 
 * 继承自Thread类,子类本身就是一个线程,创建对象就是创建了一个线程
 * 优点:创建线程简单
 * 缺点:java是单继承,有局限性
 */
class PrintLower extends Thread{
	
	@Override
	public void run() {
		for(char c='a';c<='z';c++){
			System.out.print(c);
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}

四、线程同步

    线程同步是指在多线程环境下,当多线程并发执行时,多个线程能不出差错的访问共享数据。Java多线程的同步依靠的是对象锁机制synchronized关键字的背后就是利用了封锁来实现对共享资源的互斥访问。下面以经典的懒汉式单例模式为例:

public class SingletonDemo {
	public static void main(String[] args) {
		Thread t1=new Thread(new Runnable() {
			@Override
			public void run() {
				Single s3=Single.getInstance2();
				System.out.println(s3);
				
			}
		});
		Thread t2=new Thread(new Runnable() {
			@Override
			public void run() {
				Single s4=Single.getInstance2();
				System.out.println(s4);
			}
		});
		t1.start();
		t2.start();
	}
}

class Single{
	private static  Single s=null;
	private Single(){}
	//方法 1
	public static synchronized Single getInstance(){
		if(s==null){
			// 1
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			s=new Single();
		}
		return s;
	}

被sychronized修饰的方法,意味着如果线程t1先执行了getInstance()方法,在执行期间,其他任何线程都不能在执行此方法。如果不是sychronized方法,当t1执行到代码1处时睡眠100毫秒,此时线程t2执行if(s==null)为true也进入了代码1处,执行完此方法执行了

s=new Single()创建了一个Single对象,然后线程t1被唤醒同样执行了s=new Single()又创建了一个对象。这时就会出现2个Single对象,也就不是单例模式了,所以不加synchronized修饰,此单例模式不是线程安全的。

最后对synchronized关键字做几点说明:

第一:synchronized用来标识一个普通方法时,表示一个线程要执行该方法,必须取得该方法所在的对象的锁。
第二:synchronized用来标识一个静态方法时,表示一个线程要执行该方法,必须获得该方法所在的类的类锁,也就是Class对象的锁。
第三:synchronized修饰一个代码块。synchronized(obj) { //code.... }。表示一个线程要执行该代码块,必须获得obj的锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值