多线程锁

JAVA(多线程)

一,锁

1. synchronized 多线程并发编程

  synchronized 三种方式来加锁:

  • 修饰静态方法,作用于当前类对象加锁,进入同步代码之前要获得当前类对象的锁。形象的比喻就是,对于这个对象可能有很多方法(房间),有一些加了锁(锁门的房间),而这些房间共用一把钥匙,导致当一个被锁方法被访问时,无法访问其他带锁的方法。
class Product{
    static volatile Integer n=10;
    static volatile boolean f=false;

    //生产
    public synchronized   void put() throws InterruptedException {//加锁,wait默认是Object的,枷锁后是锁里的
        if(f){
            try{
                wait();
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        for (int i = 1; i <= 3; i++) {
            Thread.sleep(500);
            this.n=i;
            System.out.println("a->"+n);
        }
        f=true;
        notify();
    }

    //消费
    public synchronized  void get() throws InterruptedException {
        if(!f){
            try{
                wait();
            } catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        for (int i = n; i >= 0; i--) {
            Thread.sleep(500);
            this.n=i;
            System.out.println("b->"+n);
        }
        f=false;
        notify();

    }

}
  • 修饰成员方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁()
  • 代码块,指定加锁对象,对给定向对象加锁,进入同步代码块之前要获得给指定对象的锁

 

1. 实例方法:调用该方法的实例

2. 静态方法:类对象

3. this:调用该方法的实例对象

4. 类对象:类对象

//同步代码块
//创建一个对象
//类对象
//当前实例this
//同步监视器
synchronized(类名.class ){
    
}

 

操作共享数据的代码

共享数据:多个线程共同操作

案例:

package duoxiancheng.suo;

/**
 * 当使用同步方法,synchronized锁的是this
 * <p>
 * 同步方法:
 * 1.同步方法依然涉及到同步锁对象,不需要我们写
 * 2.非静态同步方法,同步锁就是this
 * 静态的同步方法,同步监视器就是类本身
 * <p>
 * 同步代码块:
 * 1.选好同步监视器(锁)推荐使用类对象,第三方,this
 * 2.在实现接口创建的线程类中,同步代码块不能用this充当同步锁
 * <p>
 * synchronized只针对当前jvm可以解决线程安全问题
 * synchronized不能跨jvm解决问题
 */
public class SellTicktDemo implements Runnable {


    /*
     * 需求:多线程模拟售票,多个窗口售票
     *
     * 分析:
     *  A.需要的类
     *      1.建立一个多线程的类SellTicktDemo
     *      2.创建一个测试类SellTicktDemoTest
     *  B.类的关系
     *      1.多线程的类SellTicktDemo,实现Runnable接口,重写run()方法
     *      2.SellTicktDemoTest 测试多线程类
     *  C.实现多线程同步
     *      1.用synchronized()方法实现线程同步
     *  D.在SellTicktDemoTest中实现多数窗口
     *
     */

    //定义票的总数
    private int total = 100;

    //定义票的编号
    private int no = total + 1;

    //定义一个线程同步对象
    private Object obj = new Object();

    @Override
    public void run() {

        while (true) {
            //同步锁
            synchronized (this.obj) {
                if (this.total > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String msg = Thread.currentThread().getName() + " 售出第   " + (this.no - this.total) + "  张票";
                    System.out.println(msg);
                    this.total--;
                } else {
                    System.out.println("票已售完,请下次再来!");
                    System.exit(0);
                }
            }
        }

    }
}
package duoxiancheng.suo;

public class Test10 {
    public static void main(String[] args) {
        //得到对象
        SellTicktDemo std = new SellTicktDemo();

        //把对象放入线程中
        Thread t1 = new Thread(std,"售票窗口1");
        Thread t2 = new Thread(std,"售票窗口2");
        Thread t3 = new Thread(std,"售票窗口3");
        Thread t4 = new Thread(std,"售票窗口4");

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

    }

}

 

二,死锁

多个线程同时被堵塞,他们一个或多个都在等待某个资源的释放,由于线程无限期的堵塞,程序不能正常终止

java死锁产生四个条件

1. 互斥条件,当资源被一个线程使用,别的线程不能使用

2. 不可抢占,资源请求者不能强制从占有者中抢夺资源,资源只能从占有者手动释放

3. 请求和保持

4. 循环等待,存在一个等待队列,p1占有p2的资源,p2占有p3的资源,p3占有p1的资源,形成了一个等待环路

 

案例

package duoxiancheng.suo;

import java.util.Date;

class LockA implements Runnable{
    @Override
    public void run() {
        System.out.println(new Date().toString()+"beging");
        while (true){
            synchronized (Test11.obj1){
                System.out.println(new Date().toString()+"obj1beging");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (Test11.obj2){
                System.out.println(new Date().toString()+"obj2beging");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class LockB implements Runnable{
    @Override
    public void run() {
        System.out.println(new Date().toString()+"beging");
        while (true){
            synchronized (Test11.obj2){
                System.out.println(new Date().toString()+"obj2beging");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (Test11.obj1){
                System.out.println(new Date().toString()+"obj1beging");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class Test11 {
    public static String obj1="obj1";
    public static String obj2="obj2";

    public static void main(String[] args) {
        LockA lockA=new LockA();
        new Thread(lockA).start();
        LockB lockB=new LockB();
        new Thread(lockB).start();

    }
}

 

三,线程重入

任意线程拿到锁之后,再次获取该锁不会被该锁锁阻碍

 

线程不会被自己锁死,synchronized可重入锁

public class Test12 {
    private static final Object M1=new Object();
    private static final Object M2=new Object();

    public static void main(String[] args) {
        new Thread(
                ()-> {
                    synchronized (M1){
                        synchronized (M2){
                            synchronized (M1){
                                synchronized (M2){
                                    System.out.println("hello lock");
                                }
                            }
                        }
                    }
                }
        ).start();
    }
}

 

JDK1.6之后:

无锁:不加锁

偏向锁:不锁锁,只有一个线程争夺时,偏向某一个线程,这个线程不加锁

轻量级锁:少量线程来了之后,尝试自旋,不挂起线程

重量级锁:排队挂起线程(synchronzied)

  挂起线程和恢复线程需要转入内核态中完成这些操作,给系统的并发性带来很大压力

  

四,Object类对多线程的支持

wait()

wait(long timeout)当线程进入等待状态

notify()唤醒正在等待的下一个线程

notifyAll()唤醒正在等待的所有线程

 

五,线程间的通信

package duoxiancheng.suo;

public class Test13 {
    private  static int num=10;
    private  static  final  Object OBJ=new Object();

    public   static void plus(int code,int i){
        synchronized (OBJ){
            if(num >= 10){
                //唤醒其他等待的线程
//                OBJ.notify();
                //唤醒所有等待的线程
                OBJ.notifyAll();
            }
            System.out.println("这是线程"+code+"->"+ ++num + "->" + i);
        }
    }
    public   static void sub(int code,int i) throws InterruptedException {
        synchronized (OBJ){
            if(num <= 0){
                OBJ.wait();
            }
            System.out.println("这是线程"+code+"->"+ --num + "->" + i);
        }
    }
    public static void main(String[] args) {
        Thread t1=new Thread(()->{
              for (int i = 0; ; i++) {
                  try {
                      Thread.sleep(5);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  try {
                      sub(1,i);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; ; i++) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    plus(2,i);
            }
        });
        t1.start();
        t2.start();
    }
}

 

Thread的两个静态方法

sleep释放CPU资源,但不会释放锁

yield方法释放CPU执行权,保留了CPU的执行资格

join方法,让出了执行权,join就加入进来

wait:释放CPU资源,释放锁

 

题:sleep和wait的区别

1. 出处:

sleep 一般用于当前线程休眠,或者轮循暂停操作,Thread

wait 则多用于多线程之间的通信,Object

2.锁的控制:

sleep释放CPU资源,但不会释放锁

wait释放CPU资源,释放锁

3.使用:

使用 sleep 方法可以让让当前线程休眠,时间一到当前线程继续往下执行,在任何地方都能使用,但需要捕获 InterruptedException 异常

sleep 一般用于当前线程休眠,或者轮循暂停操作

wait 则多用于多线程之间的通信。

4.线程切换:

sleep 会让出 CPU 执行时间且强制上下文切换,而 wait 则不一定,wait 后可能还是有机会重新竞争到锁继续执行的。

 

守护线程和用户线程

  • 守护线程依赖于创建它的线程,而用户线程则不依赖。
  • 如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。
  • main方法执行完毕后,用户线程则不会消亡,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

 

案例:生产者与消费者模型

2个线程,一条线程生产产品,另一条线程消费产品

 

六,线程的退出

使用线程标志,线程正常退出

interrupt方法,中断线程,会抛出异常,捕获后再做停止线程的逻辑

 

七,线程常用方法

1. start:启动当前线程,启动run方法

2. run

3. currentThread:静态方法,获取正在执行的线程

4. getId() :返回此线程的唯一标识

5. getName():设置当前线程name

6. getName():获取当前线程name

7. getPriority():获取当前线程优先级

8. setPriority():设置当前线程优先级

9. getState() :获取当前线程的生命周期

10. interrupt():终端线程的执行

11. interrupted():查看当前线程是否中断

12. isDaemon():是否是守护线程

 

懒汉单例的改进

单例推荐内部类,枚举(天生构造器私有化)

public class Test1 {
    private Test1(){}

    private static  Test1 INSTANT;

    public static Test1 getInstance(){
         if(INSTANT == null){
             synchronized (Test1.class) {
                 if(INSTANT == null){
                     INSTANT = new Test1();
                 }
             }
         }
         return INSTANT;
    }
}

 

枚举单一用例

 class Singleton {
    private Singleton(){
    }
    public static enum SingletonEnum {
        SINGLETON;
        private Singleton instance = null;
        private SingletonEnum(){
            instance = new Singleton();
        }
        public Singleton getInstance(){
            return instance;
        }
    }
}
public class Test4 {
    public static void main(String[] args) {
        Singleton s1=Singleton.SingletonEnum.SINGLETON.getInstance();


    }
}

 

 

 

posted @ 2022-08-03 15:41  一只神秘的猫  阅读(38)  评论(0)    收藏  举报