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

暑假java-day15

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

暑假java-day15

文章目录
      • 1.java有三大变量?
      • 2.如何选择类?
      • 3.synchronized的三种写法
        • 第一种:同步代码块
        • 第二种:在实例方法上使用synchronized
        • 第三种:在静态方法上使用synchronized
      • 4.synchronized面试题
      • 5.死锁的写法
      • 6.总结:在以后开发中怎么解决线程安全问题?
      • 7.守护线程
        • java语言中线程分为两大类:
        • 守护线程的特点:
        • 主线程main方法是一个用户线程。
        • 守护线程用在什么地方:
      • 8.定时器
        • 定时器的作用:
        • 定时器的应用场景:
        • 如何实现定时器:
      • 9.实现线程的第三种方式:

1.java有三大变量?

实例变量: 在堆中。
静态变量: 在方法区。
局部变量: 在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量永远不共享。(一个线程一个栈)
局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。

2.如何选择类?

如果使用局部变量的话:
建议使用:StringBuilder。
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。

ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable实线程安全的。

3.synchronized的三种写法 第一种:同步代码块
灵活
synchronized(共享线程对象){
	同步代码块;
}
synchronized(this){   //线程安全
     double before=this.getBlance();
     double after=before-money;
     try {
         Thread.sleep(1000);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
     this.setBlance(after);
 }
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
    public synchronized void withdraw(double money){
        double before=this.getBlance();
        double after=before-money;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setBlance(after);
    }
第三种:在静态方法上使用synchronized
表示找类锁。
类锁永远只有一把。
就算创建了100个对象,那类锁也只有一把。

    对象锁:1个对象1把锁,100个对象100把锁。
    类锁:100个对象,也可能只是1把类锁。
4.synchronized面试题

面试题1:

package com.jmpower.javase.exam;

//面试题:doOther方法执行的时候需要等待doSome方法的结束吗?
//不需等待,因为doOther()方法没有synchronized
public class Exam01 {
    public static void main(String[] args) {
        MyClass mc=new MyClass();

        Thread t1=new MyThread(mc);
        Thread t2=new MyThread(mc);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        try {
            Thread.sleep(1000);//这个睡眠的作用是:为了保证t1线程先执行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}
class MyThread extends Thread{
    private MyClass mc;

    public MyThread(MyClass mc) {
        this.mc = mc;
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("t1")){
            mc.doSome();
        }
        if(Thread.currentThread().getName().equals("t2")){
            mc.doOther();
        }
    }
}
class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome end");
    }
    public void doOther(){
        System.out.println("doOher begin");
        System.out.println("doOther end");
    }
}

面试题2(在面试题1的基础上,将doOther修改为被synchronized修饰)

//需要等待,因为实例方法都被synchronized修饰,共享线程对象是this,两个方法均需等待。
class MyClass{
    public synchronized void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome end");
    }
    public synchronized void doOther(){
        System.out.println("doOher begin");
        System.out.println("doOther end");
    }
}

面试题3(在面试题2的基础上,修改为两个线程两个MyClass对象)

//不需等待,因为MyClass对象是两个,两把锁。
public static void main(String[] args) {
        MyClass mc1=new MyClass();
        MyClass mc2=new MyClass();

        Thread t1=new MyThread(mc1);
        Thread t2=new MyThread(mc2);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        try {
            Thread.sleep(1000);//这个睡眠的作用是:为了保证t1线程先执行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }

面试题4(在面试题3的基础上,将方法体改为被synchronized修饰的静态方法)

//需要等待,synchronized出现在静态方法上是找类锁。同一个类只有一把锁。
class MyClass{
    public synchronized static void doSome(){
        System.out.println("doSome begin");
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("doSome end");
    }
    public synchronized static void doOther(){
        System.out.println("doOher begin");
        System.out.println("doOther end");
    }
}
5.死锁的写法
package com.jmpower.javase.threadsafe;

public class DeadLock {
    public static void main(String[] args) {
        Object o1=new Object();
        Object o2=new Object();

        //t1和t2两个线程共享o1,o2
        Thread t1=new MyThread1(o1,o2);
        Thread t2=new MyThread2(o1,o2);

        t1.start();
        t2.start();
    }
}
class MyThread1 extends Thread{
    Object o1;
    Object o2;
    public MyThread1(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run() {
        synchronized (o1){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (o2){

            }
        }
    }
}
class MyThread2 extends Thread{
    Object o1;
    Object o2;
    public MyThread2(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }
    public void run() {
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (o1){

            }
        }
    }
}
6.总结:在以后开发中怎么解决线程安全问题?

第一种*方案:尽量使用局部变量代替"实例变量和静态变量"
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应一个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

7.守护线程 java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)	

其中具有代表性的就是:垃圾回收机制(守护线程)

守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束。守护线程自动结束。
主线程main方法是一个用户线程。 守护线程用在什么地方:

如,每天00:00的时候系统自动备份。
这个需要使用到定时器,并且我们可以将定时器设置会守护线程。
一直在那里看着,每到00:00的时候就备份一次。所有的用户线程
如果结束了,守护线程自动退出,没有必要进行数据备份了。

package com.jmpower.javase.thread;

public class ThreadTest12 {
    public static void main(String[] args) {
        Thread t=new BackupDateThread();
        t.setName("备份数据的线程");

        //启动线程之前,将线程设置为守护线程。
        t.setDaemon(true);

        t.start();

        //主线程:主线程是用户线程
        for(int i=0;i<10;i++)
        {
            System.out.println(Thread.currentThread().getName()+"---"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class BackupDateThread extends Thread{
    public void run() {
        int i=0;
        //即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
        while(true)
        {
            System.out.println(Thread.currentThread().getName()+"--->"+(++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
8.定时器 定时器的作用:

间隔特定的时间,执行特定的程序。

定时器的应用场景:

每周要进行银行账户的总账操作。
每天要进行数据的备份操作。

如何实现定时器:

可以使用sleep方法,睡眠,设置睡眠时间,每到这个时间点醒来执行任务。这种方式是最最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,这个直接拿来用。不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

package com.jmpower.javase.timer;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;


public class TimerTest {
    public static void main(String[] args) throws ParseException {
        //创建定时器对象
        Timer timer=new Timer();

        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime=sdf.parse("2022-7-13 18:57:00");

        //指定定时任务
        //timer.schedule(定时任务,第一次执行时间,间隔多久执行一次);
        timer.schedule(new LogTimerTask(),firstTime, 1000*10);

    }
}
//编写一个定时任务类
//假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask{
    @Override
    public void run() {
        //编写你需要执行的任务就行了。
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date=new Date();
        String nowTime=sdf.format(date);
        System.out.println(nowTime+"完成一次备份");
    }
}
9.实现线程的第三种方式:

FutureTask方式,实现Callable接口。
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

package com.jmpower.javase.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;


public class ThreadTest13 {
  public static void main(String[] args) throws Exception{
      //第一步: 创建一个“未来任务类”对象。
      //参数非常重要,需要给一个Callable接口实现类对象。
      FutureTask task=new FutureTask(new Callable() {
          @Override
          public Object call() throws Exception {//call()方法就相当于run方法。只不过这个有返回值
              //线程执行一个任务,执行之后可能会有一个执行结果
              //模拟执行
              System.out.println("call method begin");
              Thread.sleep(1000*10);
              System.out.println("call method end");
              int a=100;
              int b=200;
              return a+b;//自动装箱(300结果变成Integer)
          }
      });

      //创建线程对象
      Thread t=new Thread(task);

      //启动线程
      t.start();

      //这里是main方法,这里在主线程中。
      //在主线程中,怎么获取线程的返回结果?
      //get()方法的执行会导致"当前线程阻塞"
      Object obj=task.get();
      System.out.println("线程执行结果:"+obj);

      //main方法这里的程序要想执行必须等待get()方法的结束
      //而get()方法可能需要很久,因为get()方法是为了拿另一个线程的执行结果。
      //另一个线程执行是需要时间的。
      System.out.println("hello world!");
  }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1013054.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

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

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