Java并发编程(一)
1、定义
线程是现代操作系统调用的最小单位,也叫轻量级进程,在一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
2、一个普通的java程序中包含哪些线程
下面是在jdk1.8.0_60环境下的代码输出。
1 import java.lang.management.ManagementFactory; 2 import java.lang.management.ThreadInfo; 3 import java.lang.management.ThreadMXBean; 4 5 public class MultiThread { 6 public static void main(String[] args) { 7 // 获取java线程管理MXBean 8 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); 9 ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); 10 // 遍历线程信息,仅打印线程ID和线程名称信息 11 for (ThreadInfo threadInfo : threadInfos) { 12 System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName()); 13 } 14 System.out.println(System.getProperty("java.version")); 15 } 16 }
输出结果:
[5] Attach Listener //负责接收到外部的命令,对该命令进行执行然后把结果返回给发送者 [4] Signal Dispatcher //分发处理发送给JVM信号的线程 [3] Finalizer //调用对象finalize方法的线程 [2] Reference Handler //清除Reference的线程 [1] main //main线程,用户程序入口 1.8.0_60 //jdk版本号
3、优先级
在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1-10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多余优先级低的线程。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略线程优先级的设定。
4、优先级例子
我的电脑是win7,64,jdk1.8,输出结果表明优先级生效了。
1 package thread; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.concurrent.TimeUnit; 6 7 public class Priority { 8 private static volatile boolean notStart = true; 9 private static volatile boolean notEnd = true; 10 11 static class Job implements Runnable{ 12 private int priority; 13 private long jobCount; 14 public Job(int priority){ 15 this.priority = priority; 16 } 17 @Override 18 public void run() { 19 while(notStart){ 20 Thread.yield(); 21 } 22 while(notEnd){ 23 Thread.yield(); 24 jobCount++; 25 } 26 } 27 } 28 public static void main(String[] args) throws InterruptedException { 29 List<Job> jobs = new ArrayList<Job>(); 30 for(int i = 0; i < 10; i++){ 31 //设置优先级 32 int priority = i < 5 ? Thread.MIN_PRIORITY:Thread.MAX_PRIORITY; 33 Job job = new Job(priority); 34 jobs.add(job); 35 Thread thread = new Thread(job,"Thread:" + i); 36 thread.setPriority(priority); 37 thread.start(); 38 } 39 notStart = false; 40 TimeUnit.SECONDS.sleep(2); 41 notEnd = false; 42 for(Job job : jobs){ 43 System.out.println("Job Priority : " + job.priority 44 + ", Count : " + job.jobCount); 45 } 46 } 47 }
输出结果
1 Job Priority : 1, Count : 6607305 2 Job Priority : 1, Count : 3393520 3 Job Priority : 1, Count : 11684488 4 Job Priority : 1, Count : 8878447 5 Job Priority : 1, Count : 6607130 6 Job Priority : 10, Count : 12661281 7 Job Priority : 10, Count : 12467001 8 Job Priority : 10, Count : 12306292 9 Job Priority : 10, Count : 12425011 10 Job Priority : 10, Count : 12817522
5、线程的状态
java线程在运行的生命周期中可能处于以下6种不同的状态,某一刻只能是其中的一个状态。
NEW:初始状态,线程被构建,但是还没有调用start()方法;
RUNNABLE:运行状态,java线程将操作系统中的就绪和运行两种状态笼统地称作“运行中”;
BLOCKED:阻塞状态,表示线程阻塞于锁;
WAITING:等待状态,表示线程进入等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断);
TIME_WATING:超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的;
TERMINATED:终止状态,表示当前线程已经执行完毕。
6、启动和终止线程
构造线程:一个新构造的线程对象是由其parent线程来进行空间分配的,而child线程继承了parent是否为Daemon(守护线程)、优先级和加载资源的contextClassLoader以及可继承的ThreadLocal,同时还会分配一个唯一的ID来标识这个child线程。
启动线程:调用start()方法,其含义是:当前线程(即parent线程)同步告知Java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程。
过期的suspend()、resume()、stop():不建议使用,以suspend方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引起死锁问题。同样,stop方法在终结一个线程时不会保证线程资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。
可以利用中断或者一个volatile boolean 变量来控制是否需要停止任务并终止该线程。
7、线程间通信:volatile,synchronized
volatile:该关键字用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。
synchronized:该关键字可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。
对于同步块的实现使用monitorenter和monitorexit指令,而同步方法则是依靠方法修饰上的ACC_SYNCHRONIZED来完成的。两者本质都是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到synchronized所保护对象的监视器。
任意一个对象都拥有自己的监视器,当这个对象由同步块或者同步方法调用时,执行方法的线程必须先获取到该对象的监视器才能进入同步块或者同步方法,而没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入BLOCKED状态。
8、等待/通知机制
等待/通知的相关方法是任意Java对象都具备的,因为这些方法定义在所有对象的超类java.lang.Object上。
notify():通知一个在对象上等待的线程,使其从wait()方法返回,返回的前提是该线程获取到了对象的锁。
notifyAll():通知所有等待在该对象上的线程。
wait():调用该方法的线程进入WAITING状态,只有等待另外线程的通知或中断才会返回,注意,调用wait()方法后,会释放对象的锁。
wait(long):超时等待一段时间,这里的参数时间是毫秒,如果没有通知就超时返回。
wait(long,int):对于超时时间更细粒度的控制,可以达到纳秒。
9、管道输入/输出流
管道输入/输出流主要用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流主要包括了4种具体实现:PipedOutputStream,PipedInputStream,PipedReader和PipedWriter,前两种面向字节,后两种面向字符。
10、Thread.join()方法
如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。
11、ThreadLocal的使用
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。