Skip to content

Latest commit

 

History

History
361 lines (298 loc) · 13.7 KB

多线程基础.md

File metadata and controls

361 lines (298 loc) · 13.7 KB

1.同步代码块与同步方法

1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法 2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。 3)synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象; 4) synchronized(Foo.class) 它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。 5)synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。

同步代码块
synchronized(对象){	}
synchronized(类.class){	}

同步函数
public synchronized void sale(){}
public static synchronized void sale(){}

wait()/notify()/notifyAll()

wait():导致当前线程等待并使其进入到等待阻塞状态。直到其他线程调用该同步锁对象的notify()或notifyAll()方法来唤醒此线程。会释放所持有的对象的锁

public final void wait() throws InterruptedException
public final void wait(long timeout) throws InterruptedException
//指定等待时长,毫秒为单位
public final void wait(long timeout,int nanos) throws InterruptedException
//指定等待时长,毫秒+纳秒,timeout毫秒为单位,nanos纳秒为单位。时间精度更高

notify():唤醒在此同步锁对象上等待的单个线程,如果有多个线程都在此同步锁对象上等待,则会任意选择其中某个线程进行唤醒操作,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。

public final void notify()

notifyAll():唤醒在此同步锁对象上等待的所有线程,只有当前线程放弃对同步锁对象的锁定,才可能执行被唤醒的线程。

public final void notify()

在调用同一个对象的wait()方法和notify()方法的语句必须放在同步代码块中,并且同步代码块使用该对象的同步锁,否则在运行时则会抛出IllegalMonitorStateException异常

示例

class ThreadA {
 
    static Object o1 = new Object();//可以是任意一个对象,或者自定义的对象
 
    public static void main(String[] args) {
        ThreadB b = new ThreadB();
        b.start();
        System.out.println("b is start....");
        synchronized (o1)// 主线程获取o1的对象锁
        {
            try {
                System.out.println("Waiting for b to complete...");
                o1.wait();//o1的对象锁释放,主线程进入等待状态
                System.out.println("Completed.Now back to main thread");
            } catch (InterruptedException e) {
            }
        }
        System.out.println("Total is :" + b.total);
    }
}
 
class ThreadB extends Thread {
    int total;
    public void run() {
        synchronized (o1) {//ThreadB获取o1的对象锁
            System.out.println("ThreadB is running..");
            for (int i = 0; i < 5; i++) {
                total += i;
                System.out.println("total is " + total);
            }
            o1.notify();//ThreadB释放o1的对象锁,通知其他等待o1对象锁的线程继续运行
        }
    }
}

o1.wait()没设置时间,需要o1.notify(),主线程才能继续运行 输出有以下两种情况:

b is start....
ThreadB is running..
total is 0
total is 1
total is 3
total is 6
total is 10
Waiting for b to complete...
 
 
b is start....
Waiting for b to complete...
ThreadB is running..
total is 0
total is 1
total is 3
total is 6
total is 10
Completed.Now back to main thread
Total is :10

sleep()

sleep()是Thread类的静态方法。该方法声明抛出了InterrupedException异常。所以使用时,要么捕捉,要么声明抛出。有两种重载方式:

static void sleep(long millis); //让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度和准度的影响。

static void sleep(long millis , int nanos);  //让当前正在执行的线程暂停millis毫秒加nanos微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的
精度和准度的影响。

sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。常用来暂停程序的运行。同时注意,sleep()方法不会释放锁。

yield()

yield()是Thread类的静态方法。它能让当前线程暂停,但不会阻塞该线程,而是由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行。因此,**使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。**但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权,**也有可能是当前线程又进入到“运行状态”继续运行!**值得注意的是,yield()方法不会释放锁。

package com.demo.test;

public class ThreadYield extends Thread{
    
    public ThreadYield(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();
            }
        }
    }

}

package com.demo.test;

public class Main {
    
    public static void main(String[] args) {

        ThreadYield yt1 = new ThreadYield("A");
        ThreadYield yt2 = new ThreadYield("B");
        yt1.start();
        yt2.start();

    }
}

第一种情况:A线程当执行到30时会将CPU时间让掉,这时B线程抢到CPU时间并执行。 第二种情况:A线程当执行到30时会将CPU时间让掉,这时A线程抢到CPU时间并执行。 第三种情况:B线程当执行到30时会将CPU时间让掉,这时A线程抢到CPU时间并执行。 第四种情况:B线程当执行到30时会将CPU时间让掉,这时B线程抢到CPU时间并执行。 也就是说:谁抢到就是谁的。

setPriority()

1 . 优先级表示重要程度或者紧急程度.但是能不能抢到资源也是不一定. 2 . 分配优先级:反映线程的重要或紧急程度 线程的优先级用1~10 表示,1的优先级最低,10的优先级最高,默认值是5

/**
* 优先级 : 只能反映 线程 的 中或者是 紧急程度 , 不能决定 是否一定先执行
* setPriority()
* 1~10 1最低 10最高 5是默认值
*/
public class Test {

    public static void main(String[] args) {
        MyThread thread = new MyThread("二狗");
        thread.setPriority(1);
        MyThread thread2 = new MyThread("小香菇");
        thread2.setPriority(10);
        MyThread thread3 = new MyThread("小蘑菇");
        MyThread thread4 = new MyThread("观海同志");
        thread4.setPriority(3);
        thread.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }
}

class MyThread extends Thread{

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

interrupt()

通常我们会有这样的需求,即停止一个线程。在java的api中有stop、suspend等方法可以达到目的,但由于这些方法在使用上存在不安全性,会带来不好的副作用,不建议被使用。

中断在java中主要有3个方法,interrupt(),isInterrupted()和interrupted()。 *interrupt(),在一个线程中调用另一个线程的interrupt()方法,即会向那个线程发出信号——线程中断状态已被设置。至于那个线程何去何从,由具体的代码实现决定。 *isInterrupted(),用来判断当前线程的中断状态(true or false)。 *interrupted()是个Thread的static方法,用来恢复中断状态

如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException。

import java.util.Date;

public class TestInterrupted implements Runnable {
	public void run(){
		System.out.println("开始运行run方法,时间:"+(new Date()));
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			System.out.println("线程已被唤醒!");
		}
		System.out.println("结束运行run方法,时间:"+(new Date()));
		
	}
}

public class TestMainInterrupted {
	public static void main(String[] args) {
		TestInterrupted t = new TestInterrupted();
		Thread th = new Thread(t);
		th.start();
		try {
			th.sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		th.interrupt();
		
	}
}

运行结果:

开始运行run方法,时间:Sat Dec 14 19:18:29 CST 2019
线程已被唤醒!
结束运行run方法,时间:Sat Dec 14 19:18:31 CST 2019

线程的唤醒是指线程从休眠的阻塞状态转变为运行状态,可以通过Thread类的interrupt()方法实现。 该线程th启动后即进入休眠,原本需要休眠5000ms,但是主线程启动后,两秒就将其唤醒。并且,将休眠的饿线程唤醒后会抛出异常,即输出catch语句块的内容。

stop() --过期方法

1.即刻停止run()方法中剩余的全部工作,包括在catch或finally语句中,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。 2.会立即释放该线程所持有的所有的锁,导致数据得不到同步的处理,出现数据不一致的问题。

public class Main{
    public static void main(String [] args) throws Exception{
        TestObject testObject = new TestObject();
        Thread t1 = new Thread(){
            public void run(){
                try {
                    testObject.print("1", "2");
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        };

        t1.start();
        Thread.sleep(1000);
        t1.stop();
        System.out.println("first : " + testObject.getFirst() + " " + "second : " + testObject.getSecond());


    }
}

class TestObject{
    private String first = "ja";
    private String second = "va";

    public synchronized void print(String first, String second) throws Exception{
        this.first = first;
        Thread.sleep(10000);
        this.second = second;
    }
    
    public String getFirst() {
        return first;
    }
    public void setFirst(String first) {
        this.first = first;
    }
    public String getSecond() {
        return second;
    }
    public void setSecond(String second) {
        this.second = second;
    }
}

输出:

first : 1 second : va

从上面的程序验证结果来看,stop()确实是不安全的。它的不安全主要是针对于第二点:释放该线程所持有的所有的锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。

suspend()和resume() --过期方法

因为suspend方法并不会释放锁,如果使用suspend的目标线程对一个重要的系统资源持有锁,那么没任何线程可以使用这个资源直到要suspend的目标线程被resumed,如果一个线程在resume目标线程之前尝试持有这个重要的系统资源锁再去resume目标线程,这两条线程就相互死锁了,也就冻结线程。 如果 resume() 操作出现在 suspend() 之前执行,那么线程将一直处于挂起状态,同时一直占用锁,这就产生了死锁。而且,对于被挂起的线程,它的线程状态居然还是 Runnable。

public class Main{
    public static void main(String [] args) throws Exception{
        TestObject testObject = new TestObject();
        Thread t1 = new Thread(){
            public void run(){
                testObject.print();
            }
        };
        t1.setName("A");
        t1.start();
        Thread.sleep(1000);

        Thread t2 = new Thread(){
            public void run(){
                System.out.println("B已启动,但进入不到print方法中");
                testObject.print();
            }
        };
        t2.setName("B");
        t2.start();
    }
}

class TestObject{
    public synchronized void print(){
        if(Thread.currentThread().getName().equals("A")){
            System.out.println("A 线程 独占该资源了");
            Thread.currentThread().suspend();
        }
    }
}

输出:

A 线程 独占该资源了
已启动,但进入不到print方法中