1、,JAVA多线程,JAVA SE基础,线程的基本概念,线程是一个程序内部的顺序控制流。 线程和进程的区别 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。 线程可以看成时轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。 多进程: 在操作系统中能同时运行多个任务(程序) 多线程: 在同一应用程序中有多个顺序流同时执行,Java的线程是通过java.lang.Thread类来实现的。VM 启动时会有一个由主方法(public static void main() )所定义的线程。可以通过创建 Thread 的实例
2、来创建新的线程。每个线程都是通过某个特定Thread对象所对应的方法run( )来完成其操作的,方法run( )称为线程体。通过调用Thead类的start()方法来启动一个线程。,线程的创建和启动,可以有两种方式创建新的线程。 第一种 定义线程类实现Runnable接口 Thread myThread new Thead(target)/target为Runnable接口类型。 Runnable中只有一个方法: public void run(); 用以定义线程运行体。 使用Runnable接口可以为多个线程提供共享的数据。 在实现Runnable接口的类的run方法定义中可以使用Threa
3、d的静态方法: public static Thread currentThread() 获取当前线程的引用。 第二种 可以定义一个Thread的子类并重写其run方法如:class MyThread extends Thead public void run() 然后生成该类的对象:MyThread myThreadnew MyThead() 使用那种好呢?,线程状态转换,线程状态转换,sleep / join / yield 方法,sleep方法 可以调用Thread的静态方法:public static void sleep(long millis) throws Interrupted
4、Exception使得当前线程休眠(暂时停止执行millis毫秒)。 由于是静态方法,sleep可以由类名直接调用:Thread.sleep() join方法 合并某个线程 yield方法 让出CPU,给其他线程执行的机会,线程模式,两种线程模式: 协作式:一个线程保留对处理器的控制直到它自己决定放弃 速度快、代价低 用户编程非常麻烦 抢先式。系统可以任意的从线程中夺回对CPU的控制权,再把控制权分给其它的线程 。 两次切换之间的时间间隔就叫做时间片 效率不如协作式高 ,OS核心必须负责管理线程 简化编程,而且使程序更加可靠 多数线程的调度是抢先式的。,线程的优先级别,Java提供一个线程调度
5、器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。Thread.MIN_PRIORITY = 1Thread.MAX_PRIORITY = 10Thread.NORM_PRIORITY = 5使用下述线方法获得或设置线程对象的优先级。int getPriority();void setPriority(int newPriority); 不同平台上的优先级 Solaris:相同优先级的线程不能相互抢占对方的cpu时间。 windows:可以抢占相同甚至更高优先级的线程的cpu时间,临
6、界资源问题(1),两个线程A和B在同时操纵Stack类的同一个实例(堆栈),A正在往堆栈里push一个数据,B则要从堆栈中pop一个数据。,class Stackint idx=0;char data = new char6;public void push(char c)dataidx = c;idx+;public char pop()idx-;return dataidx; ,临界资源问题(2),1. 操作之前 data = | a | b | | | | | idx=2 2. A执行push中的第一个语句,将c推入堆栈;data = | a | b | c | | | | idx=2
7、3. A还未执行idx+语句,A的执行被B中断,B执行pop方法,返回c:data = | a | b | c | | | | idx=1 4. A继续执行push的第二个语句:data = | a | b | c | | | | idx=2最后的结果相当于c没有入栈, 产生这种问题的原因在于对共享数据访问的操作的不完整性。,线程同步,public class Test implements Runnable Timer timer = new Timer();public static void main(String args) Test test = new Test();Thread
8、t1 = new Thread(test);Thread t2 = new Thread(test);t1.setName(“t1“); t2.setName(“t2“);t1.start(); t2.start();public void run()timer.add(Thread.currentThread().getName(); class Timerprivate static int num = 0;public void add(String name)num +;try Thread.sleep(1); catch (InterruptedException e) System
9、.out.println(name+“, 你是第“+num+“个使用timer的线程“); ,Synchronized总结,无论synchronized关键字加在方法上还是对象上,它取得的锁都是锁在了对象上,而不是把一段代码或函数当作锁而且同步方法很可能还会被其他线程的对象访问。 每个对象只有一个锁(lock)与之相关联。 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。 搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。 还有一些技巧可以让我们对共享资源的同步访问更加安全: 定义private 的instance变量+它的
10、 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。 如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。 这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()这样,调用端得到的就是对象副本的引用了。,线程同步,在Java语言中,引入了对象互斥锁的
11、概念,保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个线程访问该对象。关键字synchronized 来与对象的互斥锁联系。当某个对象synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。, synchronized(this)num +;try Thread.sleep(1); catch (InterruptedException e) System.out.println(name+“, 你是第“+num+“个使用timer的线程“); ,synchronized 的使用方法:,synchronized 还可以放在
12、方法声明中,表示整个方法为同步方法,例如:synchronized public void add(String name),面试:Wait sleep区别,来源不同 Sleep是Thread提供的方法 Wait继承自Object 代码位置不同 Wait需要写在Synchronize语句块里面 是否释放锁定对象 调用wait方法,释放锁定该对象 Sleep时别的线程也不可以访问锁定对象,总结,线程、进程的概念 线程的创建和启动方式 线程的调度和优先级 Sleep Join Yield synchronized Wait Notify、notifyAll,Q&A,Any Question?,谢谢大家,