Java多线程详解

Java多线程详解

什么是线程,线程是现代操作系统调度的最小单元,也叫轻量级进程,在一个进程里可创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换,让使用者感觉到这些线程在同时执行。
1、线程的状态
Java线程在运行的生命周期中可能处于6种不同的状态,在给定的一个时刻,线程只能处于其中的一个状态:

NEW: 新建状态,线程对象已经创建,但尚未启动
RUNNABLE:就绪状态,可运行状态,调用了线程的start方法,已经在java虚拟机中执行,等待获取操作系统资源如CPU,操作系统调度运行。
BLOCKED:堵塞状态。线程等待锁的状态,等待获取锁进入同步块/方法或调用wait后重新进入需要竞争锁
WAITING:等待状态。等待另一个线程以执行特定的操作。调用以下方法进入等待状态。 Object.wait(), Thread.join(),LockSupport.park
TIMED_WAITING: 线程等待一段时间。调用带参数的Thread.sleep, objct.wait,Thread.join,LockSupport.parkNanos,LockSupport.parkUntil
TERMINATED:进程结束状态。

2、线程的创建四种方式

  • 继承Thread类创建线程
  • 实现Runnable接口创建线程
  • 使用Callable和Future创建线程
  • 使用线程池例如用Executor框架

3、java 并发编程的三大特点
原子性:单个操作不能被打断,要么全部执行完毕,要么不执行。在这点上有点类似于事务操作,要么全部执行成功,要么回退到执行该操作之前的状态。
可见性:一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量这种修改(变化)。
有序性:对于一个线程的代码而言,我们总是以为代码的执行是从前往后的,依次执行的。这么说不能说完全不对,在单线程程序里,确实会这样执行;但是在多线程并发时,程序的执行就有可能出现乱序。用一句话可以总结为:在本线程内观察,操作都是有序的;如果在一个线程中观察另外一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义(WithIn Thread As-if-Serial Semantics)”,后半句是指“指令重排”现象和“工作内存和主内存同步延迟”现象。

我们看看下面这段代码:

public class TestMain {
	private  int i = 0;
	public static void main(String[] args) {
		TestMain aa = new TestMain();
		aa.go();
	}
	void go(){
		for(int j = 0;j<10;j++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					for(int k = 0;k<100000;k++){
						i++;
						System.out.println(i);
					}	
				}			
			}).start();
		}
	}
}

都知道,当go函数在执行的时候,创建的10个线程,每个线程都需要加100000次,由于没有满足并发的三大特点。这里计算出来的结果就会存在问题。
原子性:这里的i++,看上去只有一步操作,实际上需要取出来i的值,然后加上1,在写进去。根本就不能满足。
当我们使用synchronized之后。这个关键字能够保证原子性,和可见性。也就是说每个时刻只有一个线程能够进入到这个方法中,必须一个线程执行完这个片段之后,其它的线程才能够调用。



public class TestMain {
	int i = 1;
	public static void main(String[] args) {
		TestMain aa = new TestMain();
		aa.go();
	}
	
	void go(){
		for(int j = 0;j<10;j++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					for(int k = 0;k<100000;k++){
						synchronized(TestMain.class){
							i++;
							System.out.println(i);
						}
					}
				}			
			}).start();
		}
	}
}

当加上同步锁之后,得到正确的结果。
当线程获取锁时,JVM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量。当线程执行到synchronized的时候,获取锁。由于锁的原子性,必须把同步块中的内容执行完成之后,才会释放锁。当线程释放锁时,JVM会把该线程对应的本地内存中的共享变量刷新到主内存中.
同理:使用 重入锁也能实现。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestMain {
	private volatile int i = 1;
	private Lock lock = new ReentrantLock();
	public static void main(String[] args) {
		TestMain aa = new TestMain();
		aa.go();
	}
	void go(){
		for(int j = 0;j<10;j++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					for(int k = 0;k<100000;k++){
							lock.lock();
							try{
							i++;
							System.out.println(i);
							}finally{
								lock.unlock();
							}	
					}	
				}			
			}).start();
		}
	}
}

看一个单利模式



public class TestMain {
	private TestMain(){}
	private volatile TestMain instance;
	public TestMain getInstance(){
		if(instance == null){
			synchronized(TestMain.class){
				if(instance == null){
					instance = new TestMain();
				}
			}
		}
		return instance;
	}
}

这是一个常见的支持多线程的单利模式,使用了双重检查。和volatile。volatile的作用是让线程修改了这个变量之后,其它线程能够立马知道。volatile的本意是:不稳定的,当线程使用到这个数据的时候,都会从内存中读取。双重检查也在一定程度上提高了程序运行效率,当在锁外部从内存中读取到instance已经被初始化。这时候,就会不在执行加锁的操作。直接返回instance。当发现instance没有被初始化化,在执行加锁操作之前,其它线程已经将instance初始化了。这时候,同步锁的机制就会从内存中去读取instance。判断一下。直接返回。双重锁机制在多线程单利是很重要的。它保证了高效率的有且仅有一次的初始化。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值