资讯专栏INFORMATION COLUMN

初识线程关键字

Michael_Ding / 3131人阅读

摘要:架构师入门笔记一初识线程关键字本章节主要介绍线程的关键字,的含义,使用方法,使用场景,以及注意事项。若次方法也加上了,就必须等待线程执行完后,才能调用关键字不具备关键字的原子性同步其主要作用就是使变量在多个线程中可见。

架构师入门笔记一 初识线程关键字

本章节主要介绍线程的关键字 synchronized,volatile 的含义,使用方法,使用场景,以及注意事项。

线程安全

首先我们要了解线程安全的概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。
要如何确保类能表现出正确的行为,这就需要关键字出马!

关键字 synchronized

synchronized 可以在任意对象及方法上加锁,而加锁的这段代码称为"互斥区"或"临界区"
其工作原理:当一个线程想要执行synchronized修饰的方法,必须经过以下三个步骤

step1 尝试获得锁

step2 如果拿到锁,执行synchronized代码体内容

step3 如果拿不到锁,这个线程就会不断地尝试获得这把锁,直到拿到为止。这个过程可能是多个线程同时去竞争这把锁(锁竞争的问题)。

注*(多个线程执行的顺序是按照CPU分配的先后顺序而定的,而并非代码执行的先后顺序)

使用方法介绍:
synchronized 重入
在使用synchronized时,当一个线程得到了一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁。

public class MySyncReentrant {  
      
    /** 
     * 重入调用 
     */  
    private synchronized void method1() {  
        System.out.println("^^^^^^^^^^^^^method1");  
        method2();  
    }  
      
    private synchronized void method2() {  
        System.out.println("-----------------------method2");  
        method3();  
    }  
      
    private synchronized void method3() {  
        System.out.println("********************method3");  
    }  
  
    public static void main(String[] args) {  
        final MySyncReentrant mySyncReentrant = new MySyncReentrant();  
        Thread thread = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                mySyncReentrant.method1();  
            }  
        }, "reentrant");  
        thread.start();  
          
        Thread thread2 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                SunClass sunClass = new SunClass();  
                sunClass.sunMethod();  
            }  
        });  
        thread2.start();  
    }  
    /** 
     * 有父子继承关系的类,如果都使用了synchronized关键字,也是线程安全的。 
     */  
    static class FatherClass {  
        public synchronized void fatherMethod(){  
            System.out.println("fatherMethod....");  
        }  
    }  
      
    static class SunClass extends FatherClass{  
        public synchronized void sunMethod() {  
            System.out.println("sunMethod....");  
            this.fatherMethod();  
        }  
    }  
      
}  

synchronized 代码块:
如果被修饰的方法执行需要很长时间,线程之间等待的时间就会很长,所以将synchronized 修饰在代码块上是可以优化执行时间。(这也叫减少锁的粒度)
synchronized (this) {} , 可以是 this(对象锁) class(类锁) Object lock = new Object(); 任何对象锁。

import java.util.concurrent.atomic.AtomicInteger;  
public class CodeBlockLock {  
      
    // 对象锁  
    private void thisLock () {  
        synchronized (this) {  
            System.out.println("this 对象锁!");  
        }  
    }  
      
    // 类锁  
    private void classLock () {  
        synchronized (CodeBlockLock.class) {  
            System.out.println("class 类锁!");  
        }  
    }  
      
    // 任何对象锁  
    private Object lock = new Object();  
    private void objectLock () {  
        synchronized (lock) {  
            System.out.println("object 任何对象锁!");  
        }  
    }  
      
    // 字符串锁,注意String常量池的缓存功能  
    private void stringLock () {  
        synchronized ("string") { // new String("string")  
            try {  
                for(int i = 0; i < 3; i++) {  
                    System.out.println("thread : " + Thread.currentThread().getName() + " stringLock !");  
                    Thread.sleep(1000);       
                }  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
      
    // 字符串锁改变  
    private String strLock = "lock";  
    private void changeStrLock () {  
        synchronized (strLock) {  
            try {  
                System.out.println("thread : " + Thread.currentThread().getName() + " changeLock start !");  
                strLock = "changeLock";  
                Thread.sleep(5000);  
                System.out.println("thread : " + Thread.currentThread().getName() + " changeLock end !");  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
      
    public static void main(String[] args) {  
        final CodeBlockLock codeBlockLock = new CodeBlockLock();  
        Thread thread1 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                codeBlockLock.thisLock();  
            }  
        });  
        Thread thread2 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                codeBlockLock.classLock();  
            }  
        });  
        Thread thread3 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                codeBlockLock.objectLock();  
            }  
        });  
        thread1.start();  
        thread2.start();  
        thread3.start();  
          
        // 如果字符串锁,用new String("string") t4,t5线程是可以获取锁的,如果直接使用"string" ,若锁不释放,t5线程一直处理等待中  
        Thread thread4 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                codeBlockLock.stringLock();  
            }  
        }, "t4");  
        Thread thread5 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                codeBlockLock.stringLock();  
            }  
        }, "t5");  
        thread4.start();  
        thread5.start();  
          
        try {  
            Thread.sleep(10000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
          
        // 字符串变了,锁也会改变,导致t7线程在t6线程未结束后变开始执行,但一个对象的属性变了,不影响这个对象的锁。  
        Thread thread6 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                codeBlockLock.changeStrLock();  
            }  
        }, "t6");  
        Thread thread7 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                codeBlockLock.changeStrLock();  
            }  
        }, "t7");  
        thread6.start();  
        thread7.start();  
    }  
  
} 

运行结果:

this 对象锁!  
class 类锁!  
object 任何对象锁!  
thread : t4 stringLock !  
thread : t4 stringLock !  
thread : t4 stringLock !  
thread : t5 stringLock !  
thread : t5 stringLock !  
thread : t5 stringLock !  
thread : t6 changeLock start !  
thread : t7 changeLock start !  
thread : t6 changeLock end !  
thread : t7 changeLock end !  

注* 给String的常量加锁,容易会出现死循环的情况。 如果加锁的字符串变了,锁也会变。若一个对象的属性变了,是不影响这个对象的锁。static + synchronized 一起使用 是类级别的锁
synchronized 异常:
synchronized 遇到异常后,自动释放锁,让其他线程调用。如果第一个线程在执行任务时,因为异常导致业务逻辑未能正常执行。后续的线程执行的任务也都是异常的。所以在编写代码时一定要考虑周全

同步与异步

同步的概念就是共享,其目标就是为了线程安全(线程安全的两个特性:原子性和可见性),A线程获取对象的锁,若B线程想要执行synchronized方法,就需要等待,这就是同步。
异步的概念就是独立,A线程获取对象的锁,若B线程想要执行非synchronized方法,是无需等待的,这就是异步。可以参考ajax请求。

public class SyncAndAsyn {  
      
    private synchronized void syncMethod() {  
        try {  
            System.out.println(Thread.currentThread().getName() + " synchronized method!");  
            Thread.sleep(4000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
      
    // 若次方法也加上了 synchronized,就必须等待t1线程执行完后,t2才能调用  
    private void asynMethod() {  
        System.out.println(Thread.currentThread().getName() + " asynchronized method!");  
    }  
      
    public static void main(String[] args) {  
        final SyncAndAsyn syncAndAsyn = new SyncAndAsyn();  
          
        Thread thread1 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                syncAndAsyn.syncMethod();  
            }  
        }, "t1");  
        Thread thread2 = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                syncAndAsyn.asynMethod();  
            }  
        }, "t2");  
        thread1.start();  
        thread2.start();  
    }  
  
}  
volatile

volatile关键字不具备synchronized关键字的原子性(同步)其主要作用就是使变量在多个线程中可见。
原理图:

在java中,每一个线程都会有一块工作内存区,其中存放着所有线程共享的主内存中的变量值的拷贝。当线程执行时,他在自己的工作内存区中操作这些变量。
为了存取一个共享的变量,一个线程通常先获取锁定并去清除它的内存工作区,把这些共享变量从所有线程的共享内存区中正确的装入到他自己的所在的工作内存区中。当线程解锁时保证该工作内存区中变量的值写回到共享内存中。
volatile的作用就是强制线程到主内存里面去读取变量,而不去线程工作内存区里面读,从而实现了多个线程间的变量可见。也就是满足线程安全的可见性。
可见性:(被volatile修饰的变量,线程执行引擎是直接从主内存中读取变量的值)

public class VolatileThread extends Thread{  
      
    // 如果不加 volatile,会导致 "thread end !" 一直没有打印,  
    private volatile boolean flag = true;  
      
    @Override  
    public void run() {  
        System.out.println("thread start !");  
        while (flag) {  
        }  
        System.out.println("thread end !");  
    }  
  
    public static void main(String[] args) {  
        VolatileThread thread = new VolatileThread();  
        thread.start();  
        try { // 等线程启动了,再设置值  
            Thread.sleep(1000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        thread.setFlag(false);  
        System.out.println("flag : " + thread.isFlag());  
    }  
      
    public boolean isFlag() {  
        return flag;  
    }  
  
    public void setFlag(boolean flag) {  
        this.flag = flag;  
    }  
  
}  

volatile 不具备原子性:(多线程之间不是同步的,存在线程安全,从下面的例子中可以得知:如果是同步的,最后一次打印绝对是1000*10 。为了弥补这个问题,可以考虑使用atomic类的系类对象)

import java.util.concurrent.atomic.AtomicInteger;  
  
/** 
 * volatile关键字不具备synchronized关键字的原子性(同步) 
 */  
public class VolatileNoAtomic extends Thread{  
    // 多次执行程序,会发现最后打印的结果不是1000的整数倍.中途打印不是1000的整数倍,可能是因为System.out打印的延迟造成的  
//  private static volatile int count;   
    private static AtomicInteger count = new AtomicInteger(0); // 不会出现以上的情况  
    private static void addCount(){  
        for (int i = 0; i < 1000; i++) {  
//          count++ ;  
            count.incrementAndGet();  
        }  
        System.out.println(count);  
    }  
      
    public void run(){  
        addCount();  
    }  
      
    public static void main(String[] args) {  
        VolatileNoAtomic[] arr = new VolatileNoAtomic[10];  
        for (int i = 0; i < 10; i++) {  
            arr[i] = new VolatileNoAtomic();  
        }  
        // 执行10个线程  
        for (int i = 0; i < 10; i++) {  
            arr[i].start();  
        }  
    }  
      
}  

其实atomic类 并非完美,它也只能保证自己方法是原子性,若要保证多次操作也是原子性,就需要synchronized的帮忙(若不用synchronized修饰,打印的结果中会出现非10倍数的信息,需多次执行才能模拟出来)

import java.util.ArrayList;  
import java.util.List;  
import java.util.concurrent.atomic.AtomicInteger;  
  
public class AtomicUse {  
  
    private static AtomicInteger count = new AtomicInteger(0);  
      
    //多个addAndGet在一个方法内是非原子性的,需要加synchronized进行修饰,保证4个addAndGet整体原子性 .  
    /**synchronized*/  
    public  int multiAdd(){  
            try {  
                Thread.sleep(100);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            count.addAndGet(1);  
            count.addAndGet(2);  
            count.addAndGet(3);  
            count.addAndGet(4); //+10  
            return count.get();  
    }  
      
    public static void main(String[] args) {  
        final AtomicUse au = new AtomicUse();  
        List ts = new ArrayList();  
        for (int i = 0; i < 100; i++) {  
            ts.add(new Thread(new Runnable() {  
                @Override  
                public void run() {  
                    System.out.println(au.multiAdd());  
                }  
            })); // 添加100个线程  
        }  
        for(Thread t : ts){  
            t.start();  
        }  
          
    }  
}  

以上便是初识线程关键字的内容,方便自己以后查阅,也希望对读者有些帮助。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/70526.html

相关文章

  • 初识Queue队列

    摘要:架构师入门笔记三初识队列和模拟基础知识线程通信概念线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理,就不能成为一个整体,线程之间的通信就成为整体的必用方法之一。它是一个基于链接节点的无界限线程安全队列。 架构师入门笔记三 初识Queue队列 wait和notify模拟Queue wait/notify 基础知识 线程通信概念:线程是操作系统中独立的个体,但这些个体如果不经过特...

    Miracle_lihb 评论0 收藏0
  • 初识JAVA多线程编程

    摘要:多线程术语辨析任务和线程是不同的中类本身不执行任何操作它只驱动赋予它的任务而才是定义任务的地方创建任务的方式有两种实现接口中的方法查看源码可以看到只有一个方法使用直接继承即可这样就创建了一个任务暗示调度器该线程可以让出资源了中实现方法部分源 java多线程 1. 术语辨析 任务和线程是不同的,Java中Thread类本身不执行任何操作,它只驱动赋予它的任务,而Runnable才是定义任...

    luzhuqun 评论0 收藏0
  • PHP下的异步尝试二:初识协程

    摘要:如果仅依靠程序自动交出控制的话,那么一些恶意程序将会很容易占用全部时间而不与其他任务共享。多个操作可以在重叠的时间段内进行。 PHP下的异步尝试系列 如果你还不太了解PHP下的生成器,你可以根据下面目录翻阅 PHP下的异步尝试一:初识生成器 PHP下的异步尝试二:初识协程 PHP下的异步尝试三:协程的PHP版thunkify自动执行器 PHP下的异步尝试四:PHP版的Promise ...

    MudOnTire 评论0 收藏0
  • [初识C++] 何为最:心酸历史

    摘要:上面需要了解的是这俩个版本都是破蛹成蝶的版本世界挑战榜咋才前三还没挤进去呀,你想想世界上有几千中编程语言,在其中脱颖出来,可以说是天之娇子,凤毛麟角了。支持正版图灵上面买吧,如果没钱买盗版吧学完以后买本正版支持一下,创作不易是吧 ...

    forrest23 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<