Java学习笔记——多线程

1、线程的创建

​ 有三种创建方式:

  • 继承Thread类

    继承之后可重写 run() 方法实现线程体,之后可通过线程对象调用 start() 方法开启线程

    public class Myjava extends Thread{
        @Override
        public void run() {		//重写run()方法
            System.out.println("hello Java");
            super.run();
        }
        
        public static void main(String[] args) throws Exception{
            Myjava myjava = new Myjava();
            myjava.start();		//开启线程
    
        }
    
    }
    
  • 实现Runnable接口

    重点是实现 run() 方法实现线程体,之后可以用本类对象为参数创建线程对象并调用 start() 方法开启线程。因为Java只支持单继承,因此推荐使用此方法。

    public class Myjava implements Runnable{
        @Override
        public void run() {		//实现run()方法
            System.out.println("hello java");
        }
        
        public static void main(String[] args) throws Exception{
            Myjava myjava = new Myjava();
            new Thread(myjava).start();	//开启线程
    
        }
    }
    
  • 实现Callable接口

    1、实现Callable接口 (需要指定泛型类型)

    2、重写call方法(需要抛出异常)

    3、创建目标对象与执行服务

    4、进行提交并执行

    5、获取结果后关闭服务

    public class Myjava implements Callable<String> {
    
        @Override
        public String call() throws Exception {
            return "hello java";
        }
    
        public static void main(String[] args) throws Exception{
            Myjava myjava = new Myjava();
            //创建执行服务
            ExecutorService executorService = Executors.newFixedThreadPool(2);
            //提交执行
            Future<String> submit = executorService.submit(myjava);
            //获取结果并结束服务
            String result = submit.get();
            executorService.shutdownNow();
        }
    }
    

2、静态代理模式与Lambda表达式

2.1 静态代理模式

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作。静态代理使用时,需定义一个接口或父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。

我们可以通过一个实际的例子来理解一下:我们电视上看的各种节目是由节目制作组提供给电视台播放的,电视台为了盈利还会在播放这些节目时播放各种广告,其中电视台就是代理对象,节目组是被代理对象。可以用代码模拟此过程:

interface Program{	//首先需要一个通用的接口
    void Play();
}

class ProgramA implements Program{	//被代理方
    @Override
    public void Play() {
        System.out.println("现在正在播放节目A");
    }

}

public class TVstation implements Program{	//TVstation就是代理对象
    ProgramA a;
    public TVstation(ProgramA a) {
        this.a = a;
    }

    @Override
    public void Play() {
        System.out.println("现在是广告时间");
        a.Play();
    }
}
2.2 Lambda表达式

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。

Lambda表达式有几个特征:

  • 可选类型声明:编译器可以识别参数类型
  • 可选的参数圆括号:单个参数不需要但多个需要
  • 可选大括号:只有一个语句则不需要大括号
  • 可选return:主体只有一个表达式则编译器自动返回表达式的值,但当使用大括号时需要显式返回

代码示例:

interface Lambda1{
    void lambda();
}

interface Lambda2{
    int lambda(int a,int b);
}

public class Myjava{
    public static void main(String[] args) throws Exception{
       Lambda1 l1=()-> System.out.println("hello");
       l1.lambda();
       Lambda1 l2=()->{
           System.out.println("hello");
           System.out.println("Lambda");
       };
       Lambda2 l3=(a,b)->a+b;
       l3.lambda(1,2);
    }
}

3、线程方法

3.1 线程状态

共有五大状态,如下图所示:

在这里插入图片描述

当Thread对象一经创建就会进入到新生状态,当调用start()方法时立马进入就绪状态但未必会立即调度执行,而当调用sleep()、wait()或者同步锁定时线程就会进入到阻塞状态,代码将不会往下执行当阻塞状态解除后将进入就绪状态等待CPU调度执行。

3.2 线程停止

可以使用JDK提供的stop()或者destroy()方法但不推荐,因为已经被废弃。可以设定一个标志位来让线程自己停下来。示例:

 private boolean flag=true;
    @Override
    public void run() {
        while(flag)
            System.out.println("hello");
    }
    public void Mystop(){
        flag=false;
    }
3.3 线程休眠

通过调用sleep(long)方法可让线程休眠,需要给函数传递一个以毫秒为单位的时间值,在给指定的时间到达后线程将进入就绪状态,此方法是Thread类的一个静态方法。使用此方法可以模拟网络的延时或者倒计时等功能。每个对象都有一把锁,而sleep方法不会释放锁

使用示例:

Thread.sleep(1000);
3.4 线程礼让与强制执行

调用定义在Thread类中的静态方法yield()可以进行礼让,让线程从运行状态转为就绪状态并让CPU重新进行调度。礼让不一定能成功因为CPU重新调度后可能仍然能够执行。

调用join()方法可以理解为插队,待此线程执行完成后再执行其他线程,其他线程会进入到阻塞状态。

两者使用示例:

Thread.yield();

Thread t=xxxx;
t.join();
3.5 线程状态的观测

线程可以处在一下几个状态之一:

  • NEW:尚未启动的线程
  • RUNNABLE:执行中的线程
  • BLOCKED:被阻塞等待监视器锁定的线程
  • WAITING:等待另一线程执行特定动作的线程
  • TIMED_WAITING:等待另一线程执行动作达到指定时间的线程
  • TERMINATED:已退出的线程

通过调用Thread类的对象的getState()方法可以得到线程的状态,使用示例:

Thread t=xxxx;
Thread.State state=t.getState();
System.out.println(state);

4、线程的优先级与守护线程

4.1 线程的优先级

Java提供一个线程调度器来监控进入就绪状态的所有线程,高优先级线程相比于低优先级线程被分配CPU的概率更大,但不是一定先于低优先级线程执行,调度是随机无规律的因此不同线程之间不能有先后依赖关系。线程优先级是范围1-10的数字,默认优先级为5,可以通过getPriority()和setPriority()方法来获得与设置线程优先级。

4.2 守护线程

线程分为用户线程守护线程,虚拟机必须保证用户线程执行完毕但不用保证守护线程。通过给Thread对象的setDaemon方法传递参数true(默认为false)可以设定线程为守护线程。


5、线程同步

5.1 线程不安全案例

考虑以下代码的执行:

class BuyTicket implements Runnable{
    private int tickets=100;
    boolean flag=true;
    @Override
    public void run() {
        while(flag)
            buy();
    }
    private void buy(){
        if(tickets<=0){
            flag=false;
            return ;
        }
        System.out.println(Thread.currentThread().getName()+"买到第"+tickets--+"张票");
    }
}

public class Myjava {

    public static void main(String[] args) throws Exception{
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"A").start();
        new Thread(buyTicket,"B").start();
        new Thread(buyTicket,"C").start();
    }

}

执行结果中出现了不同线程买了同一张票,这是因为未加锁,对同一对象不同线程访问到了“同一张票”
在这里插入图片描述

5.2 线程同步机制

线程同步其实就是一种等待机制,多个访问同一对象的线程形成一个队列待前面的处理完再执行。为保证数据在方法中能被正确访问,在访问时加入锁机制synchronized,当一个线程获得锁时将独占资源,其他线程必须等待锁释放。也存在一些问题:多线程竞争下加锁和释放锁会影响性能;优先级高的线程等待优先级低的线程释放锁也会引起性能问题。

5.3 同步方法与同步块
  • 同步方法

    使用synchronized关键字,同步方法不需要指定锁,默认是this或者class对象,示例:

    public synchronized void method(){}
    

    缺陷:方法里不是所有内容都需要加锁,将一个大的方法声明为synchronized方法可能影响效率

  • 同步块

    使用格式:synchronized(Obj){ } 。Obj可以为任何对象但应使用会被修改数据的对象

5.4 死锁

死锁:多个线程各自占有一些共享资源并互相等待其他线程释放锁,而导致两个或多个线程都在等待对方释放锁因而停止执行的情形。当某一同步块拥有两个以上对象的锁时就可能出现死锁问题。例如以下的情形:

	if(xxx){
        synchronized (A){//获得A的锁
            Thread.sleep(1000);
            synchronized (B){//修眠1s后想获得B的锁

            }
        }
    }else{
        synchronized (B){//获得B的锁
            Thread.sleep(2000);
            synchronized (A){//休眠2S后向=想获得A的锁

            }
        }
    }
5.5 Lock锁

JDK5.0开始可以通过显式定义同步锁对象来实现同步;ReentrantLock类实现了Lock,它与syncronized有相同的并发性质,并可以显式的加锁与释放锁。

使用示例:

class mylock implements Runnable{
    private int tickets=100;
    private boolean flag=true;
    private ReentrantLock Lock=new ReentrantLock(); //定义lock锁

    @Override
    public void run() {
        while(flag)
            buy();
    }
    private void buy(){
        Lock.lock();    //显式加锁
        if(tickets<=0){
            flag=false;
            Lock.unlock();  //显式释放锁
            return ;
        }
        System.out.println(Thread.currentThread().getName()+"买到第"+tickets--+"张票");
        Lock.unlock();
    }
}

使用Lock锁后输出没有问题。

5.6 线程池

线程池思想:提前创建好多个线程放入线程池中,需要使用时直接获取,使用完后再放回池中以避免频繁的创建与销毁。可以提高响应速度降低资源消耗。

使用示例:

class Mypool implements Runnable{
    @Override
    public void run() {
    }
}

public class Myjava {

    public static void main(String[] args) throws Exception{
        //创建线程池
        ExecutorService service= Executors.newFixedThreadPool(3);
        //使用线程池中的线程执行线程体
        service.execute(new Mypool());、
        //关闭链接
        service.shutdown();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值