Java Thread

1,线程的基本概念

线程是一个程序内部的顺序控制流。

线程和进程的区别:(资源分配和处理器分配的基本单元)

①,每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销;
②,线程可以看成是轻量级进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小;
③,多进程:操作系统中能同时运行多个任务(程序);
④,多线程:在同一应用程序中有多个顺序流同时执行;

Java线程是通过java.lang.Thread类来实现的。VM启动时会有一个由主方法(public static void main(){})所定义的线程。可以通过创建Thread的实例来创建新的线程。每个线程都是通过某个特定的Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。
注意:
要注意方法调用和运行线程的区别。方法调用,主程序会等方法运行完成程序再继续往下走;运行线程:相当于新建一个分支,主程序与线程并行执行。

2,线程的创建和启动

第一种:定义线程类实现Runnable接口

Thread thread = new Thread(target);//target为Runnable接口类型

Runnable中只有一个方法: public void run();用以定义线程运行体。

使用Runnable接口可以为多个线程提供共享的数据。在实现Runnable接口的类的run方法定义中可以使用Thread的静态方法:

public static Thread currentThread()获取当前线程的引用。

例如:

public static void main(String[] args) {
    Thread t = new Thread(new MyThread());
    t.start();//线程启动
    t.run();//方法调用
}

class MyThread implements Runnable {
    public void run() {
        ...
    }        
}

第二种:定义一个Thread的子类,并重写其run方法,如:

    class MyThread extends Thread {
        public void run() {...}
    }
然后生成该类的对象:MyThread thread = new MyThread(...);

例如:

public static void main(String[] args) {
    Thread t = new MyThread();
    t.start();        
}

class MyThread extends Thread {
    public void run() {
        ...            
    }        
}

3,线程的状态

3.1,线程的状态类型:

①,新建状态(New):创建了一个线程对象,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等线程运行资源;
②,就绪状态(Runnable):在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会;
③,运行状态(Running):就绪状态的线程获取了CPU,执行程序代码;
④,阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(1),等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池(waitting queue)中;
(2),同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中;
(3),其他阻塞:运行的线程执行sleep()或join()方法,或者发出I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超市、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态;
⑤,挂起状态(Suspend):可以通过调用suspend方法(已过时)将线程的状态转换为挂起状态。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序调用resume方法(已过时)恢复线程运行;
⑥,死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

3.2,线程状态转换图


3.3,阻塞和挂起的区别:

操作系统中挂起和阻塞的区别如下:
(1),挂起是一种主动行为,因此恢复也应该要主动完成,而阻塞则是一种被动行为,是在等待事件或资源时任务的表现,你不知道他什么时候被阻塞(pend),也就不能确切 的知道他什么时候恢复阻塞。而且挂起队列在操作系统里可以看成一个,而阻塞队列则是不同的事件或资源(如信号量)就有自己的队列;
(2),阻塞(pend)就是任务释放CPU,其他任务可以运行,一般在等待某种资源或信号量的时候出现。挂起(suspend)不释放CPU,如果任务优先级高就永远轮不到其他任务运行,一般挂起用于程序调试中的条件中断,当出现某个条件的情况下挂起,然后进行单步调试;
(3),阻塞(pend)是task主动去等一个事件或消息,suspend是直接悬挂task,以后这个task和你没任何关系,任何task间的通信或者同步都和这个suspended task没任何关系了,除非你resume task;
(4),任务调度是操作系统来实现的,任务调度时,直接忽略挂起状态的任务,但是会顾及处于pend下的任务,当pend下的任务等待的资源就绪后,就可以转为ready了。ready只需要等待CPU时间,当然,任务调度也占用开销,但是不大,可以忽略。可以这样理解,只要是挂起状态,操作系统就不在管理这个任务了;
(5),挂起是主动的,一般需要用挂起函数进行操作,若没有resume的动作,则此任务一直不会ready。而阻塞是因为资源被其他任务抢占而处于休眠态。两者的表现方式都是从就绪态里“清掉”,即对应标志位清零,只不过实现方式不一样。 

3.4,wait和sleep 的区别

(1),这两个方法来自不同的类分别是,sleep来自Thread类,和wait来自Object类。sleep是Thread的静态类方法,谁调用的谁去睡觉,即使在a线程里调用了b的sleep方法,实际上还是a去睡觉,要让b线程睡觉要在b的代码中调用sleep。
(2),最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”。
(3),使用范围:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
   synchronized(x){
      x.notify()
     //或者wait()
   }
(4),sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

3.5,线程控制的基本方法

 isAlive() 判断线程是否还“活着”,即线程是否还未终止
 getPriority() 获得线程的优先级数值
 setPriority(int newPriority) 设置线程的优先级数值

 sleep(long millis)

 sleep(long millis, int nanos)

将当前线程睡眠指定时间
 join() 调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行
 yield() 让出CPU,当前线程进入就绪队列等待调度
 wait() 当前线程进入线程等待池(wait pool)

 notify()

 notifyAll()

唤醒对象的wait pool中的一个/所有等待线程

3.5.1,线程优先级

Java提供一个线程调度器来监控程序中就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪个线程来执行。线程的优先级用数字标志,范围1~10,缺省优先级为5

public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;
使用以下方法获得或这只线程对象的优先级:
public final void setPriority(int paramInt) {
    checkAccess();
    if ((paramInt > 10) || (paramInt < 1)) {
        throw new IllegalArgumentException();
    }
    ThreadGroup localThreadGroup;
    if ((localThreadGroup = getThreadGroup()) != null) {
        if (paramInt > localThreadGroup.getMaxPriority()) {
            paramInt = localThreadGroup.getMaxPriority();
        }
        setPriority0(this.priority = paramInt);
    }
}

public final int getPriority() {
    return this.priority;
}

3.5.2,sleep/join/yield方法

(1),sleep:可以调用Thread的静态方法sleep使得当前线程休眠,由于是静态方法,sleep可以直接由类名直接调用。

例子:

public class Test {

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
        try {
            Thread.sleep(10000);//主线程睡眠            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();//打断子线程(不推荐的方法,还有stop())
        //thread.flag = false;
    }
    
}

class MyThread extends Thread {
    boolean flag = true;
    public void run() {
        while(flag) {
            System.out.println("====" + new Date() + "====");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                return;//sleep时出现异常则线程直接结束
            }
        }
    }
}

结果:

====Tue Jun 14 11:05:30 CST 2016====
====Tue Jun 14 11:05:31 CST 2016====
====Tue Jun 14 11:05:32 CST 2016====
====Tue Jun 14 11:05:33 CST 2016====
====Tue Jun 14 11:05:34 CST 2016====
====Tue Jun 14 11:05:35 CST 2016====
====Tue Jun 14 11:05:36 CST 2016====
====Tue Jun 14 11:05:37 CST 2016====
====Tue Jun 14 11:05:38 CST 2016====
====Tue Jun 14 11:05:39 CST 2016====

源码:

public static native void sleep(long paramLong) throws InterruptedException;

public static void sleep(long paramLong, int paramInt)
        throws InterruptedException {
    if (paramLong < 0L) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if ((paramInt < 0) || (paramInt > 999999)) {
        throw new IllegalArgumentException(
                "nanosecond timeout value out of range");
    }
    if ((paramInt >= 500000) || ((paramInt != 0) && (paramLong == 0L))) {
        paramLong += 1L;
    }
    sleep(paramLong);
}

(2),join:合并线程

public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread("abcde");
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 3; i++) {
            System.out.println("I am main thread");
        }
    }
    
}

class MyThread extends Thread {
    MyThread(String name) {
        super(name);
    }
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("I am " + getName());
        }
    }
}

结果:

I am abcde
I am abcde
I am abcde
I am main thread
I am main thread
I am main thread

源码:

public final void join() throws InterruptedException {
    join(0L);
}

public final synchronized void join(long paramLong)
        throws InterruptedException {
    long l1 = System.currentTimeMillis();
    long l2 = 0L;
    if (paramLong < 0L) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (paramLong == 0L) {
        while (isAlive()) {
            wait(0L);
        }
    }
    while (isAlive()) {
        long l3 = paramLong - l2;
        if (l3 <= 0L) {
            break;
        }
        wait(l3);
        l2 = System.currentTimeMillis() - l1;
    }
}

(3),yield:让出CPU,给其他线程执行的机会

例子:

public class Test {
    public static void main(String[] args) {
        MyThread t1 = new MyThread("t1");
        MyThread t2 = new MyThread("t2");
        t1.start();
        t2.start();
    }
}

class MyThread extends Thread {
    MyThread(String name) {
        super(name);
    }
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(getName() + ":" + i);
            if(i%10 == 0) {
                yield();
            }
        }
    }
}

结果:

每当被10整除的时候会yield
...
t1:14
t2:70
t1:15
...
...
t2:72
t1:20
t2:73
...
t2:76
t1:30
t2:77
...

源码:

public static native void yield();

4,线程同步

在Java语言中,引入了对象互斥锁的概念,保证共享数据错做的完整性,每个对象对应于一个可称为”互斥锁“的标记,这个标记保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized来与对象的互斥锁联系。当某个对象synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
例一:

public class Test implements Runnable {
    Timer timer = new Timer();
    public static void main(String[] args) {
        Test test = new Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }

    @Override
    public void run() {
        timer.add(Thread.currentThread().getName());
    }
}

class Timer {
    private static int num = 0;

    public synchronized void add(String name) {
        // synchronized (this) {
        num++;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
        }
        System.out.println(name + ", 你是第" + num + "个使用timer的线程");
        // }
    }
}

结果一(可能):

t1, 你是第2个使用timer的线程
t2, 你是第2个使用timer的线程

加上synchronized控制

结果二:

t1, 你是第1个使用timer的线程
t2, 你是第2个使用timer的线程

例二:

public class Test implements Runnable {

    public int flag = 1;
    static Object o1 = new Object();
    static Object o2 = new Object();
    
    @Override
    public void run() {
        System.out.println("flag = " + flag);
        if(flag == 1) {
            synchronized (o1) {//获得对象o1锁
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2) {//获得对象o2锁
                    System.out.println(1);
                }
            }
        }
        if(flag == 0) {
            synchronized (o2) {//获得对象o2锁
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1) {//获得对象o1锁
                    System.out.println(0);
                }
            }
        }
    }
    
    public static void main(String[] args) {
        Test test1 = new Test();
        Test test2 = new Test();
        test1.flag = 1;
        test2.flag = 0;
        Thread t1 = new Thread(test1);
        Thread t2 = new Thread(test2);
        t1.start();
        t2.start();
    }
}

结果:

flag = 0
flag = 1
分别在等待获得o1,o2对象锁的,程序死锁,无法结束

例三:

哲学家吃饭问题

5,生产者消费者问题

public class Test {
    public static void main(String[] args) {
        SyncStack ss = new SyncStack();
        Producer p = new Producer(ss);
        Consumer c = new Consumer(ss);
        
        new Thread(c).start();
        new Thread(p).start();
        new Thread(p).start();//多加个生产者线程
        
    }
    
}

class WoTou {
    int id;
    WoTou(int id) {
        this.id = id;
    }
    public String toString() {
        return "WoTou:" + id;
    }
}

class SyncStack {
    int index = 0;
    WoTou[] arrWT = new WoTou[5];
    //push
    public synchronized void push(WoTou wt) {
        while(index == arrWT.length) {
            try {
                this.wait();//阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notifyAll();//唤醒其他所有线程
        arrWT[index] = wt;
        index++;
    }
    //pop
    public synchronized WoTou pop() {
        while (index == 0) {
            try {
                this.wait();//阻塞
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notifyAll();//唤醒其他所有线程
        index--;
        return arrWT[index];
    }
}

//生产者
class Producer implements Runnable {
    SyncStack ss = null;
    
    Producer(SyncStack ss) {
        this.ss = ss;
    }
    
    @Override
    public void run() {
        for(int i=1; i<= 10; i++) {
            WoTou wt = new WoTou(i);//produce
            ss.push(wt);
            System.out.println("生产了:" + wt);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者
class Consumer implements Runnable {

    SyncStack ss = null;
    Consumer(SyncStack ss) {
        this.ss = ss;
    }
    
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            WoTou wt = ss.pop();
            System.out.println("消费了:" + wt);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }    
}

结果:

生产了:WoTou:1
消费了:WoTou:1
生产了:WoTou:1
生产了:WoTou:2
消费了:WoTou:2
生产了:WoTou:2
消费了:WoTou:2
生产了:WoTou:3
生产了:WoTou:3
生产了:WoTou:4
生产了:WoTou:4
消费了:WoTou:4
生产了:WoTou:5
消费了:WoTou:5
生产了:WoTou:5
生产了:WoTou:6
消费了:WoTou:5
消费了:WoTou:6
生产了:WoTou:6
生产了:WoTou:7
消费了:WoTou:6
消费了:WoTou:7
生产了:WoTou:8
生产了:WoTou:9
消费了:WoTou:8

 

原文地址:https://www.cnblogs.com/lemon-now/p/5583838.html