多线程(创建---多线程安全问题)

本文详细介绍了Java多线程的创建方法,包括继承Thread、实现Runnable接口、匿名内部类,并探讨了线程安全问题的解决方案,如synchronized、ReentrantLock以及Lambda表达式的使用。还提到了如何避免线程安全问题并提升线程执行效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单线程 运行途中有Expection就不会运行,而且效率较低
多线程:java的多线程是抢占性调度,优先级高就先调用,优先级相同就随机调用

创建多线程的方法

方法一
1.创建类继承Thread类作为Thread的子类
2.子类重写run方法(创建线程任务)也就是线程需要做什么
3.在main()方法中创建子类对象
4.子类对象调用start()方法开启新线程
注意:
多次启动一个线程是违法的,特别是一个线程结束后重启这个线程,是不允许的

方法二
1.创建Runnable的实现类
2.重写run()方法
3.在main()方法中创建实现类的对象

/*假设Runnable的实现类为RunnableImp*/
RunnableImp ri=new Runnable();

4.在main方法中以Runnable的实现类对象为参数构造Thread对象

/*例:*/Thread t=new Thread(ri);

5.用Thread对象调用start()开启新线程

方法二优于方法一
原因:1.继承只能继承一个而实现可以实现多个接口,若继承了Thread则不能继承其他父类
2.增强了程序的拓展性
将设置任务和开启多线程分离(解耦)
用一个thread的start方法可以开启多个不同的run(构造时传递不同的实现类对象)

/*
例:加入RunnableImp1和RunnableImp2都为Runnable的实现类
但是两个实现类的run方法内容不同
*/
public static void main(Stirng args[]{
  RunnableImp1 ip1=new RunnableImp1();
  Thread T=new Thread(ip1);
  /*
  若想开启一个RunnableImp2的线程则可以
  Thread T=new Thread(new RunnableImp2 );
  T.strat();//开启一个新线程	
}

匿名内部类
作用:
简化代码
方法:
把子类继承父类,创建子类对象,重写父类方法一步完成
把实现类实现接口,创建实现类,重写接口方法一步完成
没有子类/接口的名字
格式:
new 父类/接口(){重写方法}

 public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"ycj");
                }
            }
        }.start();//开启多线程

        Runnable r=new Runnable(){//定义接口的实现类对象   多态
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"严昌敬");
                }
            }
        };
        new Thread(r).start();

        //Runnable的简单写法,不用创建Runnable对象接收
        new Thread(new Runnable(){//定义接口的实现类对象   多态
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+"三爷");
                }
            }
        }).start();

方法

获取线程名称:

 public class Getname extends Thread {
    public Getname() {
    }
    @Override
    public void run() {
        //获取线程的名称
        /*
        方法一:直接获取线程名称
        String name = getName();
        System.out.println(name);
        */
        /*
        方法二 先获取当前线程,在通过线程获取线程名称
        Thread T=Thread.currentThread();//获取当前线程,静态方法
        System.out.println(T);
        String name= T.getName();
        System.out.println(name);
        */
        //方法二写成一句话
        System.out.println(Thread.currentThread().getName());
    }
}

让线程休眠

public static void main(String[] args) {
        for (int i = 0; i < 60; i++) {
            try{
            Thread.sleep(1000);}//使进程休眠1000毫秒,需要抛出异常
            catch (Exception e) {
            }
            System.out.println(i);
        }
    }

线程安全问题

若多个线程用一个共享数据,则在争夺cpu时可能产生重复或者不存在的情况
解决线程安全问题的方法

方法1.在继承的子类或者是实现类里
格式
sychronized(锁对象){
可能出现线程安全问题的代码
}
注意:
1.锁对象可以是任意的对象
2.锁对象不能重复
3.锁对象的作用
把同步代码块锁住,只让一个线程在同步代码块中执行

private int tickets=100;
//锁对象定义在外面,防止调用run方法时产生多个重复的锁对象
//任意对象
Object o=new Object();
@Override
public void run() {
  while (true) {
      synchronized (o) {
          if (tickets > 0) {
              try {
                  Thread.sleep(10);
              }
              catch (Exception e) {

              }
              System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
              tickets--;
          }
      }
  }

方法2
创建一个方法用sychronized修饰,方法体是可能出现多线程安全问题的代码(从开始调用共享数据开始)

public class RunnableImp2 implements Runnable{
    private int tickets=100;
    @Override
    public void run() {
        while(true)
            payticket();
    }
    public  synchronized void payticket(){
        if (tickets>0) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
            System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
            tickets--;
        }
    }
}

方法三

java.util.concurrent.Locks.Lock接口
用lock锁处理线程安全问题的方法
lock实现提供了比sychronized方法更广泛的操作
1.在成员位置创建一个ReentrantLock()
1.在可能出现线程安全问题的代码前用调用.lock()方法
2.在可能出现线程安全问题的代码结束后用.unlock()解锁
例:

public class Demo01Lock extends Thread {
   private int tickets=100;
   Lock l=new ReentrantLock();//1.创建ReentrantLock对象 (多态)

   @Override
   public void run() {
      /*
   更好的方法
   */
       while(true) {
           l.lock();//2.在可能出现线程安全问题的代码前获取锁
           if (tickets > 0) {
               try {
                   Thread.sleep(10);
                   System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets + "张票");
                   tickets--;
               } catch (Exception e) {
                   e.printStackTrace();
               }
               finally {
                   //这样可以保证无论是否有异常的出现都会释放锁,增加效率
                   l.unlock();//3.释放锁
               }
           }
           if(tickets==0)
               break;
       }
   }

用lambda和匿名内部类重写父类/接口方法

格式:new 父类名/接口名(){
重写方法
}

用lambda重写父类/接口的方法比匿名内部类更简单
格式:
new 父类/接口 对象=(父类/接口)(参数列表)->{重写的方法}

在方法中重写就可以省略构造父类/接口对象的过程
直接()->{} (在方法中用lambda重写会隐含的构造父类/接口,且自己匹配需要重写的方法)

其实严格意义上不是构造父类和接口的对象,抽象类不能直接构造

lambda优化

参数列表:
若参数只有一个,则参数和其类型都可以省略不写(要么一起写要么一起不写)
若有多个参数,则只能省略其参数类型
方法体(且方法体中代码只能有一句):
若有return; 则{}和return和;都可以不写(要么一起写要么一起不写)
若无return则{}和;可以不写(要么一起写要么一起不写)

注意

只有当父类/接口方法只有一个时才能用lambda重写,否则无法自动匹配

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值