[Java 并发] 线程的基本知识(一)

来源:互联网 发布:mac如何收藏网页的图片 编辑:程序博客网 时间:2024/06/11 07:26

本人大二,有些知识理解的不够深刻。若有错误,欢迎大家指正。

线程

在操作系统的课程学习里面,我们学过进程,也知道了线程。线程作为调度和分配的最小单位,可以看作一个简化了的进程,且具有并发性。
下面看看线程的状态:
Thread_Status

  • 初始状态:被初始化,还没有开始执行。也就是还在外存的后备队列中。
  • 阻塞状态:在这个状态之下,线程没有得到处理机,一个一个的在阻塞队列(内存里面)里面。
  • 就绪状态:在这个状态之下,线程也是没有得到处理机的,一个一个的在就绪队列(内存里面)的。但是当当前运行的线程阻塞之后,处理机就会以某种算法在就绪队列里面找一个线程运行。
  • 运行状态:只有在这个状态之下,线程才可以得到处理机。
  • 挂起状态:会把线程挂起,放在外存的一个队列里面。(具体内容参见操作系统的书)
  • 死亡状态:执行完毕,终止了。
    (其实在Java里面大家可以去看Thread.State来进行学习)。

在操作系统的课程里面还学了一个知识:
抢占与非抢占机制,抢占指优先级高的线程可以先于优先级低的线程先运行。

线程组

线程组即ThreadGroup,既然是一个组,那么就有了统一的属性,所以线程组就是为了统一的管理线程,包括设置一组线程的异常处理,统一的安全策略等。线程组一般是以树的形式出现的,即线程组下面可以有子线程组。Thread的构造方法就可以传入一个ThreadGroup对象:

public Thread(ThreadGroup group, Runnable target, String name,                  long stackSize) {        init(group, target, name, stackSize);    }

join

The join method allows one thread to wait for the completion of another. If t is a Thread object whose thread is currently executing,
t.join();
causes the current thread to pause execution until t’s thread terminates. Overloads of join allow the programmer to specify a waiting period. However, as with sleep, join is dependent on the OS for timing, so you should not assume that join will wait exactly as long as you specify.Like sleep, join responds to an interrupt by exiting with an InterruptedException.

join();join(long)Thread里面的一个方法,t.join()表示其他所有的线程都将等待,直到t线程运行完。准确的说:其他线程将被挂起,知道t线程运行完了之后,才将挂起的线程加入到就绪队列里面。join(long)表示t线程能够运行的最大时间。

/** * Created by WQH on 2016/2/3. * * Thread.join()方法保证了Thread的run方法的完全执行 * 首先将Main线程挂起,t1执行完成后在执行Main */public class JoinMain implements Runnable {    public static int a = 0;    public synchronized void inc() {        a++;        System.out.println("Add");    }    public void run() {        for (int i = 0; i < 5; i++) {            inc();        }    }    public static void main(String[] args) throws Exception {        Thread t1 = new Thread(new JoinMain());        t1.start();        t1.join(); //只有当t1执行完成之后 才会执行下面的代码        System.out.println(a);    }}

上述程序的运行结构如下:

AddAddAddAddAdd5

sleep

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers.

sleep(long millis)Thread里面的一个静态方法,让当前线程睡眠t毫秒,在这时间间隔里面,可以允许其他优先级不同的线程抢占处理机。睡眠的线程由运行状态转为阻塞状态。但是睡眠状态的线程不释放线程锁

这个方法还是用的比较多了,在这里就暂时不赘述了。

yield

A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.

yield()方法,在中文里面叫让步,也就是说当前线程的重要的事情做好了,可以把处理让给相同优先级的其他线程(但不一定非得让)。微观来说:将当前线程加入从运行状态加入到就绪队列,和其他优先级一样的线程再去争夺处理机,所以有可能还是这个线程抢到了处理机。yield不会去释放线程锁

由于处理机可以忽略这次让步请求,所以对于一些比较重要的操作不要过于依赖yield

下面看一个实例:注意一点,mThread1mThread0都要用一个Yield实例。

public class Yield implements Runnable {    @Override    public void run() {        for (int i = 0; i < 10; ++i) {                System.out.println(Thread.currentThread().getName() + " => i = " + i);                if (i == 5)                    Thread.yield();        }    }    public static void main(String args[]) {        Yield mRunnable = new Yield();        Thread mThread1 = new Thread(mRunnable);        Thread mThread2 = new Thread(mRunnable);        mThread1.start();        mThread2.start();    }}

可以看到在Thread1运行到 i == 5时,会做出让步,此时处理机会交给Thread0.(其实可以多运行几次,可以看到:这种情况并不是每次都出现,但概念比较大。但我运行了好多次都没有出现,但是概念上说是成立的)

Thread-0 => i = 0Thread-1 => i = 0Thread-0 => i = 1Thread-1 => i = 1Thread-0 => i = 2Thread-0 => i = 3Thread-1 => i = 2Thread-0 => i = 4Thread-1 => i = 3Thread-0 => i = 5Thread-1 => i = 4Thread-1 => i = 5Thread-0 => i = 6Thread-0 => i = 7Thread-1 => i = 6Thread-0 => i = 8Thread-1 => i = 7Thread-0 => i = 9Thread-1 => i = 8Thread-1 => i = 9

但是由于yieldsleep都是不释放线程锁的,所以把上面的run改为下面的语句的话,就会只有一种情况出现:

 @Override    public synchronized void run() {        for (int i = 0; i < 10; ++i) {                System.out.println(Thread.currentThread().getName() + " => i = " + i);                if (i == 5)                    Thread.yield();        }    }

下面的结果不管运行多少次都不会变的,为什么呢?因为mThread0首先获得Yield的锁,而由于yield不会释放这个锁,所以就会一直把run执行完,然后释放锁,然后mThread1才会继续执行。

Thread-0 => i = 0Thread-0 => i = 1Thread-0 => i = 2Thread-0 => i = 3Thread-0 => i = 4Thread-0 => i = 5Thread-0 => i = 6Thread-0 => i = 7Thread-0 => i = 8Thread-0 => i = 9Thread-1 => i = 0Thread-1 => i = 1Thread-1 => i = 2Thread-1 => i = 3Thread-1 => i = 4Thread-1 => i = 5Thread-1 => i = 6Thread-1 => i = 7Thread-1 => i = 8Thread-1 => i = 9

那么:yieldsleep都是可以让出处理机的,他们之间有什么不同之处吗?

  • yield:可以让出处理机给优先级等于自己的线程,假设有一个线程T的优先级比自己低,那么其他优先级和自己一样的线程会比线程T先抢到优先级
  • sleep:可以把处理机让给优先级低的线程。即在就绪队列上,根据一定的算法,来选择一个线程执行。

守护线程

对于Linux的守护进程,Java里面有一个守护线程(Daemon)。那么什么是守护线程呢?

For Daemon Threads, when the JVM stops all daemon threads are exited Due to this reason daemon threads shouldn’t be used often since cleanup might not be executed on them.

所谓守护线程,当然是守护在一个线程旁边啦。假设A是B的守护线程,那么B如果终止了,A也就会随之终止。所以JVM会退出当所有的线程都是守护线程的时候。Java GC就是一个典型的 Daemon线程

下面看一个实例:

public class Daemon implements Runnable {    @Override    public void run() {        try {            System.out.println("Start Daemon");            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            System.out.println("Exit Error");        }    }}public class Main {    public static void main(String args[]) {        Thread mThread = new Thread(new Daemon());        // setDaemon要在start之前调用        mThread.setDaemon(true);        mThread.start();    }}

执行一哈:什么都没有

Process finished with exit code 0

那么把mThread.setDaemon(true);注释掉再次运行:

Start DaemonExit Error

为什么?因为没有注释的时候, Deamon是主线程的守护线程,主线程执行完了,JVM就会退出,根本都不会管守护线程。但是有注释的时候,主线程退出,Deamon线程没有执行完,所以就会出现InterruptedException


好了,就这些,虽然这些方法在实际中根本没有用到,但是对于Java多线程和多线程的知识还有有帮助的。
邮箱:1906362072@qq.com

0 0
原创粉丝点击