线程的基本概念
线程是一个程序里面不同的执行路径,是一个程序内部的顺序控制流。
³ 线程和进程的区别
² 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。
² 线程可以看成时轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
² 多进程: 在操作系统中能同时运行多个任务(程序)
² 多线程: 在同一应用程序中有多个顺序流同时执行
线程的创建和启动
³ 可以有两种方式创建新的线程。
² 第一种
± 定义线程类实现Runnable接口
± Thread myThread = new Thead(target)//target为Runnable接口类型。
± Runnable中只有一个方法:
° public void run(); 用以定义线程运行体。
± 使用Runnable接口可以为多个线程提供共享的数据。
± 在实现Runnable接口的类的run方法定义中可以使用Thread的静态方法:
° public static Thread currentThread() 获取当前线程的引用。
² 第二种
± 可以定义一个Thread的子类并重写其run方法如:
class MyThread extends Thead {
public void run(){…}
}
± 然后生成该类的对象:
MyThread myThread=new MyThead(…)
³ 使用那种好呢?推荐使用接口,因为使用继承之后就不能继承其它的类了,比较死。使用接口比较灵活(可以继续实现其它类或者接口)
Eg:
publicstaticvoid main(String[] args) {
// TODO Auto-generated method stub
Thread1 r=new Thread1();
r.run();//方法调用
Thread th=new Thread(r);
th.start();//线程启动
Thread2 r2=new Thread2();
r2.start();
for (int i = 0; i <100; i++) {
System.out.println("Main Thread---"+i);
}
}
}
class Thread1 implements Runnable {
@Override
publicvoid run() {
// TODO Auto-generated method stub
for (int i = 0; i <100; i++) {
System.out.println("Thread1---"+i);
}
}
}
class Thread2 extends Thread{
publicvoid run() {
// TODO Auto-generated method stub
for (int i = 0; i <100; i++) {
System.out.println("Thread2---"+i);
}
}
}
线程状态转换
线程控制基本方法
方 法 | 功 能 |
isAlive() | 判断线程是否还“活”着,即线程是否还未终止。 |
getPriority() | 获得线程的优先级数值 |
setPriority() | 设置线程的优先级数值 |
Thread.sleep() | 将当前线程睡眠指定毫秒数 |
join() | 调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行。 |
yield() | 让出CPU,当前线程进入就绪队列等待调度。 |
wait() | 当前线程进入对象的wait pool。 |
notify()/ notifyAll() | 唤醒对象的wait pool中的一个/所有等待线程。 |
sleep / join / yield 方法
³ sleep方法:
² 可以调用Thread的静态方法:
public static void sleep(long millis) throws InterruptedException
使得当前线程休眠(暂时停止执行millis毫秒)。
² 由于是静态方法,sleep可以由类名直接调用:
Thread.sleep(…)
³ join方法:
² 合并某个线程,等待该合并的线程终止才会继续执行当前的线程。
³ yield方法:
² 让出CPU,给其他线程执行的机会,暂停当前正在执行的线程对象,并执行其他线程。只会暂停一下。
Eg:
publicclass TestInterrupt {
/**
* @param args
*/
publicstaticvoid main(String[] args) {
// TODO Auto-generated method stub
Mythread th=new Mythread();
th.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//th.interrupt();//可以终止子线程。并不是终止子线程的最好的方法。
//更加不要使用stop方法来终止线程,stop方法更加的粗暴。
th.flag =false;//使用这种方法来终止子线程比较合适
}
}
class Mythread extends Thread{
booleanflag=true;
publicvoid run() {
while(flag){
System.out.println(new Date());
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
return;
}
}
}
}
Eg2:
publicclass TestJoin {
/**
* @param args
*/
publicstaticvoid main(String[] args) {
// TODO Auto-generated method stub
MyThread2 th=new MyThread2("child Thread");
th.start();
try {
th.join();//将th线程合并到当前线程中等待th线程执行完后才会执行当前线程。
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for(int i=0;i<10;i++){
System.out.println("I am Main Thread");
}
}
}
class MyThread2 extends Thread{
public MyThread2(String s) {
// TODO Auto-generated constructor stub
super(s);
}
publicvoid run(){
for(int i=0;i<10;i++){
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
return;
}
System.out.println("I am "+getName());
}
}
}
Eg3:
publicclass TestYield {
/**
* @param args
*/
publicstaticvoid main(String[] args) {
// TODO Auto-generated method stub
MyThread3 th1=new MyThread3("th1");
MyThread3 th2=new MyThread3("th2");
th1.start();
th2.start();
}
}
class MyThread3 extends Thread{
public MyThread3(String s) {
super(s);
}
publicvoid run() {
for(int i=0;i<=100;i++){
System.out.println(getName()+" "+i);
if(i%10==0){
yield();
}
}
}
}
线程的优先级别
³ Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。
³ 线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5。
² Thread.MIN_PRIORITY = 1
² Thread.MAX_PRIORITY = 10
² Thread.NORM_PRIORITY = 5
³ 使用下述线方法可以获得或设置线程对象的优先级。
² int getPriority();
² void setPriority(int newPriority);
³ 不同平台上的优先级
² Solaris:相同优先级的线程不能相互抢占对方的cpu时间。
² windows:可以抢占相同甚至更高优先级的线程的cpu时间
Eg:
publicclass TestPriority {
/**
* @param args
*/
publicstaticvoid main(String[] args) {
// TODO Auto-generated method stub
Thread t1 = new Thread(new T1());
Thread t2 = new Thread(new T2());
t1.setPriority(Thread.NORM_PRIORITY+3);
t1.start();
t2.start();
}
}
class T1 implements Runnable {
@Override
publicvoid run() {
// TODO Auto-generated method stub
for (int i = 0; i <= 1000; i++) {
System.out.println("T1"+" "+i);
}
}
}
class T2 implements Runnable {
@Override
publicvoid run() {
// TODO Auto-generated method stub
for (int i = 0; i <= 1000; i++) {
System.out.println("---------T2"+" "+i);
}
}
}
线程同步
³ 在Java语言中,引入了对象互斥锁的概念,保证共享数据操作的完整性。每个对象都对应于一个可称为“互斥锁”的标记,这个标记保证在任一时刻,只能有一个线程访问该对象。
³ 关键字synchronized 来与对象的互斥锁联系。当某个对象synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
Ø synchronized 的使用方法:
synchronized(this){
num ++;
try {Thread.sleep(1);}
catch (InterruptedException e) {}
System.out.println
(name+", 你是第"+num+"个使用timer的线程");
}
Ø synchronized 还可以放在方法声明中,表示整个方法为同步方法,例如:
synchronized public void add(String name){…}
Eg
publicclass TestSynchronised implements Runnable {
/**
* @param args
*/
Timer timer=new Timer();
publicstaticvoid main(String[] args) {
// TODO Auto-generated method stub
TestSynchronised test=new TestSynchronised();
Thread t1=new Thread(test);
Thread t2=new Thread(test);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
@Override
publicvoid run() {
// TODO Auto-generated method stub
timer.add(Thread.currentThread().getName());
}
}
class Timer{
privatestaticintnum=0;
publicvoid add(String name){
synchronized(this){
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(name+"你是第"+num+"个使用timer的线程");
}
}
}
³ 无论synchronized关键字加在方法上还是对象上,它取得的锁都是锁在了对象上,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
³ 每个对象只有一个锁(lock)与之相关联。
³ 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
³ 搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。
³ 还有一些技巧可以让我们对共享资源的同步访问更加安全:
² 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。
² 如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。
死锁:简单的说就是有两个线程t1和t2,这两个线程都需要锁住A和B两个对象才能执行完,当t1锁住了A而t2锁住了B的时候,这个时候两个线程都无法继续执行,就会产生死锁。
Eg:
publicclass TestDeadLock implements Runnable {
publicintflag = 1;
static Object o1 = new Object(), o2 = new Object();
publicstaticvoid main(String[] args) {
TestDeadLock td1 = new TestDeadLock();
TestDeadLock td2 = new TestDeadLock();
td1.flag = 1;
td2.flag = 0;
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
publicvoid run() {
System.out.println("flag=" + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o2) {//锁住o1的同时还要锁住02(在o1的里面)。
System.out.println("1");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("0");
}
}
}
}
}
Synchronized总结
³ 无论synchronized关键字加在方法上还是对象上,它取得的锁都是锁在了对象上,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
³ 每个对象只有一个锁(lock)与之相关联。
³ 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
³ 搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。
³ 还有一些技巧可以让我们对共享资源的同步访问更加安全:
² 定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。
² 如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了。