《Java高级:多线程-Thread类的介绍使用(11种)》

一、Thread类

(一)Thread类

线程Thread是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。

Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。

属性

MIN_PRIORITY: 一个线程可以拥有的最低优先级1
NORM_PRIORITY:分配给线程的默认优先级5
MAX_PRIORITY:一个线程能够拥有的最高优先级10

方法

static int activeCount()
返回当前线程的线程组中活动线程的数目。
void checkAccess()
判定当前运行的线程是否有权修改该线程。
int countStackFrames()
已过时。 该调用的定义依赖于 suspend(),但它遭到了反对。此外,该调用的结果从来都不是意义明确的。
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
void destroy()
已过时。 该方法最初用于破坏该线程,但不作任何清除。它所保持的任何监视器都会保持锁定状态。不过,该方法决不会被实现。即使要实现,它也极有可能以 suspend() 方式被死锁。如果目标线程被破坏时保持一个保护关键系统资源的锁,则任何线程在任何时候都无法再次访问该资源。如果另一个线程曾试图锁定该资源,则会出现死锁。这类死锁通常会证明它们自己是“冻结”的进程。有关更多信息,请参阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?。
static void dumpStack()
将当前线程的堆栈跟踪打印至标准错误流。
static int enumerate(Thread[] tarray)
将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中。
static Map<Thread,StackTraceElement[]> getAllStackTraces()
返回所有活动线程的堆栈跟踪的一个映射。
ClassLoader getContextClassLoader()
返回该线程的上下文 ClassLoader。
static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()
返回线程由于未捕获到异常而突然终止时调用的默认处理程序。
long getId()
返回该线程的标识符。
String getName()
返回该线程的名称。
int getPriority()
返回线程的优先级。
StackTraceElement[] getStackTrace()
返回一个表示该线程堆栈转储的堆栈跟踪元素数组。
Thread.State getState()
返回该线程的状态。
ThreadGroup getThreadGroup()
返回该线程所属的线程组。
Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()
返回该线程由于未捕获到异常而突然终止时调用的处理程序。
static boolean holdsLock(Object obj)
当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
void interrupt()
中断线程。
static boolean interrupted()
测试当前线程是否已经中断。
boolean isAlive()
测试线程是否处于活动状态。
boolean isDaemon()
测试该线程是否为守护线程。
boolean isInterrupted()
测试线程是否已经中断。
void join()
等待该线程终止。
void join(long millis)
等待该线程终止的时间最长为 millis 毫秒。
void join(long millis, int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
void resume()
已过时。 该方法只与 suspend() 一起使用,但 suspend() 已经遭到反对,因为它具有死锁倾向。有关更多信息,请参阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume。
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
void setContextClassLoader(ClassLoader cl)
设置该线程的上下文 ClassLoader。
void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
设置当线程由于未捕获到异常而突然终止,并且没有为该线程定义其他处理程序时所调用的默认处理程序。
void setName(String name)
改变线程名称,使之与参数 name 相同。
void setPriority(int newPriority)
更改线程的优先级。
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
设置该线程由于未捕获到异常而突然终止时调用的处理程序。
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
static void sleep(long millis, int nanos)
在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
void stop()
已过时。 该方法具有固有的不安全性。用 Thread.stop 来终止线程将释放它已经锁定的所有监视器(作为沿堆栈向上传播的未检查 ThreadDeath 异常的一个自然后果)。如果以前受这些监视器保护的任何对象都处于一种不一致的状态,则损坏的对象将对其他线程可见,这有可能导致任意的行为。stop 的许多使用都应由只修改某些变量以指示目标线程应该停止运行的代码来取代。目标线程应定期检查该变量,并且如果该变量指示它要停止运行,则从其运行方法依次返回。如果目标线程等待很长时间(例如基于一个条件变量),则应使用 interrupt 方法来中断该等待。有关更多信息,请参阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume。
void stop(Throwable obj)
已过时。 该方法具有固有的不安全性。有关详细信息,请参阅 stop()。 该方法的附加危险是它可用于生成目标线程未准备处理的异常(包括若没有该方法该线程不太可能抛出的已检查的异常)。 有关更多信息,请参阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume?。
void suspend()
已过时。 该方法已经遭到反对,因为它具有固有的死锁倾向。如果目标线程挂起时在保护关键系统资源的监视器上保持有锁,则在目标线程重新开始以前任何线程都不能访问该资源。如果重新开始目标线程的线程想在调用 resume 之前锁定该监视器,则会发生死锁。这类死锁通常会证明自己是“冻结”的进程。有关更多信息,请参阅为何不赞成使用 Thread.stop、Thread.suspend 和 Thread.resume。
String toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。
static void yield()
暂停当前正在执行的线程对象,并执行其他线程。

(二)Thread.join()等待线程

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

定义

join()throws InterruptedException;  //无参数的join()等价于join(0),作用是一直等待该线程死亡

join(long millis, int nanos) throws InterruptedException;  //最多等待该线程死亡millis毫秒

join(long millis, int nanos) throws InterruptedException ; //最多等待该线程死亡millis毫秒加nanos纳秒

源码

public final void join() throws InterruptedException {
    join(0L);//后面参数为wait的最大时间,如果是0表示永远等待直到该线程执行完毕
}
public final synchronized void join(long millis) //参数millis为0.  
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    if (millis == 0) { //进入这个分支  
        while (isAlive()) { //判断本线程是否为活动的。这里的本线程就是t1.  
            wait(0); //阻塞  
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

案例

class BThread extends Thread {
    public BThread() {
        super("[BThread] Thread");
    };
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(threadName + " loop at " + i);
                Thread.sleep(1000);
            }
            System.out.println(threadName + " end.");
        } catch (Exception e) {
            System.out.println("Exception from " + threadName + ".run");
        }
    }
}
class AThread extends Thread {
    BThread bt;
    public AThread(BThread bt) {
        super("[AThread] Thread");
        this.bt = bt;
    }
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        try {
            bt.join();
            System.out.println(threadName + " end.");
        } catch (Exception e) {
            System.out.println("Exception from " + threadName + ".run");
        }
    }
}
public class TestDemo {
    public static void main(String[] args) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        BThread bt = new BThread();
        AThread at = new AThread(bt);
        try {
            bt.start();
            Thread.sleep(2000);
            at.start();
            at.join();
        } catch (Exception e) {
            System.out.println("Exception from main");
        }
        System.out.println(threadName + " end!");
    }
}

总结

1. 在Java多线程中,如果某一个线程s在另一个线程t上调用t.join(),此线程s将被挂起,直到目标线程t结束才恢复(此时t.isAlive()返回为假)。也可以在调用join()时带上一个超时参数(单位可以是毫秒,或者毫秒和纳秒),这样如果目标线程在这段时间内还没有结束的话,join()方法总能返回。对join()方法的调用可以被中断,做法是在调用线程上调用interrupt()方法。

2. 如果线程被生成了,但还未被起动,isAlive()将返回false,调用它的join()方法是没有作用的。将直接继续向下执行。

(三)Thread.Sleep()暂停当前线程

Thread.sleep()被用来暂停当前线程的执行,会通知线程调度器把当前线程在指定的时间周期内置为wait状态。当wait时间结束,线程状态重新变为Runnable并等待CPU的再次调度执行。所以线程sleep的实际时间取决于线程调度器,而这是由操作系统来完成的。

在windows环境下,进程调度是抢占式的。一个进程在运行态时调用sleep(),进入等待态,睡眠结束以后,并不是直接回到运行态,而是进入就绪队列,要等到其他进程放弃时间片后才能重新进入运行态。所以sleep(1000),在1000ms以后,线程不一定会被唤醒。sleep(0)可以看成一个运行态的进程产生一个中断,由运行态直接转入就绪态。这样做是给其他就绪态进程使用时间片的机会。总之,还是操作系统中运行态、就绪态和等待态相互转化的问题。

定义

java.lang.Thread sleep(long millis)  暂停当前线程的执行,暂停时间由方法参数指定,单位为毫秒。注意参数不能为负数,否则程序将会抛出IllegalArgumentException。

java.lang.Thread sleep(long millis, int nanos)  暂停当前线程的执行,暂停时间为millis毫秒数加上nanos纳秒数。纳秒允许的取值范围为0~999999.

案例

public class TestThreadSleep implements Runnable{
    public static void main(String[] args) {  
          
        TestThreadSleep runnable = new TestThreadSleep();  
        Thread thread = new Thread(runnable);  
        thread.start();  
    }  
    @Override  
    public void run() {  
          
        System.out.println("i am sleep for a while!");  
        try {  
            Date currentTime = new Date();  
            long startTime = currentTime.getTime();  
            Thread.sleep(4000);  
            currentTime = new Date();  
            long endTime = currentTime.getTime();  
            System.out.println("休眠时间为:"+(endTime-startTime)+"ms");  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
}

总结

1. 它只用于暂停当前线程的执行。

2. 线程被唤醒(wake up)并开始执行的实际时间取决于操作系统的CPU时间片长度及调度策略。对于相对空闲的系统来说,sleep的实际时间与指定的sleep时间相近,但对于操作繁忙的系统,这个时间将会显得略长一些。

3. 线程在sleep过程中不会释放它已经获得的任意的monitor和lock等资源。

4. 其他的任意线程都能中断当前sleep的线程,并会抛出InterruptedException。

(四)Thread.currentThread()获取当前线程

Thread.currentThread()是Thread的一个静态方法,用来获取当前线程对象的一个引用。

案例

public class Demo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt).start();
        new Thread(mt, "Name1").start();
        new Thread(mt, "Name2").start();

        System.out.println(Thread.currentThread().getName()); // main主方法
        //      System.out.println(this.getName());                   // this获取不到线程对象
    }
}

class MyThread extends Thread {@Override public void run() {
        try {
            Thread.sleep(3000);
            Thread t = Thread.currentThread();
            System.out.println("当前线程名字:" + t.getName() + " 当前线程的优先级别为:" + t.getPriority() + " ID:" + t.getId());
            //           System.out.println("当前线程名字:" + this.getName() + " 当前线程的优先级别为:" + this.getPriority() + " ID:"+ this.getId());
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}

总结

1. Thread提供静态方法currentThread()来供我们调用,既可以避免this无法获取到main线程的问题,又可以避免this无法获取到Thread t2=new Thread(Thread t)来开启的线程的问题,可以说currentThread方法可以在所有场合获取到正确的当前线程。

2. 在自定义线程类时,如果线程类是继承java.lang.Thread的话,那么线程类就可以使用this关键字去调用继承自父类Thread的方法,this就是当前的对象。

3. Thread.currentThread()可以获取当前线程的引用,一般都是在没有线程对象又需要获得线程信息时通过Thread.currentThread()获取当前代码段所在线程的引用。

(五)Thread.interrupt() 中断线程

thread.interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。

定义

interrupt()的作用是中断本线程。

1. 本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。

2. 如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。

3. 如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。

4. 如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。

5. 中断一个“已终止的线程”不会产生任何操作。

方法

public static boolean interrupted    测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false。
public boolean isInterrupted()    测试线程是否已经中断。线程的中断状态 不受该方法的影响。
public void interrupt()    中断线程。

案例

class Example3 extends Thread {
    public static void main(String args[]) throws Exception {
        Example3 thread = new Example3();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        thread.interrupt();// 等中断信号量设置后再调用
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            System.out.println("Thread running...");
            try {
                /*
                 * 如果线程阻塞,将不会去检查中断信号量stop变量,所 以thread.interrupt()
                 * 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并
                 * 进行异常块进行 相应的处理
                 */
                Thread.sleep(1000);// 线程阻塞,如果线程收到中断操作信号将抛出异常
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted...");
                /*
                 * 如果线程在调用 Object.wait()方法,或者该类的 join() 、sleep()方法
                 * 过程中受阻,则其中断状态将被清除
                 */
                System.out.println(this.isInterrupted());// false

                //中不中断由自己决定,如果需要真真中断线程,则需要重新设置中断位,如果
                //不需要,则不用调用
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("Thread exiting under request...");
    }
}

总结

1. Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,设置线程的中断标示位,在线程受到阻塞的地方(如调用sleep、wait、join等地方)抛出一个异常InterruptedException,并且中断状态也将被清除,这样线程就得以退出阻塞的状态。
2. 一般来说,阻塞函数,如:Thread.sleep、Thread.join、Object.wait等在检查到线程的中断状态时,会抛出InterruptedException,同时会清除线程的中断状态

(六)Thread.interrupt 用法

1. Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和 Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。因此,如果线程被上述几种方法阻塞,正确的停止线程方式是设置共享变量,并调用interrupt()(注意变量应该先设置)。如果线程没有被阻塞,这时调用interrupt()将不起作用;否则,线程就将得到异常(该线程必须事先预备好处理此状况),接着逃离阻塞状态。在任何一种情况中,最后线程都将检查共享变量然后再停止。实例如下:

class Example3 extends Thread {
    volatile boolean stop = false;
    public static void main(String args[]) throws Exception {
        Example3 thread = new Example3();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        thread.stop = true;//如果线程阻塞,将不会检查此变量
        thread.interrupt();
        Thread.sleep(3000);
        System.out.println("Stopping application...");
        //System.exit( 0 );
    }
 
    public void run() {
        while (!stop) {
            System.out.println("Thread running...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("Thread interrupted...");
            }
        }
        System.out.println("Thread exiting under request...");
    }
}

2. 如果线程在I/O操作进行时被阻塞,Thread.interrupt()将不起作用,因为线程将不会退出被阻塞状态。

class Example5 extends Thread {
    volatile boolean stop = false;
    volatile ServerSocket socket;
 
    public static void main(String args[]) throws Exception {
        Example5 thread = new Example5();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        thread.stop = true;
        thread.socket.close();
        Thread.sleep(3000);
        System.out.println("Stopping application...");
        //System.exit( 0 );
    }
 
    public void run() {
        try {
            socket = new ServerSocket(7856);
        } catch (IOException e) {
            System.out.println("Could not create the socket...");
            return;
        }
        while (!stop) {
            System.out.println("Waiting for connection...");
            try {
                Socket sock = socket.accept();
            } catch (IOException e) {
                System.out.println("accept() failed or interrupted...");
            }
        }
        System.out.println("Thread exiting under request...");
    }
}


3. 一般来说,阻塞函数,如:Thread.sleep、Thread.join、Object.wait等在检查到线程的中断状态时,会抛出InterruptedException,同时会清除线程的中断状态

class MyThread extends Thread {    
    ......    
    ......    
    public void run() {    
        try {    
            while(!Thread.currentThread().isInterrupted()) {  //如果线程没有被中断就继续运行  
                //阻塞代码:sleep,wait等      
                //当其他线程,调用此线程的interrupt()方法时,会给此线程设置一个中断标志    
                //sleep,wait等方法会检测这个标志位,同时会抛出InterruptedException,并清除线程的中断标志    
                //因此在异常段调用Thread.currentThread().isInterrupted()返回为false     
            }    
        } catch (InterruptedException e) {    
            //由于阻塞库函数,如:Object.wait,Thread.sleep除了抛出异常外,还会清除线程中断状态,因此可能在这里要保留线程的中断状态    
            Thread.currentThread().interrupt();//从新设置线程的中断标志    
        }    
    }    
    public void cancel() {    
        interrupt();  //中断线程  
    }    
}
外部调用
MyThread thread = new MyThread();    
thread.start(); //开启线程   
......    
thread.cancel();  //中断线程  
System.out.println(thread.isInterrupt)//打印线程中断标志

4. 使用中断信号量中断非阻塞状态的线程,使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。线程必须周期性的核查这一变量,然后有秩序地中止任务。

class Example2 extends Thread {
    volatile boolean stop = false;// 线程中断信号量

    public static void main(String args[]) throws Exception {
        Example2 thread = new Example2();
        System.out.println("Starting thread...");
        thread.start();
        Thread.sleep(3000);
        System.out.println("Asking thread to stop...");
        // 设置中断信号量
        thread.stop = true;
        Thread.sleep(3000);
        System.out.println("Stopping application...");
    }

    public void run() {
        // 每隔一秒检测一下中断信号量
        while (!stop) {
            System.out.println("Thread is running...");
            long time = System.currentTimeMillis();
            /*
             * 使用while循环模拟 sleep 方法,这里不要使用sleep,否则在阻塞时会 抛
             * InterruptedException异常而退出循环,这样while检测stop条件就不会执行,
             * 失去了意义。
             */
            while ((System.currentTimeMillis() - time < 1000)) {}
        }
        System.out.println("Thread exiting under request...");
    }
}

(七)Thread synchronized同步锁

在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如synchronized(obj)就获取了“obj这个对象”的同步锁。

原理

当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。不同线程对同步锁的访问是互斥的,也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。

1. 修饰普通方法,对普通方法同步

修饰一个普通方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。

public class SynchronizedTest {
    public synchronized void method1(){
        System.out.println("Method 1 start");
        try {
            System.out.println("Method 1 execute");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public synchronized void method2(){
        System.out.println("Method 2 start");
        try {
            System.out.println("Method 2 execute");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method2();
            }
        }).start();
    }
}

线程2需要等待线程1的method1执行完成才能开始执行method2方法。执行结果如下:

Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end

2. 修饰静态方法,静态方法(类)同步

修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。

public class SynchronizedTest {
     public static synchronized void method1(){
         System.out.println("Method 1 start");
         try {
             System.out.println("Method 1 execute");
             Thread.sleep(3000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 1 end");
     }
 
     public static synchronized void method2(){
         System.out.println("Method 2 start");
         try {
             System.out.println("Method 2 execute");
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("Method 2 end");
     }
 
     public static void main(String[] args) {
         final SynchronizedTest test = new SynchronizedTest();
         final SynchronizedTest test2 = new SynchronizedTest();
 
         new Thread(new Runnable() {
             @Override
             public void run() {
                 test.method1();
             }
         }).start();
 
         new Thread(new Runnable() {
             @Override
             public void run() {
                 test2.method2();
             }
         }).start();
     }
}

对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以即使test和test2属于不同的对象,但是它们都属于SynchronizedTest类的实例,所以也只能顺序的执行method1和method2,不能并发执行。执行结果如下:

Method 1 start
Method 1 execute
Method 1 end
Method 2 start
Method 2 execute
Method 2 end

3. 修饰代码块,代码块同步

修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象。

public class SynchronizedTest {
    public void method1(){
        System.out.println("Method 1 start");
        try {
            synchronized (this) {
                System.out.println("Method 1 execute");
                Thread.sleep(3000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 1 end");
    }

    public void method2(){
        System.out.println("Method 2 start");
        try {
            synchronized (this) {
                System.out.println("Method 2 execute");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Method 2 end");
    }

    public static void main(String[] args) {
        final SynchronizedTest test = new SynchronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method1();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                test.method2();
            }
        }).start();
    }
}

虽然线程1和线程2都进入了对应的方法开始执行,但是线程2在进入同步块之前,需要等待线程1中同步块执行完成。执行结果如下:

Method 1 start
Method 1 execute
Method 2 start
Method 1 end
Method 2 execute
Method 2 end

4、修饰一个类,对类同步

修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

class MyRunable implements Runnable {
    
    @Override
    public void run() {
        synchronized(this) {
            try {  
                for (int i = 0; i < 5; i++) {
                    Thread.sleep(100); // 休眠100ms
                    System.out.println(Thread.currentThread().getName() + " loop " + i);  
                }
            } catch (InterruptedException ie) {  
            }
        }  
    }
}
public class Demo1_1 {

    public static void main(String[] args) {  
        Runnable demo = new MyRunable();     // 新建“Runnable对象”

        Thread t1 = new Thread(demo, "t1");  // 新建“线程t1”, t1是基于demo这个Runnable对象
        Thread t2 = new Thread(demo, "t2");  // 新建“线程t2”, t2是基于demo这个Runnable对象
        t1.start();                          // 启动“线程t1”
        t2.start();                          // 启动“线程t2” 
    } 
}

运行结果

t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4

run()方法中存在“synchronized(this)代码块”,而且t1和t2都是基于"demo这个Runnable对象"创建的线程。这就意味着,我们可以将synchronized(this)中的this看作是“demo这个Runnable对象”;因此,线程t1和t2共享“demo对象的同步锁”。所以,当一个线程运行的时候,另外一个线程必须等待“运行线程”释放“demo的同步锁”之后才能运行。

(八)Thread.yield() 线程协作让步

Thread yield()方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态。

案例1:

“线程t1”在能被4整数的时候,并没有切换到“线程t2”。这表明,yield()虽然可以让线程由“运行状态”进入到“就绪状态”;但是,它不一定会让其它线程获取CPU执行权(即,其它线程进入到“运行状态”),即使这个“其它线程”与当前调用yield()的线程具有相同的优先级。

class ThreadA extends Thread{
    public ThreadA(String name){ 
        super(name); 
    } 
    public synchronized void run(){ 
        for(int i=0; i <10; i++){ 
            System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i); 
            // i整除4时,调用yield
            if (i%4 == 0)
                Thread.yield();
        } 
    } 
} 

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

案例2:

public class YieldTest extends Thread {
    public YieldTest(String name) {
        super(name);
    }

    @Override public void run() {
        for (int i = 1; i <= 50; i++) {
            System.out.println("" + this.getName() + "-----" + i);
            // 当i为30时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行)
            if (i == 30) {
                this.yield();
            }
        }
    }

    public static void main(String[] args) {
        YieldTest yt1 = new YieldTest("张三");
        YieldTest yt2 = new YieldTest("李四");
        yt1.start();
        yt2.start();
    }
}

(九)Thread sleep(),wait()区别详解

对于sleep()方法,我们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。sleep用于线程控制,而wait用于线程间的通信。

sleep()简介

1. sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;

2. sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。

3. 在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

wait()简介

1. wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;

2. wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。

3. wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。

区别

区别一

1. sleep是Thread类的方法,是线程用来 控制自身流程的,比如有一个要报时的线程,每一秒中打印出一个时间,那么我就需要在print方法前面加上一个sleep让自己每隔一秒执行一次。就像个闹钟一样。
2. wait是Object类的方法,用来线程间的通信,这个方法会使当前拥有该对象锁的进程等待知道其他线程调用notify方法时再醒来,不过你也可以给他指定一个时间,自动醒来。这个方法主要是用走不同线程之间的调度的。

区别二

1. 关于锁的释放 ,在这里假设大家已经知道了锁的概念及其意义。调用sleep方法不会释放锁(自己的感觉是sleep方法本来就是和锁没有关系的,因为他是一个线程用于管理自己的方法,不涉及线程通信)

2. 调用wait方法会释放当前线程的锁(其实线程间的通信是靠对象来管理的,所有操作一个对象的线程是这个对象通过自己的wait方法来管理的,就好像这个对象是电视机,三个人是三个线程,那么电视机的遥控器就是这个锁,假如现在A拿着遥控器,电视机调用wait方法,那么A就交出自己的遥控器,由jVM虚拟机调度,遥控器该交给谁。)

区别三

由于wait函数的特殊意义,所以他是应该放在同步语句块中的,这样才有意义。

/**
 * java中的sleep()和wait()的区别
 */
public class TestD {
    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(new Thread2()).start();
    }
    
    private static class Thread1 implements Runnable{
        @Override
        public void run(){
            synchronized (TestD.class) {
            System.out.println("enter thread1...");    
            System.out.println("thread1 is waiting...");
            try {
                //调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池
                TestD.class.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("thread1 is going on ....");
            System.out.println("thread1 is over!!!");
            }
        }
    }
    
    private static class Thread2 implements Runnable{
        @Override
        public void run(){
            synchronized (TestD.class) {
                System.out.println("enter thread2....");
                System.out.println("thread2 is sleep....");
                //只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
                TestD.class.notify();
                //==================
                //区别
                //如果我们把代码:TestD.class.notify();给注释掉,即TestD.class调用了wait()方法,但是没有调用notify()
                //方法,则线程永远处于挂起状态。
                try {
                    //sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,
                    //但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
                    //在调用sleep()方法的过程中,线程不会释放对象锁。
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("thread2 is going on....");
                System.out.println("thread2 is over!!!");
            }
        }
    }
}

如果注释掉代码:TestD.class.notify();

运行结果:

enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!

总结

1. sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。

2. 调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

(十)Thread类实现多线程方法

Java Thread类实现多线程有下面3个步骤:

1.首先创建一个任务类extends Thread类,因为Thread类实现了Runnable接口,所以自定义的任务类也实现了Runnable接口,重新run()方法,其中定义具体的任务代码或处理逻辑。

2.创建一个任务类对象,可以用Thread或者Runnable作为自定义的变量类型。

3.调用自定义对象的start()方法,启动一个线程。

Thread类实现多线程实例如下:

package com.muzeet.mutithread;

//每个任务都是Runable接口的一个实例,任务是可运行对象,线程即可运行对象。必须创建任务类,重写run方法定义任务
public class ExtendFromThread extends Thread {
    private int countDown = 10;
    @Override
    //重写run方法,定义任务
    public void run() {
        while(countDown-- >0)
        {
            System.out.println("$" + this.getName() 
                    + "(" + countDown + ")");
        }
    }
    //调用start方法会启动一个线程,导致任务中的run方法被调用,run方法执行完毕则线程终止
    
    public static void main(String[] args) {
        
        ExtendFromThread thread1 = new ExtendFromThread();
        ExtendFromThread thread2 = new ExtendFromThread();
        thread1.start();
        thread2.start();
        
        System.out.println("火箭发射倒计时:");
    }
}

start()方法

Thread用 start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

总结

(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。

(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。

(十一)Runnable接口实现多线程方法

Java Runnable接口实现多线程有下面4个步骤:

1.定义一个任务类实现Runnable接口,实现Runnable接口中的run()方法(run()方法告知系统线程该如何运行),run()方法中定义具体的任务代码或处理逻辑。

2.定义了任务类后,为任务类创建一个任务对象。

3.任务必须在线程中执行,创建一个Thread类的对象,将前面创建的实现了Runnable接口的任务类对象作为参数传递给Tread类的构造方法。

4.调用Thread类对象的start()方法,启动一个线程。它会导致任务的run()方法被执行,当run()方法执行完毕,则线程就终止。

Runnable接口实现多线程实例如下:

//每个任务都是Runable接口的一个实例,任务是可运行对象,线程是便于任务执行的对象。必须创建任务类,重写run方法定义任务
public class ThreadDemo1 implements Runnable {
    private int countDown = 10;
    @Override
    //重写run方法,定义任务
    public void run() {
        while(countDown-- >0)
        {
            System.out.println("$" + Thread.currentThread().getName() 
                    + "(" + countDown + ")");
        }
    }
    //调用start方法会启动一个线程,导致任务中的run方法被调用,run方法执行完毕则线程终止
    
    public static void main(String[] args) {
        Runnable demo1 = new ThreadDemo1();
        
        Thread thread1 = new Thread(demo1);
        Thread thread2 = new Thread(demo1);
        thread1.start();
        thread2.start();
        
        System.out.println("火箭发射倒计时:");
    }
}

run()方法

Runnable run() 方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待 run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。总结:调用start方法方可启动线程,而run方法只是thread的一 个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void.。

总结

Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参数方法。设计该接口的目的是为希望在活动时执行代码的对象提供一个公共协议。例如,Thread 类实现了Runnable。激活的意思是说某个线程已启动并且尚未停止。此外,Runnable 为非 Thread 子类的类提供了一种激活方式。通过实例化某个Thread 实例并将自身作为运行目标,就可以运行实现Runnable 的类。大多数情况下,如果只想重写run() 方法,而不重写其他 Thread 方法,那么应使用Runnable 接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。(推荐使用创建任务类,并实现Runnable接口,而不是继承Thread类)。

(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大大大钢琴

喜欢!就请他吃3块钱好吃的吧!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值