JUC第一课——线程以及Synchronized关键字

线程

基本概念

  • 进程:程序的⼀次执⾏过程,是系统运⾏程序的基本单位,因此进程是动态的。系统运行⼀个程序即是⼀个进程从创建,运行到消亡的过程
  • 线程:一个进程的不同执行路径
    线程与进程相似,但线程是⼀个比进程更小的执行单位。⼀个进程在其执行的过程中可以产生多个线
    程。

    线程也被称为轻量级进程
  • 纤程:是用户态的线程,是线程中的线程,切换和调度不需要经过OS(操作系统)。轻量级的线程 - 纤程

创建线程的方法:

  1. 继承Thread类,重写run()方法:class MyThread extends Thread{};
    创建实例:new MyThread().start();

  2. 实现Runable接口,重写run()方法:MyRun implements Runable{}
    创建实例:new Thread(new MyRun()).start();

  3. 使用Lambda表达式:
    创建实例:new Thread(
    ()->{System.out.println(""};}
    ).start();

package character01;

/**
 * @author laimouren
 */
public class 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();
    }
}

面试题:
启动线程的三种方式?

  1. 继承Tread类
  2. 实现Runable接口
  3. 使用线程池:Executors.newCachedThread

线程方法

sleep():当前线程暂停一段时间,让别的线程运行,时间到了会自动回到就绪队列
yield():返回到线程等待队列继续等待,可能会刚返回又继续调用(返回绪状态)
join():在线程t1中调用t2.join(),意思是t1会等待t2线程完成之后再继续运行,可以用来保证线程之间的顺序
stop():方法不建议使用
interrupt():打断线程,业务逻辑不建议用
getState():获得线程的状态

线程状态

java线程状态迁移图
在这里插入图片描述
状态都是由JVM管理的,借助操作系统
创建的时候是new状态,
调用start方法时是runable状态(线程被挂起时处于ready状态,运行时处于running状态)
处于running状态时,可以通过调用各种方法处于另外三种状态TimeWaitingWaitingBlocked
调用结束就会进入terminated状态,之后不能再调用start方法

synchronized关键字

Hotspot底层实现synchronized:从对象头中拿出前两位作为markword来标记是否有锁
JVM没有要求

使用

多个线程访问同一个资源,该资源需要上锁(该锁不一定是锁自己,可以是其他对象)

普通方法

Object不能是String常量 Integer Long等基础数据类型

Integer类型:
i++实际上是i = new Integer(i+1),所以执行完i++后,i已经不是原来的对象了,锁的对象发生了改变,多个线程锁的对象各不相同,同步块自然就无效了。

Long 类型:同上

String类型:
是因为String定义的变量会放在常量池中,如果多个线程定义的String变量的值相等,则锁无效,他们看起来锁的是不同对象,其实是同一个对象。这种很难发现

package character01;

/**
 * @author laimouren
 */
public class Synchronized {
    private int count = 0;
    private Object o = new Object();

    public void m(){
        //任何线程要执行下面的代码,都需要先获得o的锁
        synchronized (o){
            count--;
            System.out.println(Thread.currentThread().getName()+"count = " + count);
        }
    }
}

因为如果每次都new一个Object太麻烦了,所有还有如下方法实现

package character01;

/**
 * @author laimouren
 */
public class Synchronized01 {
    private int count = 0;

    public void m(){
        //任何线程要执行下面的代码,都需要先获得this的锁
        synchronized (this){
            count--;
            System.out.println(Thread.currentThread().getName()+"count = " + count);
        }
    }
    public synchronized void m1(){
        //等同于synchronized(this)
        count--;
        System.out.println(Thread.currentThread().getName()+"count = " + count);
    }
}
static方法
package character01;

/**
 * @author laimouren
 */
public class Synchronized03 {
    private static int count = 0;

    public synchronized static void m(){
        //等同于synchronized(Synchronized03.class)
        count--;
        System.out.println(Thread.currentThread().getName()+"count = " + count);
    }
    public static void m1(){
        synchronized(Synchronized03.class) {
            count--;
            System.out.println(Thread.currentThread().getName() + "count = " + count);
        }
    }
}

同步方法和非同步方法是否能被同时调用:多个线程执行时,使用synchronized关键字修饰的方法执行时能否执行没有使用该关键字的方法?
答案是可以的

面试题:
模拟银行账户,对业务写方法加锁,对业务读方法不加锁,这样行不行?
答案是如果允许脏读,则允许,否则不行,会产生脏读问题(dirtyRead)
解决方案是都加锁

package character01;

import java.util.concurrent.TimeUnit;

/**
 * 模拟银行账户,对业务写方法加锁,对业务读方法不加锁,这样行不行?
 * 答案是不行,会产生脏读问题(dirtyRead)
 * @author laimouren
 */
public class Synchronized04 {
    String name;
    double balance;
    public synchronized void set(String name,double balance){
        this.name = name;
        try{
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        this.balance = balance;
    }
    public /*synchronized */ double getBalance(String name){
        return this.balance;
    }

    public static void main(String[] args) {
        Synchronized04 s = new Synchronized04();
        new Thread(()->s.set("zhangsan",100.0)).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(s.getBalance("zhangsan"));
        try {
            TimeUnit.SECONDS.sleep(2);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(s.getBalance("zhangsan"));
    }
}

可重入

意思是同一个线程的锁可以访问多个方法

同一个类中的多个方法共用一把锁
package character01;

import java.util.concurrent.TimeUnit;

/**
 * @author laimouren
 */
public class Synchronized05 {
    synchronized void m1(){
        System.out.println("m1 start");
        try{
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        m2();
        System.out.println("m1 end");
    }
    synchronized void m2(){
        try{
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("m2");
    }

    public static void main(String[] args) {
        new Synchronized05().m1();
    }
}
父子继承的两个类共用一把锁
package character01;

import java.util.concurrent.TimeUnit;

/**
 * 父子锁可重入性
 * @author laimouren
 */
public class Synchronized06 {
    synchronized void m(){
        System.out.println("m start");
        try{
            TimeUnit.SECONDS.sleep(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("m end");
    }

    public static void main(String[] args) {
       new son().m();
    }
    static class son extends Synchronized06{
        @Override
        synchronized void m(){
            System.out.println("son m start");
            super.m();
            System.out.println("son m end");
        }

    }
}

异常和锁

程序执行的过程中,如果出现异常,默认情况锁会被释放
所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
比如:在一个web app处理过程中,多个servlet线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程抛出异常,其他线程就会进入同步代码区,有可能会访问到异常时产生的数据
因此要非常小心的处理同步业务逻辑中的异常

package Test;

import java.util.concurrent.TimeUnit;

/**
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
* 比如,在一个web app处理过程中,多个servlet线程共同访问通一个资源,这是如果异常处理不合适
* 在第一个线程中抛出异常,其他线程就会进入同步代码去,有可能访问到异常产生是的数据
* 因此要非常小心的处理同步业务逻辑中的异常
* @author Jcon
*
*/
public class Demo11 {

int count = 0;
synchronized void test(){
    System.out.println(Thread.currentThread().getName() + " start......");
    while (true) {
        count ++;
        System.out.println(Thread.currentThread().getName() + " count = " + count);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (count == 5) {
            int i = 1/0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch处理,然后让循环继续
        }
    }
}
public static void main(String[] args) {
    Demo11 demo11 = new Demo11();
    Runnable r = new Runnable() {
        @Override
        public void run() {
            demo11.test();
        }
    };
    new Thread(r, "t1").start();
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    new Thread(r, "t2").start();
    }
}

底层实现

JDK早期,synchronized是重量级的,需要调用OS
后来改进:
锁升级的概念:
参考文献:《我就是厕所所长》一 二

synchronized(Object)
刚开始只有一个线程访问的时候,没有给它加锁,只是用markword记录这个线程的id(偏向锁)(占用CPU)
如果线程争用:升级为自旋锁(占用CPU,只在用户态)
自旋10次以后,升级为重量级锁去OS那申请资源(不占cpu)

锁只能升级不能降级

什么情况下使用自旋锁?

加锁的代码,如果执行时间少,线程比较少,使用自旋锁
执行时间长,线程数比较多,使用系统锁

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值