栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

线程 基本概念

进程
线程:一个进程的不同执行路径
纤程

创建线程的方法:

  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;


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是对象头中拿出前两位来标记是否有锁
JVM没有要求
作用:

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

Object不能是String常量 Integer Long等基础数据类型
因为String常量都是被引用同一个

package character01;


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;


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;


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;


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 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;


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;


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线程共同访问同一个资源,这时如果异常处理不合适,在第一个线程抛出异常,其他线程就会进入同步代码区,有可能会访问到异常时产生的数据
因此要非常小心的处理同步业务逻辑中的异常
代码了解即可,不做展示

底层实现

JDK早期,synchronized是重量级的,需要调用OS
后来改进:
锁升级的概念:
参考文献:《我就是厕所所长》一 二
synchronized(Object)
markword记录这个线程的id(偏向锁)
如果线程争用:升级为自旋锁
自旋10次以后,升级为重量级锁去OS那申请资源(不占cpu)

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/318942.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号