juc常用方法,线程状态,锁的原理

JUC

线程和进程如何区分

1、进程有独立的内存空间,线程没有,进程是操作系统的基本单位,线程是cpu调度的基本单位

一、线程如何创建

一、继承Thread方法创建

public class T01_WhatIsThread {
    private static class T1 extends Thread {
        @Override
        public void run() {
           for(int i=0; i<10; i++) {
               try {
                   TimeUnit.MICROSECONDS.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("T1");
           }
        }
    }

    public static void main(String[] args) {
    	//这种方法就是单纯的方法引用,并没有创建线程
        //new T1().run();
        //通过调用start()方法创建线程进行运行
        new T1().start();
        for(int i=0; i<10; i++) {
            try {
                TimeUnit.MICROSECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main");
        }

    }
}

二、继承Runnable接口(3种线程调用方法)

public class T02_HowToCreateThread {
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello MyThread!");
        }
    }

    static class MyRun implements Runnable {
        @Override
        public void run() {
            System.out.println("Hello MyRun!");
        }
    }

    public static void main(String[] args) {
        new MyThread().start();
        new Thread(new MyRun()).start();
        new Thread(()->{
            System.out.println("Hello Lambda!");
        }).start();
    }

}

三、Executors.newCachedThrad,通过线程池,但是我只了解,不会

二、线程的常用方法

一、Sleep()

改方法运行后会释放cpu资源,等时间结束在继续运行,改方法不会释放锁

    static void testSleep() {
        new Thread(()->{
            for(int i=0; i<100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

二、yield();

该方法调用时,当前线程重新进入等待队列,该线程重新和其他线程一起等cpu的调度

    static void testYield() {
        new Thread(()->{
            for(int i=0; i<100; i++) {
                System.out.println("A" + i);
                if(i%10 == 0) Thread.yield();


            }
        }).start();

        new Thread(()->{
            for(int i=0; i<100; i++) {
                System.out.println("------------B" + i);
                if(i%10 == 0) Thread.yield();
            }
        }).start();
    }

三、join();

当前线程等待调用的线程执行完毕之后在运行,下列代码中t1会等t2结束在运行

如果没有传参join(),就是得等当前线程运行结束

static void testJoin() {
        Thread t1 = new Thread(()->{
            for(int i=0; i<100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(()->{

            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            for(int i=0; i<100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

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

三、线程状态

一、status();

当前线程的状态信息,3中,Ready(等待运行)Running(运行中)和Teminated(运行结束)

注意,线程没有关闭线程一说,因为关闭线程stop()方法会出问题,所以非必要不运行,等运行结束关闭就可以了

在这里插入图片描述

四、synchronized锁

1、synchronized是针对对象头两位锁的,多个线程都是看对象头的两个直接是否上锁,所以锁的时候,不能让对象重新初始化,所以用final修饰。

2、不能用String常量,

3、老的jdk版本中,synchronized直接是重量级锁,但是因为现实过程中,可能出现多个线程抢占锁的时候,同一个线程在释放锁之后又重新获取锁,所以对synchronized进行了优化操作,出现了偏向锁,自旋锁,重量级锁的升级操作,就是当线程1释放了锁,记录下这个线程1的信息,释放完立马在和别人竞争锁时,直接选择线程1,如果线程1没有抢到锁,升级为自旋锁,就是我在这自己绕圈,等我自旋完看你是否释放,释放了我就抢占锁,没有释放我就不抢占锁,如果自旋完原来占有的线程没有释放,那么就升级为重量级锁。

​ 3.1、线程数如果多的话,大量的线程数在自旋占用资源,那么效率可能会更低,线程数如果比较正常,或者少,那么自选锁对资源的占用就比较少。

​ 3.2、synchronized因为锁升级的概念,所以效率未必会比其他锁效率低

synchronized实现i++的思路

在这里插入图片描述

五、volatile

1、保证线程可见性,看下面代码,分析加volatile和不加的区别

​ 1.1:在新建线程时,线程中的值会去copy公共地方的变量,靠cpu的缓存一致性MESI才能实现

​ 1.2:没有volatile是线程之间的可见性是不保证的,子线程更改了东西会立马同步到公共区域,但是不会同步到其他子线程中


public class T01_HelloVolatile {
	volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别
	void m() {
		System.out.println("m start");
		while(running) {
		}
		System.out.println("m end!");
	}

	public static void main(String[] args) {
		T01_HelloVolatile t = new T01_HelloVolatile();

		new Thread(t::m, "t1").start();

		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		t.running = false;
	}

}

2、禁止指令重排序

​ cpu层面禁止不了,jvm虚拟机可以禁止,这个基于现在的cpu的指令重排序命令来优化的,原本的cpu是单个命令,直接就运行了,现在的cpu为了提高效率,对于一些指令进行优化,有些指令可能会运行时同时运行,这就导致了工作上面的

双重锁在超高的并发中,会出现问题。

jvm的new对象过程,

​ 1、申请内存,给到默认值

​ 2、内存中设置我们的定义值

​ 3、把内存绑定给代码中的对象,

​ 因为CPU的指令重排序,偶尔会出现1 3 2的情况,单例模式下的双重锁,当第一个进去了,在创建对象时,1,3完成赋值了,而2还没有完成,另一个进程拿的这个对象去操作了,会出现值不对的情况,出现问题

​ volatile 关键字,使一个变量在多个线程间可见

volatile虽然保证了线程可见,但是不能保证原子性,不能替换synchronized

如果只有volatile发现i++不是10000,但是synchronized可以保证是10000条数据


public class T04_VolatileNotSync {
	/*volatile */int count = 0;
	synchronized void m() {
		for(int i=0; i<10000; i++) count++;
	}

	public static void main(String[] args) {
		T04_VolatileNotSync t = new T04_VolatileNotSync();

		List<Thread> threads = new ArrayList<Thread>();

		for(int i=0; i<10; i++) {
			threads.add(new Thread(t::m, "thread-"+i));
		}

		threads.forEach((o)->o.start());

		threads.forEach((o)->{
			try {
				o.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		});

		System.out.println(t.count);


	}

}

AtomicLong多线程i++无锁化操作线程安全

AtomicLong,AtomicInteger,底层用的CAS无锁化

CAS(Compare And Set)无锁化

在这里插入图片描述

CAS是CPU原语支持,中间不可以被打断,所以在更改的时候不会出现第二个

cas(更改的对象,期望值,更新值);每个线程都会输入一个原来的值,一个要改变的值,当发现原来的值和输入不一样,那就是有线程改变了这个值,我循环等上一个线程改变,我在输入一个新的原来的值,一个新的改变到的值。

AbA问题:

会出现aba的问题,在上一个cas操作的时候变成b又变成1,第二个线程也不会有问题,不改也没事,不过想要解决这个问题,加版本号,后面在检查是加上版本号处理

aba问题出现,如果是基本数据类型,那没有问题,怎么样都行

如果是处理对象,就需要解决

举例:刚开始执行一个对象a,对象a中有个引用属性b,我cas(a,a,b)期望值改成b,并且改成b的时候,b中的属性变更了一点,然后我又cas(b,b,a)这么着下来,我们去比较a的时候,a是没有问题的,但是a指向的b的属性值有了问题,后续业务逻辑出错

public class T01_AtomicInteger {
   /*volatile*/ //int count1 = 0;
   
   AtomicInteger count = new AtomicInteger(0); 

   /*synchronized*/ void m() { 
      for (int i = 0; i < 10000; i++)
         //if count1.get() < 1000
         count.incrementAndGet(); //count1++
   }

   public static void main(String[] args) {
      T01_AtomicInteger t = new T01_AtomicInteger();

      List<Thread> threads = new ArrayList<Thread>();

      for (int i = 0; i < 10; i++) {
         threads.add(new Thread(t::m, "thread-" + i));
      }

      threads.forEach((o) -> o.start());

      threads.forEach((o) -> {
         try {
            o.join();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      });

      System.out.println(t.count);

   }

}

LongAdder多线程i++分段锁操作

i会变成一个数组分别上锁,每个数组元素都加锁,由全部的线程平均分到数据的各个节点上,然后由最后的结果获取

在这里插入图片描述

/**
 * 解决同样的问题的更高效的方法,使用AtomXXX类
 * AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的
 * @author mashibing
 */
package com.mashibing.juc.c_018_00_AtomicXXX;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAdder;


public class T01_AtomicInteger {
   /*volatile*/ //int count1 = 0;

// AtomicInteger count = new AtomicInteger(0);
   LongAdder count = new LongAdder();

   /*synchronized*/ void m() {
      for (int i = 0; i < 10000; i++)
         //if count1.get() < 1000
         count.increment(); //count1++
   }

   public static void main(String[] args) {
      T01_AtomicInteger t = new T01_AtomicInteger();

      List<Thread> threads = new ArrayList<Thread>();

      for (int i = 0; i < 10; i++) {
         threads.add(new Thread(t::m, "thread-" + i));
      }

      threads.forEach((o) -> o.start());

      threads.forEach((o) -> {
         try {
            o.join();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      });

      System.out.println(t.count);

   }

}

Atomic与synchronized与longAdder性能测试程序

根据下列程序,可以看到AtomicLong的效率要高于sync,但是要低于LongAdder,因为LongAdder在特别多的线程情况下,效率是比较高的,而AtomicLong是因为cas乐观锁,效率是比较高的,sync锁升级,在升级之后效率是很低的。

package com.company.test020;

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;

/**
* @description: TODO
* @author yang.su
* @version 1.0
*/
public class AtomicVsSyncVsLongAdder {
    static Long count3 =0L;
    static AtomicLong count1 = new AtomicLong(0);
    static LongAdder count2  = new LongAdder();
    public static void main(String[] args) throws Exception {


        final Object lock =  new Object();
        Thread[] threads = new Thread[1000];
        //AtomicLong测试
        for (int i = 0 ; i<threads.length ; i++){
            threads[i] = new Thread(()->{
                for (int j = 100000 ; j>0 ; j--){
                    count1.incrementAndGet();
                }
            });
        }
        long atomicStart = System.currentTimeMillis();
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        long atomicEnd = System.currentTimeMillis();
        System.out.println("atomicLong"+count1+":"+(atomicEnd-atomicStart));

        //LongAdder
        for (int i = 0 ; i<threads.length ; i++){
            threads[i] = new Thread(()->{
                for (int j = 100000 ; j>0 ; j--){
                    count2.increment();
                }
            });
        }
        long longAdderStart = System.currentTimeMillis();
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        long longAdderEnd = System.currentTimeMillis();
        System.out.println("longAdder"+count2+":"+(longAdderEnd-longAdderStart));

        //sync
        for (int i = 0 ; i<threads.length ; i++){
            threads[i] = new Thread(()->{
                for (int j = 100000 ; j>0 ; j--){
                    synchronized (lock){
                        count3++;
                    }

                }
            });
        }
        long syncStart = System.currentTimeMillis();
        for (Thread thread : threads) {
            thread.start();
        }
        for (Thread thread : threads) {
            thread.join();
        }
        long syncEnd = System.currentTimeMillis();
        System.out.println("sync"+count2+":"+(syncEnd-syncStart));

    }
}
      synchronized (lock){
                    count3++;
                }

            }
        });
    }
    long syncStart = System.currentTimeMillis();
    for (Thread thread : threads) {
        thread.start();
    }
    for (Thread thread : threads) {
        thread.join();
    }
    long syncEnd = System.currentTimeMillis();
    System.out.println("sync"+count2+":"+(syncEnd-syncStart));

}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值