1-21 线程1

Thread线程

PS:现在没有单片机【单核CPU】,接下来在你电脑看到所有结果都是对

为什么要有线程

PS:Java程序的组成【顺序,分支,循环】
需求:在这里设计一个程序既可以打游戏又可以听音乐?

请添加图片描述

对于在的需求,解决方案有两种

1.以进程方式实现打游戏和听音乐

2.以线程方式实现打游戏和听音乐

什么是进程和什么是线程

进程:是指一个内存中运行的**【应用程序】,每个进程都有字节的独立的一块内存区域,一应用程序可以同时【启动多个线程】**

PS:一个进程中可以包含多个线程

【例如: 百度网盘有下载任任务【百度网盘是是进程,而它的下载是线程(多线程)】】

如果使用进程解决打游戏和听音乐是可以?

完全可以可以,可以开发一个进程听歌,也可以开发一个进程玩游戏,进程和进程之间的通信不是很方便,并且若是在某个进程内部完功能,就不建议以进程的形式开发【因为启动一个功能就相当于开启进程,就相当于执行了以应用程序,并且彼此之间是独立】

此时需要在进程的内部同时完成这个功能,此时就可以选用**【线程】**

线程:是指**【进程】中的一个【执行任务单元(控制单元)】,一个进程可以同时【并发运行多个线程】**

例如:【IDEA Java编译器在这个编译器中(进程),存在多个线程帮助IDEA进行进程管理

(例如:改错,报错)】

一个进程至少有一个线程,为了提高效率,可以在一个进程中启动多个线程即**【多线程】**

进程和线程的区别

进程:是有自己独立内存空间的,进程中的数据存放空间(堆栈区域)是独立的,至少有一个线程

线程:堆空间是共享的,栈空间是独立的,线程消耗资源比进程要小,相互之间可以互相影响**【线程通信、同步和异步线程】,所以称线程为【轻型的进程或进程元】**

PS:开发线程成本要远远低于开发进程,因为一个进程中有多个线程并发执行,那么从微观的角度而言是可以解析出(考虑出)先后执行顺序,那么那个线程先执行,那个线程后执行是可以思考出来,但是在实际的执行中是不可以预测【线程执行完全取决于CPU的调用,CPU会分发时间片和回收时间片】,程序源可以进行干预,可以限制或修改或控制线程获取CPU时间片,以达到对线程的控制

多线程并发访问的时候可以看做时瞬间抢夺CPU时间片,谁抢到谁先执行,这就造成了线程的随机性

并发和并行

请添加图片描述

PS:并发和并行是即相似又有区别(微观的角度)

并行:指两个或多个事件在**【同一个时刻点】**发生

并发:指两个或多个事件在**【同一个时间段内】**发生

在操作系统中,在多道程序环境下,并发性是指在一段时间内宏观上有**【多个程序在同时运行】**,但在单CPU系统中,每一时刻却仅能有一道程序执行(时间片),故微观上这些程序只能是【分时地交替执行】。

**倘若计算机系统中有多个CPU,则这些可以并发执行的程序便可被分配到多个处理器上,实现多任务并行执行,**即利用每个处理器来处理一个可并发执行的程序,这样,多个程序便可以同时执行,因为是微观的,所以大家在使用电脑的时候感觉就是多个程序是同时执行的。

所以,大家买电脑的时候喜欢买“核”多的,其原因就是“多核处理器”电脑可以同时并行地处理多个程序,从而提高了电脑的运行效率。

单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。

同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个进程的运行,当系统只有一个CPU时,进程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

线程的执行都需要时间片即CPU分配给各个程序的运行时间.

多线程优势:

多线程作为一种多任务、并发的工作方式,当然有其存在优势:

① 进程之前不能共享内存,而线程之间共享内存(堆内存)则很简单。

② 系统创建进程时需要为该进程重新分配系统资源,创建线程则代价小很多,因此实现多任务并发时,多线程效率更高.

③ Java语言本身内置多线程功能的支持,而不是单纯第作为底层系统的调度方式,从而简化了多线程编程.

Java中如何实现线程

创建线程一共三种方式:先学习前两种

1.继承Thread类: 此时的类就是一个线程类,这个类必须实现run方法,run方法就是线程执行的核心

2.实现Runnable接口: 此时这个类就不是一个线程类,它只是一个是实现了一个接口的类,这个类也必须实现接口中的run方法,run方法就是线程执行的核心

    • 返回值类型此方法是实现线程的核心操作
      voidrun() 当一个对象实现的接口 Runnable是用来创建一个线程,启动线程使对象的 run方法在单独执行的线程调用。

方式一:继承Thread类

1.先创建一个类然后继承Thread2.在子类中必须重写run方法【run方法的实现体就是当前线程主体】
3.在测试类的main方法中,创建线程并启动
    
创建线程对象的两种方式:
    1. 继承Thread的子类  线程对象名 = new 继承Thread();
    PS: 因为是继承Thread类所以,它是子类会有父类中方法,所以这个类也被成线程类
    
    2. Thread  线程对象名  = new 继承Thread();
    PS: 对象的向上转型,还是一个完整线程类
        
切记,切记,切记!!启动线程一定是 调用start方法 ,而不是调用run方法
        线程对象.start();
package com.qfedu.Thread;
//音乐的线程类   MusicThread就是一个线程类
public class GameThread extends Thread {
    //必须重写父类Thread的run方法,run方法是线程的核心
    @Override
    public void run() {
        //run的方法方法体就是线程执行的核心【你要干什么】
        //启动做什么操作
        for(int i= 0;i<50;i++){

            System.out.println("打游戏:"+i);
        }

    }

}
//音乐的线程类   MusicThread就是一个线程类
public class MusicThread extends Thread {
    //必须重写父类Thread的run方法,run方法是线程的核心
    @Override
    public void run() {
        //run的方法方法体就是线程执行的核心【你要干什么】
        //启动做什么操作
        for(int i= 0;i<50;i++){
            System.out.println("播放音乐:"+i);
        }

    }

}
//测试类
public class ThreadTest {
    public static void main(String[] args) {
        //Java中存在两个线程【这两个线程是必须有的】
        //一个是main入口方法即主线程【主线程的优先级高于一切子线程(自己创建)】
        //另外一个线程是GC垃圾回收机制【开启了一个守护线程,这个线程守护当前执行代码当代码执行完毕之后,进行空间回收】/
         //在main方法中启动线程
       MusicThread thread1 = new MusicThread();//线程对象就创建好了,这是通过继承Thread类完成操作
        Thread thread2 = new GameThread();//创建好了线程对象,此时是完成了向上转型,所以可以进行线程操作
       //线程的执行结果是不可预测【但是可以大范围猜测到部分执行结果】,但是可以强加干预,以达到预期效果
       thread1.start();
        thread2.start();

    }
}



    

方式二:实现Runnable接口

PS:实现Runnable接口并不是一个线程类,这个类只是实现了Runnable接口,需要借助Thread类,来完成线程的创建
    
1. 创建一个普通类实现Runnable接口
2. 在普通类中必须实现run方法
3. 借助Thread类创建线程对象,以启动线程    
    
切记,切记,切记!!启动线程一定是 调用start方法 ,而不是调用run方法
        线程对象.start();    
    • Thread(Runnable接口实现类的对象) 分配一个新的 Thread对象。
      Thread(Runnable接口实现类的对象, String 当前线程的名字) 分配一个新的 Thread对象。
创建对象:
    Thread  线程对象 = new Thread(Runnable接口实现类的对象);Thread  线程对象 = new Thread(Runnable接口实现类的对象,当前线程的名字);    

代码实现

package com.qfedu.Runnable;
//此类是实现Runnable接口
public class GameThread implements Runnable {
    @Override
    public void run() {
        //run的方法方法体就是线程执行的核心【你要干什么】
        //启动做什么操作
        for(int i= 0;i<50;i++){
            System.out.println("打游戏:"+i);
        }
    }
}

//此类是实现Runnable接口
public class MusicThread implements Runnable {
    @Override
    public void run() {
        //run的方法方法体就是线程执行的核心【你要干什么】
        //启动做什么操作
        for(int i= 0;i<50;i++){
            System.out.println("播放音乐:"+i);
        }
    }
}

package com.qfedu.Runnable;
//测试类操作
public class RunnableTest {
    public static void main(String[] args) {
       //创建实现Runnable接口类的线程对象
        Thread thread1 = new Thread(new GameThread());
        //创建线程对象的同时,指定线程的名字
        Thread thread2 = new Thread(new MusicThread(),"听音乐");

        thread1.start();
        thread2.start();
    }
}

Thread类和Runnable接口的匿名实现类方式(了解)

PS:当前线程仅限在当前类中的时候用,这种情况下就可以使用匿名内部类的形式完成

package com.qfedu.AnonymityThread;
//这些操作方式仅限 线程使用一次,若线程多次使用【必须创建类的实现】
public class AnonymityThreadDemo {
    public static void main(String[] args) {
        //此方式仅限使用一次【匿名内部类】
        new Thread(){
            @Override
            public void run() {
                //run的方法方法体就是线程执行的核心【你要干什么】
                //启动做什么操作
                for(int i= 0;i<50;i++){
                    System.out.println("听音乐:"+i);
                }
            }
        }.start();

        //实现Runnable接口【匿名内部类】
        new Thread(new Runnable() {
            @Override
            public void run() {
                //run的方法方法体就是线程执行的核心【你要干什么】
                //启动做什么操作
                for(int i= 0;i<50;i++){
                    System.out.println("打游戏:"+i);
                }
            }
        }).start();

        //lambda表达式的Runnable接口实现
        new Thread(()->{
            //run的方法方法体就是线程执行的核心【你要干什么】
            //启动做什么操作
            for(int i= 0;i<50;i++){
                System.out.println("打酱油:"+i);
            }
        }).start();
    }
}


start方法和run方法的区别

1) start:
  用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
2) run:
  run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。

Thread类创建线程和Runnable创建线程的区别

两者都要实现run方法,使用Thread那么需要继承,并重写父类中的run方法**【当前类是线程类】,实现Runnable接口,就需要实现接口的run方法【不是线程类是普通实现类】**,从扩展角度而言,最好使用Runnable接口,若能确定只是一个线程的调用那么我们使用Thread,若是需要继承一个父类在调用线程并进行扩展推荐使用Runnable。

实际项目中推荐大家使用Runnable接口

class A extends Thread{}

class A extends B implement Runnable,XXXInteface{} --》 这种样式使用最多

线程中常用方法

线程状态

PS:在API中标注错误,State是一个枚举并不是一个类,但是不影响使用

 public enum State {
        /**
         * 出生,新生
         * 表示线程尚未启动正在等待创建
         */
        NEW,
        /**
         * 准备就绪和执行
           当前线程正在准备就绪状态等待执行
         */
        RUNNABLE,

        /**
         * 休眠
         线程【阻塞状态】,此时当前编程会让出CPU时间片,等待继续执行【sleep】
         */
        BLOCKED,

        /**
         等待
         无限期的等待,等待另外一个线程执行操作【wait】
         */
        WAITING,

        /**
         时间等待
         无限期等待,等待另外一个线程执行操作【这个等待是有时限限制的,如果在规定时间内没有唤醒,到时间后也会自动醒来】 【wait和sleep】
         */
        TIMED_WAITING,

        /**
 			消亡,死亡
 			线程执行完毕之后会出现这个状态【这个状态很少能观测到】
         */
        TERMINATED;
    }

代码

package com.qfedu.State;
//getState()方法可以获取到当前线程的状态
public class StateDemo extends Thread{
    @Override
    public void run() {
        System.out.println("进入run方法当前线程的状态:"+this.getState());
        int sum = 0;
        for(int i = 0;i<=100;i++){
            sum += i;
        }
        System.out.println("sum = "+sum);
        System.out.println("结束run方法当前线程的状态:"+this.getState());
    }

    public static void main(String[] args) {
        //1. 创建线程对象
        Thread thread = new StateDemo();
        System.out.println("启动线程前的状态:"+thread.getState());
        thread.start();
        System.out.println("start方法之后线程的状态:"+thread.getState());
        System.out.println("我是一个无关紧要的执行---------");
        System.out.println(thread.getState());

    }
}


设置线程优先级

PS:优先级有机率提高线程获取CPU时间片的概率,理论上优先接越高,那么获取CPU时间片的机率越大,反之越小【高优先级的会享有优先执行权】

优先级相当于是给CPU一共暗示【先由我这个线程开始】,但是实际的决定权还是在争抢CPU时间片

所有创建好的线程由一个默认优先级**【级别统一都是5】**

线程优先级的范围 ,从1开始到10结束【前后都包含】,优先级为1等级最低 ,优先级为10等级最高

    • 数据类型int系统设置好是三个静态常量
      static intMAX_PRIORITY 线程可以拥有的最大优先级。 【10】
      static intMIN_PRIORITY 线程可以拥有的最小优先级。 【1】
      static intNORM_PRIORITY 被分配给线程的默认优先级。 【5】

修改优先接可以使用提供方法 setPriority(参数是一个int类型范围是从1开始到10结束)

package com.qfedu.Priority;
//修改线程优先级
public class SetPriorityDemo  extends  Thread{
    @Override
    public void run() {
        for (int  i = 0;i<100;i++){
            //getName() 可以获取当前线程的名字
            //线程名字的组成 是 Thread-数字 ,从0开始根据创建线程的多少进行递增
            System.out.println("线程:"+getName()+"---------"+i);
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new SetPriorityDemo();
        Thread thread2 = new SetPriorityDemo();
        //先不设置线程优先级getPriority()获取线程优先级 默认优先级都是5
//        System.out.println(thread1.getPriority());
//        System.out.println(thread2.getPriority());
        //修改线程优先级  优先级的差值越大效果相对比较明显,优先级的差值越小效果越不明显
        thread1.setPriority(Thread.MAX_PRIORITY); //和直接传递10效果是一样
        thread2.setPriority(Thread.MIN_PRIORITY);//和直接传递1效果一样
        thread1.start();
        thread2.start();

    }
}

更改线程的名字

线程是有默认名字【Thread-数字】,数字是从0开始,随着线程增多递增,随着线程减少而递减

为了更加有效的区分线程,提供了三种秀海线程名字的方式

1.使用线程对象.setName("线程名字")
2.实现Runnable接口,在创建线程对象的时候指定线程的名字 new Thread(Runnable接口实现类对象,"线程名字") 3.继承Thread类,提供一个参数的构造方法,这个参数是线程名字,在对象时调用这个构造方法进行对名字初始化

PS:获取名字,使用getName()方法    
    package com.qfedu.SetName;

public class SetNameDemo {
    public static void main(String[] args) {
        new ThreadA("我叫ThreadA").start();
        //通过外部设置线程名字
        ThreadB threadB = new ThreadB();
        threadB.setName("我叫ThreadB");
        threadB.start();
        new Thread(new ThreadC(),"我叫ThreadC").start();

    }
}
class ThreadA extends  Thread{
    //继承Thread类,提供有参构造方法添加线程名字
    public ThreadA(String name){
        super(name);
    }
    @Override
    public void run() {
        System.out.println("线程名:"+getName());
    }
}
class ThreadB extends  Thread{

    @Override
    public void run() {
        System.out.println("线程名:"+getName());
    }
}

class ThreadC implements Runnable{
   // 实现Runnable接口的类不是线程类,只是实现Runnable接口中run方法以便提供给Thread使用
    @Override
    public void run() {
        //在Runnable接口中获取当前线程的名字,需要使用到一个静态方法这个方法可以获取当前线程实例【对象】
        //currentThread() 在什么位置调用就代表什么哪个线程对象
        System.out.println("线程名:"+Thread.currentThread().getName());
       
    }
}

线程休眠

线程休眠:让执行的线程暂停一段时间,进入计时等待状态

    • static voidsleep(long millis) 当前正在执行的线程休眠(暂停执行)为指定的毫秒数。
      毫秒和秒之间的换算 1000毫秒 == 1秒 这个方法存在一个编译时异常InterruptedException【中断异常】这个异常时编译时异常,在写代码的时候就需要处理

      sleep方法的执行过程

      当线程中调用sleep方法之后,当前线程会放弃CPU(时间片),在指定时间段内,sleep所在的线程就不会获取到执行的机会

      特别注意:【在同步锁(对象锁/同步监听器)中sleep不会释放CPU时间片】

      需求:实现一个交替打印输出的效果,做一个线程,线程的执行逻辑是当%5整除的时候进行休眠,在主线程中也执行这个效果,

package com.qfedu.ThreadMethod;
//sleep方法
public class SleepDemo extends  Thread{
    @Override
    public void run() {
        for (int i = 1;i<=100;i++){
            if(i%5==0){
                System.out.println("当前线程"+getName()+"~~~~~~~~"+i);
                //线程中异常不建议抛出,建议内部处理,快速生成try-catch等 代码块ctrl+atl+t
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new SleepDemo();
        thread.setName("子线程");
        thread.start();
        for (int i = 1;i<=100;i++){
            if(i%5==0){
                //主线程休眠
                System.out.println("当前线程名字:"+Thread.currentThread().getName()+"~~~~~~~~"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

PS:现阶段sleep方法无法做到线程的交替执行,线程中有sleep方法会释放cpu时间片【线程休眠】,但是线程醒来之后会继续争抢CPU时间片
    特别注意:【同步锁(对象锁/同步监听器)】这些范围内sleep方法是不会释放cpu时间片

线程礼让

PS:这个方法的效果不是特别明显

yield方法:若在线程体中,调用该方法,但不线程会给CPU发出同一个暗示【不急着运行】,可以将本线程占有的CPU时间片进行回收,分配给线程使用

PS:至于是否CPU立即回收时间片,取决于CPU,CPU即可以立即回收也可以不回收【CPU忽略提示】

需要注意:调用该方法之后,线程对象会进入到就绪状态【等待分配CPU时间片】,所以这里完全可能出现,某个线程调用了yield方法之后线程调用器又把它重新执行起来。

package com.qfedu.ThreadMethod;
//线程礼让
class EThread extends  Thread{
    @Override
    public void run() {
        for(int i = 1 ;i<=10;i++){
            System.out.println("线程的名字:"+getName()+"~~~~~~~"+i);
            if (i%2 == 0){
                Thread.yield();
            }
        }
    }
}
public class YieldDemo {
    public static void main(String[] args) {
        Thread a = new EThread();
        a.setName("a线程");
        Thread b = new EThread();
        b.setName("b线程");

        a.start();
        b.start();
    }
}



sleep方法和yield方法的区别

1.都能使当前线程处于放弃CPU时间片,把运行机会给其他线程

2.sleep方法会给其他线程运行机会,但是不考虑【其他线程优先级】yield方法只会给相同优先级或优先级更高的线程运行机会

3.调用sleep方法后,线程进入的计时等待状态【当前状态完毕之后是准备就绪状态】,调用yield方法后,线程进入的准备就绪状态

线程合并

join(合并):将一个正在处于运行状态的线程,强制进入得到等待状态【让出CPU时间】,让加入到这个线程执行的另外一个线程执行,原有线程只能等待加入线程执行完毕之后才可以继续执行

package com.qfedu.Join;

public class MeiZi extends Thread {
    @Override
    public void run() {
        for(int  i = 1 ;i<=50;i++){
            System.out.println("妹子在看爬山"+"第【"+i+"】集");
            if(i == 10){
                //添加其他线程执行
                //join方法原则必须是在start方法之后,需要处理异常【编译时异常】
                HaiZi hz = new HaiZi();
                hz.start();
                try {
                    hz.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


public class HaiZi extends  Thread {
    @Override
    public void run() {
        for(int i  = 1;i<=50;i++){
            System.out.println("汉子在看被爬山"+i+"集");
        }
    }
}


public class JoinTest {
    public static void main(String[] args) {
         MeiZi mz = new MeiZi();
         mz.start();
    }
}


特点:

1.线程合并,当前线程一定会释放CPU时间片,CPU会将时间片分配给要Join进来线程【谁调用Join,谁就那个线程】

2.在加入线程没有执行完毕之前,原始线程是不会执行的

3.join执行之前,要加入的线程是处于准备就绪状态【start】

守护线程【后台线程】

后台线程又称为【守护线程】,JVM中垃圾回收机制是一个典型后台线程

特点;

若所有的前台线程都死亡,后台线程会自动死亡。若前台线程没有结束,后台线程就不会结束。

如果设置后台线程,线程对象.setDaemon(true) 【参数是一个boolean值,这个值true就是守护线程 false就不是】

检查档案线程是都是守护线程 线程对象.isDaemon() 返回值是boolean true就是 false 就不是

PS:线程开启来访问,需要连接一些物理【磁盘文件等】或网络资源【tcp,网络通信】,释放他们的资源

package com.qfedu.SetDeamon;

public class SetDeamonDemo  extends  Thread {
    @Override
    public void run() {
        for(int i = 0;i<=50;i++){
            System.out.println("当前线程的名字:"+getName()+"-----"+i);
            //为了能看到效果让其sleep一会
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new SetDeamonDemo();
        thread1.setName("守护线程");
        thread1.setDaemon(true);
        thread1.start();
        //当前守护的是主线程
        for(int  i = 0;i<10;i++){
            System.out.println("主线程~~~~~"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


中断线程【线程中断】

PS:这个方法是Java用来替代stop方法,用来停止线程使用

吐槽:这个方法真感觉“呵呵”,这个方法本身不具备任何停止能力,这个人方法只是一个【中断标记】,可以在线程中检查这个【中断标记】,如果让线程检查到了就让线程停止【这个停止其实就是(return或break)】

stop方法是真心好用,但是需要注意如果使用一定要承担风险【莫名其妙崩溃,会影响其他线程停止】

中断线程需要两个方法合作:

interrupt 中断标记,向运行中的线程添加一个【标记】

interrupted 中断标记检测,检测线程中是否有中断标记,如果有可以进行进一步处理,如果没有这放弃【true和false】

失败案例:

package com.qfedu.SetDeamon;

public class InterruptDemo extends Thread {
    @Override
    public void run() {
        for(int i = 1;i<=10;i++){
            System.out.println("子线程中i的值是:"+i);
            if(i == 5){
                interrupt();//在API中的翻译是中断线程
               // stop();
            }
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new InterruptDemo();
        thread1.start();
    }
}


正确案例:

package com.qfedu.SetDeamon;

public class InterruptDemo2 extends Thread {
    @Override
    public void run() {
        for(int i = 1;i<=10;i++){
            System.out.println("子线程中i的值是:"+i);
            if(i == 5){
              if(Thread.interrupted()){//判断中断标是否存在
                  System.out.println("线程已经中断");
                  return;
              }
            }
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new InterruptDemo2();
        thread1.start();
        thread1.interrupt();//启动线程后开启了中断标记
    }
}


中断睡眠

package com.qfedu.SetDeamon;
//中断睡眠
public class InterruptSleepDemo extends Thread {
    @Override
    public void run() {
        System.out.println("进入线程");
        try {
            Thread.sleep(2000);
            System.out.println("线程完成自然睡眠醒来");
        } catch (InterruptedException e) {
            System.out.println("线程被终止睡眠");
        }
    }

    public static void main(String[] args) {
       InterruptSleepDemo isd = new InterruptSleepDemo();
       isd.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isd.interrupt();
    }
}


同步代码块【同步锁】

需求: 线程进行火车票售卖,一共有且仅有100张,4个窗口同时售卖

​ PS:.4个窗口相当于是4个线程对象,一共买100张票,这个票不允许出现负数,不允许为0

多线程并发访问同一个资源的时候,建议将这个资源的设置为static修饰【静态所有对象共享】

package com.qfedu.Synchronized;
//保证100张票
public class SellTicket1 extends Thread{
    //将当前多线程并发访问变量设置为静态,就可以保证全局唯一【多有对象共享静态变量】
    private static int  tickets = 100;

    @Override
    public void run() {
        //执行买票的逻辑
        for(int i = 0;i<100;i++){ //执行的卖票的次数
            if(tickets > 0){//证明票是够的,买票
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("当前售票员:【"+Thread.currentThread().getName()+"】"+"第【"+(tickets--)+"】票");
            }

        }
    }

    public static void main(String[] args) {
        //这里将当票写成员变量,成员变量的特点,就是每个对象都维护自己的成员
        //只想卖出100张票【需要使用的同一个对象】
//        SellTicket1 st1 = new SellTicket1();
//        Thread t1 = new Thread(st1,"刘德华");
//        Thread t2 = new Thread(st1,"张学友");
//        Thread t3 = new Thread(st1,"郭富城");
//        Thread t4 = new Thread(st1,"吴奇隆");
        //400张票 将票定义成成员变量
//        Thread t1 = new SellTicket1();
//        Thread t2 = new SellTicket1();
//        Thread t3 = new SellTicket1();
//        Thread t4 = new SellTicket1();
//        t1.setName("刘德华");
//        t2.setName("张学友");
//        t3.setName("郭富城");
//        t4.setName("吴奇隆");
        Thread t1 = new Thread(new SellTicket1(),"刘德华");
        Thread t2 = new Thread(new SellTicket1(),"张学友");
        Thread t3 = new Thread(new SellTicket1(),"郭富城");
        Thread t4 = new Thread(new SellTicket1(),"吴奇隆");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}


问题:

1.所有对象共享一个票资源,无论是Thread还是当前Runnable都能保证是同一个,建议将当前资源修改为static,保证所有对象共享其一

2当前线程执行过快,为了解决这个在当前run方法中,添加了Sleep方法,但是添加完方法后,出现重票,出现0,但是可能还会负票

在多线程并发访问同一个临界资源【票】,如果能保证临界资源安全【即正确计算】

Java中提供了一个策略就是对操作临界资源位置,添加【锁对象】,保证当前只能有一个线程操作临界资源,这样就可以保证临界资源安全

同步代码块和同步方法

PS:同步代码块也可以叫做【同步锁,对象锁,同步代码锁】

同步代码块:
保证在多线程并发访问的前提下,只有一个线程可以操作临界资源,这个线程不执行完毕,其他线程无法执行

PS:在同一个时间段内,只能有一个线程对象持有锁资源【锁对象】,当前线程不释放锁资源之前,其他线程没有能力触发这个临界资源

为了提高代码的效率,尽量减少同步代码块的范围,即什么位置操作临界资源,就在什么位置使用

同步代码块语法

synchronized(传一个唯一的对象){
 		包含操作临界资源的代码   
}
*特别说明:*
    1.synchronized后面传入的这个对象,我们就成为同步代码块的"锁对象即锁资源"
    PS:很多地方法讲解同步代码块的时候,都习惯性使用"this"作为当前做锁对象,不要使用这个,因为"this"会出现锁不住
    private static final Object obj = new Object(); //自己创建一个锁对象
    直接使用字符串  ""  空字符串的形式代表锁对象, 因为字符串是不可改变
        
    2.每个线程执行到同步代码块的时候都会持有当前锁资源对象,在线程没有执行完毕之前,这个锁对象是不会释放的,多个线程在同一个时间段内只有一个线程持有这个锁对象,以保证临界资源安全【只有一个线程操作】
        
    3.sleep方法在同步代码块中是不会被释放CPU资源    
        
package com.qfedu.Synchronized;
//保证100张票
public class SellTicket1 extends Thread{
    //将当前多线程并发访问变量设置为静态,就可以保证全局唯一【多有对象共享静态变量】
    private static int  tickets = 100;

    @Override
    public void run() {
        //执行买票的逻辑
        for(int i = 0;i<100;i++){ //执行的卖票的次数
            synchronized ("") { //只要添加一个同步代码块即可,保证锁资源唯一
                if (tickets > 0) {//证明票是够的,买票
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("当前售票员:【" + Thread.currentThread().getName() + "】" + "第【" + (tickets--) + "】票");
                }
            }
        }
    }

    public static void main(String[] args) {
      
        Thread t1 = new Thread(new SellTicket1(),"刘德华");
        Thread t2 = new Thread(new SellTicket1(),"张学友");
        Thread t3 = new Thread(new SellTicket1(),"郭富城");
        Thread t4 = new Thread(new SellTicket1(),"吴奇隆");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}


同步方法

同步方法其实就是同步代码块的修改版本【同步方的的锁对象是"this“并且不可以修改】,开发中建议使用同步代码块,不建议使用同步方法

访问权限修饰符  synchronized 返回值类型 方法名(形参列表){
		临界资源的操作
}
package com.qfedu.Synchronized;
//保证100张票
public class SellTicket1 extends Thread{
    //将当前多线程并发访问变量设置为静态,就可以保证全局唯一【多有对象共享静态变量】
    private static int  tickets = 100;

    @Override
    public void run() {
        //执行买票的逻辑
        for(int i = 0;i<100;i++){ //执行的卖票的次数
               seller();
            }
        }
    //同步方法,此时默认的锁就是this
    public synchronized  void seller() {
        if (tickets > 0) {//证明票是够的,买票
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("当前售票员:【" + Thread.currentThread().getName() + "】" + "第【" + (tickets--) + "】票");
        }
    }

    public static void main(String[] args) {
        SellTicket1 st1 = new SellTicket1();
        Thread t1 = new Thread(st1,"刘德华");
        Thread t2 = new Thread(st1,"张学友");
        Thread t3 = new Thread(st1,"郭富城");
        Thread t4 = new Thread(st1,"吴奇隆");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}



ps:必须保证当前类的对象是惟一的

静态同步代码块和静态同步方法
PS:静态同步代码块和静态同步方法和同步代码块和同步方法有所区别,区别在于锁对象

​ 静态同步代码块使用锁对象是一个字节码文件对象【类名.class】

​ 静态同步方法,当前类是锁对象

​ 静态同步方法和同步代码块也被称之为【类锁】

语法:

synchronized(类锁){  //类名.class
     临界资源
}
package com.qfedu.Synchronized;
//保证100张票
public class SellTicket1 extends Thread{
    //将当前多线程并发访问变量设置为静态,就可以保证全局唯一【多有对象共享静态变量】
    private static int  tickets = 20;

    @Override
    public void run() {
        //执行买票的逻辑
        for(int i = 0;i<20;i++){ //执行的卖票的次数
              //静态同步代码块,字节码文件对象
             synchronized (String.class){
                 if (tickets > 0) {//证明票是够的,买票
                     try {
                         Thread.sleep(500);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     System.out.println("当前售票员:【" + Thread.currentThread().getName() + "】" + "第【" + (tickets--) + "】票");
                 }
             }
            }
        }


    public static void main(String[] args) {

        Thread t1 = new Thread(new SellTicket1(),"刘德华");
        Thread t2 = new Thread(new SellTicket1(),"张学友");
        Thread t3 = new Thread(new SellTicket1(),"郭富城");
        Thread t4 = new Thread(new SellTicket1(),"吴奇隆");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}













访问权限修饰符  static synchronized 返回值类型 方法名(参数列表){
			临界资源    
}
package com.qfedu.Synchronized;
//保证100张票
public class SellTicket1 extends Thread{
    //将当前多线程并发访问变量设置为静态,就可以保证全局唯一【多有对象共享静态变量】
    private static int  tickets = 100;

    @Override
    public void run() {
        //执行买票的逻辑
        for(int i = 0;i<100;i++){ //执行的卖票的次数
               seller();
            }
        }
    //静态同步方法,此时默认的锁就是当前类的字节码对象,即 SellTickets1.class
    public static  synchronized  void seller() {
        if (tickets > 0) {//证明票是够的,买票
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("当前售票员:【" + Thread.currentThread().getName() + "】" + "第【" + (tickets--) + "】票");
        }
    }

    public static void main(String[] args) {

        Thread t1 = new Thread(new SellTicket1(),"刘德华");
        Thread t2 = new Thread(new SellTicket1(),"张学友");
        Thread t3 = new Thread(new SellTicket1(),"郭富城");
        Thread t4 = new Thread(new SellTicket1(),"吴奇隆");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}













如何选中那种操作?

类锁【静态同步代码块和静态同步方法】在开发中相对使用较少,静态同步方法使用还是比较多,建议同步代码块使用对象锁。

synchronized的好与坏

好处:保证了多线程并发访问是同步操作安全

缺点:使用synchronized的方法或代码块性能稍低,所以尽量减少使用范围

PS:Java5中提供一个替代synchronized的锁对象 【Lock】

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值