• Java笔记:多线程基础


    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(){}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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纳秒为单位。时间精度更高
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    public final void notify()
    
    • 1

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

    public final void notify()
    
    • 1

    在调用同一个对象的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对象锁的线程继续运行
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    sleep()

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

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

    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();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    第一种情况: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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    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();
    		
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    运行结果:

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

    线程的唤醒是指线程从休眠的阻塞状态转变为运行状态,可以通过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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    输出:

    first : 1 second : va
    
    • 1

    从上面的程序验证结果来看,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();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    输出:

    A 线程 独占该资源了
    已启动,但进入不到print方法中
    
    • 1
    • 2
  • 相关阅读:
    算法|每日一题|只出现一次的数字|位运算
    彻底解决Qt中文乱码以及汉字编码的问题(UTF-8/GBK)
    博客自动化测试
    zblog插件大全-zblog免费插件
    k8s--基础--02--组件
    vue之组件传值---父传子(属性)---子传父(emit,sync,v-model)
    Unity UGUI的Image(图片)组件的介绍及使用
    redis 哨兵
    AI遮天传 DL-深度学习在计算机视觉中的应用
    [6368] 20 接手新团队:士气低、交付迟、事故多发,如何下手解决?
  • 原文地址:https://blog.csdn.net/liufang_imei/article/details/132815233