TwentyThreeDay-java 线程的睡眠与终止,synchronized

1、关于线程sleep方法

static void sleep(long millis)

静态方法,参数毫秒,作用:当前线程进入休眠(进入阻塞状态)放弃占用的CPU时间片,让给其它线程使用

public static void main(String[] args) {
    //使主线程睡眠5s
   /* try {
        Thread.sleep(1000*5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName());//5s后输出 main
​
    */
    //每间隔一秒输出
    for (int i = 0; i < 10; i++) {
        System.out.println(Thread.currentThread().getName()+"-->"+i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2、终止线程睡眠

实现在主线程休眠五秒后叫醒分支线程,使用interrupt(),原理是使分支线程的睡眠出现异常,捕捉异常后,执行run中后续程序。

//MyThread01分支线程类

class MyThread01 implements Runnable{
    /*
    重点:run()方法不可以抛出异常,只能try...catch,
    原因是因为Runnable中run方法没有抛出异常,重写的方法不能比接口中的方法抛出的异常多
     */
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始");//Thread-0开始
        try {
            Thread.sleep(1000*60*60*24);//休眠一天
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());//sleep interrupted
        }
        System.out.println(Thread.currentThread().getName()+"结束");//Thread-0结束
    }
}

//主线程

public static void main(String[] args) {
    //封装线程
    Thread thread=new Thread(new MyThread01());
    //启动线程
    thread.start();
    //主线程工作5s后终止分支线程的睡眠
    try {
        Thread.sleep(1000*5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //终止run的睡眠
    thread.interrupt();//原理是使分支线程的睡眠出现异常,捕捉异常后,执行run中后续程序
​
}

3、使用shop()终止线程

已过时,原因暴力终止线程会造成数据丢失

把2中的代码thread.interrupt()换成下面这行代码

thread.stop();//这种暴力终止线程,会使数据可能丢失

4、线程安全问题

1>、什么时候数据在多线程并发的环境下会存在安全问题?

满足三个条件:1、多线程并发,2、有共享数据,3、共享数据有修改的行为

2>、怎样解决线程安全问题?

当多线程并发的情况下,有共享数据,并且会被修改,此时就会存在线程安全问题,解决方法:线程排队执行(不能并发)称为线程同步机制, 线程排队则会牺牲效率,但是要以安全为主

3>、同步编程模型与异步编程模型

异步:(多线程并发)各自执行各自的

同步:多线程排队,两线程之间发生等待关系

5、关键字synchronized

//银行账户类

public class Account {
    private String act;
    private double blance;
    Object obj=new Object();
​
    public Account(String act, double blance) {
        this.act = act;
        this.blance = blance;
    }
​
    public String getAct() {
        return act;
    }
​
    public void setAct(String act) {
        this.act = act;
    }
​
    public double getBlance() {
        return blance;
    }
​
    public void setBlance(double blance) {
        this.blance = blance;
    }
    public void WithDrowMoney(double money){
        /*
        关键字synchronized线程安全的,小括号里是线程的共享对象,在此程序中,共享对象是账户对象故用this,或者obj,因为obj每个线程都有且只有一个。
        当一个线程遇到这个关键字,则会在这里(锁池)寻找对象锁,这时会释放之前占有的CPU时间片,如果没找到就要等待里面的线程执行完毕,找到之后
        就会锁门,然后进入就绪状态继续抢夺CPU时间片,其他想要进来的线程(与此线程共享对象的,不共享对象的不需要排队)需要等待。
        synchronized的代码块越少效率越高
         */
        synchronized(this){
            //取之前的余额
            double beforeBlance=this.blance;
            //取之后的余额
            double afterBlance = beforeBlance-money;
            //修改余额
            this.setBlance(afterBlance);
        }
    }
}

当synchronized 在方法上:

public synchronized void WithDrowMoney(double money){}

缺点:

则对象锁只能是this,不灵活,整个方法体都需要同步,扩大了同步范围,导致效率低

优点:代码节俭了,如果共享的对象就是this且整个代码开都要同步则可以使用这种方式

 

//线程

public class ThreadTest extends Thread{
    private Account act;
    //利用构造方法来使多个线程共享一个对象
    public ThreadTest(Account act){
        this.act=act;
    }
    @Override
    public void run() {
        //取钱,假设取5000
        double money=5000;
        //取款
        act.WithDrowMoney(money);
        System.out.println(Thread.currentThread().getName()+"账户:"+act.getAct()+"取走金额:"+money+",余额:"+act.getBlance());
    }
}

//测试类

public static void main(String[] args) {
    //创建账户对象
    Account act=new Account("act-001",10000);
    //两个线程共享一个账户对象
    Thread t1=new ThreadTest(act);
    Thread t2=new ThreadTest(act);
    //修改名字
    t1.setName("t1");
    t2.setName("t2");
    //线程启动
    t1.start();
    t2.start();
​
}

6、总结synchronized

三种写法:

第一种:同步代码块

synchronized(线程共享对象){同步代码块;}

第二种:在实例方法上使用,表示共享对象一定是this,并且同步代码块是一整个方法体

第三种:在静态方法上使用synchronized,表示类锁,类锁永远只有一把。

7、写死锁

代码:

//线程MyThread05

class MyThread05 extends Thread{
    Object o1;
    Object o2;
    public MyThread05(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        //先锁o1在锁o2
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o2){
​
            }
        }
    }
}

//线程Mythread06

class  Mythread06 extends Thread{
    Object o1;
    Object o2;
    public Mythread06(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run(){
        //先锁o2在锁o1
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){
​
            }
        }
    }
}

//主线程

public static void main(String[] args) {
    Object o1=new Object();
    Object o2=new Object();
    //两个线程共享o1,o2
    Thread t1=new MyThread05(o1,o2);
    Thread t2=new Mythread06(o1,o2);
​
    t1.start();
    t2.start();
​
}

8、在解决线程安全问题的时候

尽量不使用synchronized,它会使程序执行效率降低,用户体验不好

第一:尽量使用局部变量来替代实例变量和静态变量

第二:如果必须使用实例变量,那么可以考虑创建多个对象,这样内存就不会共享了

第三:如果不能使用局部变量,对象也不能创建多个,那么就可以选择synchronized了,线程同步机制

上一篇:CMakeLists.txt 编译参数记录


下一篇:Scala隐式转换、上下界、类比较