Java并发编程(JUC)---Synchronized,ThreadLocal,volatile

线程间的共享

三个关键字:Synchronized,ThreadLocal,volatile的使用实例

1.Synchronized内置锁

1)类锁:每个类的Class对象

    代码示例:
          类锁的方法和使用类锁的线程
          解析:在类锁的方法里用线程的休眠代表平常的业务工作(用static关键字修饰)
                     在类锁的线程只是做简单的输出和调用类锁的方法
//类锁的方法
    private static synchronized void synClass(){
        try {
            Thread.sleep(1000);
            System.out.println("synClass is going...");
            Thread.sleep(1000);
            System.out.println("synClass ended...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 //使用类锁的线程
    private static class SynClass extends Thread{
        @Override
        public void run() {
            System.out.println("TestClass is running");
            synClass();
        }
    }

2)对象锁: 每个对象 (new出来的实例)

代码示例:
对象锁的方法和使用对象锁的线程
解析:在对象锁的方法里用线程的休眠和输出语句代表平常的业务工作
在对象锁的线程里SynClzAndIns是包含这些方法的实体类,先要new出这个实例对象,然后调用相应的对象锁的方法

 //对象锁的方法
    private synchronized void instance(){
        try {
            Thread.sleep(3000);
            System.out.println("synInstance is going..."+this.toString());
            Thread.sleep(3000);
            System.out.println("synInstance ended..."+this.toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 //使用对象锁的线程
    private static class InstanceSyn implements Runnable{
        private SynClzAndInst synClzAndInst;

        public InstanceSyn(SynClzAndInst synClzAndInst) {
            this.synClzAndInst = synClzAndInst;
        }

        @Override
        public void run() {
            System.out.println("TestInstance is running..."+synClzAndInst);
            synClzAndInst.instance();
        }
    }

具体代码块(SynClzAndInst的类中包含有1个使用类锁的线程和2个使用对象锁的线程,1个使用类锁的方法和2个使用对象锁的方法)

package 线程间的共享;

/**
 * @author lenovo
 */
public class SynClzAndInst {

    //使用类锁的线程
    private static class SynClass extends Thread{
        @Override
        public void run() {
            System.out.println("TestClass is running");
            synClass();
        }
    }
    //使用对象锁的线程
    private static class InstanceSyn implements Runnable{
        private SynClzAndInst synClzAndInst;

        public InstanceSyn(SynClzAndInst synClzAndInst) {
            this.synClzAndInst = synClzAndInst;
        }

        @Override
        public void run() {
            System.out.println("TestInstance is running..."+synClzAndInst);
            synClzAndInst.instance();
        }
    }
    //使用对象锁的线程
    private static class Instance2Syn implements Runnable{
        private SynClzAndInst synClzAndInst;

        public Instance2Syn(SynClzAndInst synClzAndInst) {
            this.synClzAndInst = synClzAndInst;
        }

        @Override
        public void run() {
            System.out.println("TestInstance2 is running..."+synClzAndInst);
            synClzAndInst.instance2();
        }
    }
    //对象锁的方法
    private synchronized void instance(){
        try {
            Thread.sleep(3000);
            System.out.println("synInstance is going..."+this.toString());
            Thread.sleep(3000);
            System.out.println("synInstance ended..."+this.toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //对象锁的方法
    private synchronized void instance2(){
        try {
            Thread.sleep(3000);
            System.out.println("synInstance2 is going..."+this.toString());
            Thread.sleep(3000);
            System.out.println("synInstance2 ended..."+this.toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //类锁的方法
    private static synchronized void synClass(){
        try {
            Thread.sleep(1000);
            System.out.println("synClass is going...");
            Thread.sleep(1000);
            System.out.println("synClass ended...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        SynClzAndInst synClzAndInst=new SynClzAndInst();
        Thread t1=new Thread(new InstanceSyn(synClzAndInst));

        SynClzAndInst synClzAndInst2=new SynClzAndInst();
        Thread t2=new Thread(new Instance2Syn(synClzAndInst2));

//        SynClass t2=new SynClass();


        t1.start();
        t2.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:
在这里插入图片描述
终上所述:

无论是使用类锁还是对象锁,线程之间都是共享的(因为他们锁的都是不同对象),他们可以并发运行。在实际的开发中,我们只有new出一个锁去锁两个不同的对象,这是他们才会产生资源的竞争

2.ThreadLocal(线程变量)

    1)它的底层实现是Map,主要应用在线程池的连接里面
    2)三个常用的方法:  protected T initialValue()           //初始化值
                     ThreadLocal.get()                    //获取值
                     ThreadLocal.set()                    //设置值				                                          

ThreadLocal代码使用实例:
业务需求: 开启多个线程,线程里的工作是将线程变量的初值取出后进行更改,并写回,观察这几个线程的变量是否相互影响。

新建一个ThreadLocal的类并覆初值
其中:initialValue()是覆初始值

static ThreadLocal<Integer> threadLocal= new ThreadLocal<Integer>(){

        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

新建一个线程数组,开启对个线程

/**
     * 运行3个线程
     */
    public static void StartThreadArray(){
        Thread[] runs=new Thread[3];
        for(int i=0;i<runs.length;i++){
            runs[i]=new Thread(new TestThread(i));
        }
        for (int i=0;i<runs.length;i++){
            runs[i].start();
        }
    }

线程里具体业务操作:在各个线程中分别取出ThreadLocal的初值,并与各自线程id号进行加法运算,并写会。考虑最终得到的线程结果值是否会相互影响。


    /**
     * 类说明:测试线程,线程的工作是将ThreadLocal的变量变化,并写回,
     * 看线程之间是否相互影响
     */
    public static class TestThread implements Runnable{
        int id;
        public TestThread(int id){
            this.id=id;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" start");
            Integer s=threadLocal.get(); //获得变量的值
            s=s+id;
            threadLocal.set(s);
            System.out.println(Thread.currentThread().getName()+":"+
                    threadLocal.get());
        }
    }

完整代码

package 线程间的共享;

/**
 * @author lenovo
 * 类说明:使用类ThreadLocal
 */
public class UseThreadLocal {
    static ThreadLocal<Integer> threadLocal= new ThreadLocal<Integer>(){

        @Override
        protected Integer initialValue() {
            return 1;
        }
    };
    /**
     * 运行3个线程
     */
    public static void StartThreadArray(){
        Thread[] runs=new Thread[3];
        for(int i=0;i<runs.length;i++){
            runs[i]=new Thread(new TestThread(i));
        }
        for (int i=0;i<runs.length;i++){
            runs[i].start();
        }
    }

    /**
     * 类说明:测试线程,线程的工作是将ThreadLocal的变量变化,并写回,
     * 看线程之间是否相互影响
     */
    public static class TestThread implements Runnable{
        int id;
        public TestThread(int id){
            this.id=id;
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+" start");
            Integer s=threadLocal.get(); //获得变量的值
            s=s+id;
            threadLocal.set(s);
            System.out.println(Thread.currentThread().getName()+":"+
                    threadLocal.get());
        }
    }

    public static void main(String[] args) {
        StartThreadArray();
    }
}

运行结果
在这里插入图片描述
结果分析:threadLocal的初值为1,Thread-0,Thread-1,Thread-2的id号分别为0,1,2;
由结果可知,ThradLocal虽然只有一个实例对象。可是在线程运行时,它被看做每个线程独一份的变量,它在线程间并不会影响。所以在线程0,线程1,线程2获得的初值都是1,然后对他们的id号进行加法运算,则结果的值分别是1,2,3。

3.关键字volatile:

1)最轻量级的同步机制,可是线程不安全,不能保证原子性
2)应用场景: 只有一个线程写,多个线程读(它能保证变量的可见性,不能保证操作的原子性)(原子性:要么一起执行完成,要么都不执行)

解析:变量的可见性:保证了变量在线程的可见性,所有线程访问由volatile修饰的变量,都必须从主存中读取后操作,并在工作内存修改后立即写回主存,保证了其他线程的可见性
不能保证操作的原子性:“原子操作(atomic operation)是不需要synchronized”,这是多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束。(也就是说多个线程运行时,可能由于调度机制的不一致,导致结果的千变万化)

代码示例

package 线程间的共享;

/**
 * @author lenovo
 * 类说明:volatile无法提供操作的原子性
 */
public class VolatileUnsafe {

    private static  class VolatileVar implements Runnable{
        private volatile int a=0;

        @Override
        public void run() {
            String threadName=Thread.currentThread().getName();
            a=a+1;
            System.out.println(threadName+":======"+a);
            try {
                Thread.sleep(100);
                a=a+1;
                System.out.println(threadName+":======"+a);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    public static void main(String[] args) {
        VolatileVar v=new VolatileVar();

        Thread t1=new Thread(v);
        Thread t2=new Thread(v);
        Thread t3=new Thread(v);
        Thread t4=new Thread(v);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

运行结果
在这里插入图片描述
在这里插入图片描述
上面两个图是运行同一段代码产生的不同结果,我们最直观的感受就是结果的多样性与结果的不可预见性。显然这是我们编写代码时,最不愿见到的。(我们设计改程序的初衷是为了进行各线程的累加操作,若它能保证原子性那么线程0的两次变量值应该为1,2;线程1的两次变量值应该为3,4…依次类推。可显然上面的结果和我们预期的不一样,这就是因为volatile修饰的变量不能保证操作的原子性,当线程0运行时,它对变量操作一次的时候,其他线程都有机会拿到该变量,导致线程0无法按照预期地完成值的第二次更改,依次类推,各线程拿到该变量的可能性由操作系统调度决定,结果这也将导致值的多样化)

发布了19 篇原创文章 · 获赞 2 · 访问量 421

猜你喜欢

转载自blog.csdn.net/TheWindOfSon/article/details/103329549