多线程

单线程

/*
	什么是单线程:
		单线程在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行
	单线程处理的优点:
    	同步应用程序的开发比较容易,但由于需要在上一个任务完成后才能开始新的任务,所以其效率通常比多线程应用程序低。
*/
public class Demo {
    public static void main(final String[] args) {
        // 代码1
        show();
    }

    public static void show(){
        // 代码11
        fun1();
        fun2();
        //代码22
    }

    public static void fun1() {
         //代码111 
    }

    public static void fun2() {
        //代码222
    }
}

进程、线程;串行、并行、并发;Java程序的运行原理

/*
        进程:
            正在运行的程序,是系统进行资源分配和调用的独立单位。
            每一个进程都有它自己的内存空间和系统资源。

        线程:
            是存在于进程中的,一个进程可以包含多个线程的。
            单个线程是进程中的单个顺序控制流,或者说就是一个单独执行路径
            一个进程如果只有一条执行路径,称之为单线程程序
            一个进程如果包含多条执行路径,称之为多线程程序。

        多线程举例:
            网盘下载,扫雷,杀毒软件。


        了解三个名词:
            1、串行:指的是所有的任务都是按照先后顺序执行的,在前一个任务没处理完的情况下
                是不会处理下一个任务的。
                比如理发店只有一个理发师,每个人剪头发的时候都需要等待前面的人剪完。
                
            2、并行:指的是将任务分给不同的处理器去处理,每一个处理器中的处理是串行。
                比如火车站售票会有多个窗口。
                
            3、并发:实质上是一种现象,并发需要处理器的支持,比如在处理一个任务的时候,操作系统
                可以进行调度其他的任务,不论串行还是并行,都需要操作系统的支持并发。
                假设喝水是一个任务,每个火车售票员在卖票的同时也能喝水,就说明支持并发。


         java程序的运行原理:
            java命令会启动一个java虚拟机。启动JVM,相当于启动了一个应用程序
            也就是启动了一个进程。
            该进程会自动启动一个“主线程”,然后由这个主线程去调用某个类的main方法
            main方法运行在主线程中的。我们之前写的程序都是单线程程序。

        思考题:
            JVM启动的时候是单线程还是多线程的呢?
                多线程。
                    主线程
                    垃圾回收线程
            JVM在启动的时候,最低要求要有两个线程,JVM启动的时候是多线程。
*/

public class MyThreadDemo {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person();
        Person p3 = new Person();
        Person p4 = new Person();
        Person p5 = new Person();
        Person p6 = new Person();
        Person p7 = new Person();
        Person p8 = new Person();
        Person p9 = new Person();
        //...pn
   
        //用for循环创建100000个对象,那么在栈中创建那么多对象,在堆中开辟了那么多空间
        //为什么没有报错呢?或者说为什么没有发生内存溢出呢?
        //那是因为JVM启动至少启动了垃圾回收线程和主线程,
        //所以JVM是多线程的。
        for(int i=0;i<100000;i++){
            Person p = new Person();
        }
    }
}

创建线程的第一种方式:继承Thread类

/*
        为什么要重写run方法呢?
            1、每一个线程实现的功能都不一样,所以要重写
            2、要启动一个线程就会调用run方法,只有当代码需要被线程执行的时候
            再将被需要的代码写到run方法里
*/

public class MyThread1 extends Thread {
    @Override
    public void run() {
        //写我们自己的逻辑代码
        //System.out.println("数加科技,yyds");
        //一般来说,被执行线程的代码都是比较耗时的,为了模拟耗时
        //我这边就用循环模拟
        for(int i=0;i<100;i++){
            System.out.println(i);
        }
    }
}

/*
        创建线程的第一种方式:继承Thread类(java.lang下的),重写run()方法

        步骤:
            1、自定义一个类继承Thread类
            2、自定义类重写Thread类中的run()方法
            3、创建子类对象
            4、启动线程
*/

public class MyThreadDemo2 {
    public static void main(String[] args) {
        //创建子类对象
        //每new一次相当于创建了一个线程(但是仅仅只是创建,并没有启动)
        //MyThread1 t1 = new MyThread1();

        
        //t1.run();
        //t1.run();
        //单纯调用run方法仅仅表示的是普通的方法调用,所以这里是单线程执行的
        //要想看到多线程的效果,就必须调用指定的启动线程的方法:start()
        //面试题:调用run方法和调用start方法的区别?
        //run()方法的调用仅仅是封装了线程执行的代码,但是直接调用的话与普通方法调用没有任何区别
        //start()方法的调用,首先启动一个线程,然后由JVM去调用该线程的run方法

        //t1.start();
        //IllegalThreadStateException:非法的线程状态异常
        //因为调用了两次start()方法,相当于t1这个线程被调用了两次,而不是启动了两个线程的意思
        //t1.start();

        System.out.println("===================================");
        //模拟多线程环境
        //至少创建2个或者2个以上的线程
        MyThread1 t1 = new MyThread1();
        MyThread1 t2 = new MyThread1();

        t1.start();
        t2.start();
    }
}

如何获取和设置线程名称 

//String getName()
//返回此线程的名称。
//假如我不给线程起名字通过getName()方法获取到的线程的名字会是什么呢?
//通过Thread无参构造方法源码发现:
//返回的是:Thread-threadInitNumber++(threadInitNumber初始默认值为0)
public class MyThread2 extends Thread {

    public MyThread2() {
        super();
    }

    public MyThread2(String name) {
      //调用父类的有参构造方法
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

/*
        如果获取和设置线程的名字呢?
            1、通过构造方法设置名字
                Thread(String name) 分配一个新的 Thread对象。
            2、通过Thread类中
                void setName(String name) 将此线程的名称更改为等于参数 name 。
*/

public class MyThreadDemo3 {
    public static void main(String[] args) {
        //创建线程对象
        //通过构造方法给线程起名字
        //由于是多线程,创建的个位为两个或者两个以上
//        MyThread2 t1 = new MyThread2("朱佳乐");
//        MyThread2 t2 = new MyThread2("小虎");
//        t1.start();
//        t2.start();

//        通过setName给线程起名字
//        MyThread2 t1 = new MyThread2();
//        MyThread2 t2 = new MyThread2();
//        MyThread2 t3 = new MyThread2();
//
//        给线程起名字
//        t1.setName("张咪");
//        t2.setName("张梦云");
//        t3.setName("刘梦云");
//
//        //启动线程
//        //线程的执行具有随机性
//        t1.start();
//        t2.start();
//        t3.start();

        //public static Thread currentThread()
        //获取当前方法所在的线程名称
        //现在获取的是main方法所在的线程名称
        String name = Thread.currentThread().getName();
        System.out.println("main方法所在的线程名称为:" + name);//main
    }
}

线程调度 

public class MyPriorityThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

/*
线程调度:
        假如我们的计算机只有一个 CPU(一核),那么CPU在某一个时刻只能执行一条指令,
        线程只有得到CPU时间片,也就是使用权,才可以执行指令。
        那么Java是如何对线程进行调用的呢?
        
        线程有两种调度模型:
        	分时调度模型   
        		所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
            
        	抢占式调度模型   
        		优先让优先级高的线程使用CPU,如果线程的优先级相同,
            	那么会随机选择一个占用CPU的时间片,
            	优先级高的线程获取的CPU时间片相对多一些。(注意是相对而不是绝对)

		Java使用的是抢占式调度模型。

        我们在前几个代码中都没有设置优先级,我们猜测应该有一个默认的优先级
        默认的优先级是多少呢?
        	默认的优先级是: 5

        获取线程的优先级
        	public final int getPriority()

        设置线程的优先级
        	public final void setPriority(int newPriority)
        	观察源码发现:线程的优先级范围是1-10

        注意事项:
            1、线程的默认优先级为5
            2、线程的优先级范围是1-10
            3、线程的优先级高仅仅表示的是获取CPU时间片的机率会高一些,
               并不代表一定是最先获取的。
*/

public class ThreadPriorityDemo {
    public static void main(String[] args) {
        //创建2个或者2个以上的线程对象
        MyPriorityThread t1 = new MyPriorityThread();
        MyPriorityThread t2 = new MyPriorityThread();
        t1.setName("朱佳乐");
        t2.setName("张咪");

        //获取线程的优先级
//        int p1 = t1.getPriority();
//        System.out.println(p1);//5
//        int p2 = t2.getPriority();
//        System.out.println(p2);//5

        //设置优先级
        //非法的优先级设置异常
        //观察源码发现:线程的优先级范围是1-10
        //IllegalArgumentException
//        t1.setPriority(1000);
//        t2.setPriority(0);
        t1.setPriority(10);
        t2.setPriority(1);

        //启动线程
        t1.start();
        t2.start();
    }
}

线程控制 

  • 线程休眠

public class MySleepThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);

            //加入一个休眠方法
            //睡1秒钟,1秒=1000毫秒
            //此时会发生编译时期异常
            //InterruptedException:被打断异常
            //try...catch...处理
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/*
		线程休眠
			public static void sleep(long millis)
            	 以毫秒为单位的睡眠时间长度 
        
        那么sleep()方法在哪里加呢?
            在重写的run()方法里面加
*/

public class ThreadSleepDemo {
    public static void main(String[] args) {
        MySleepThread t1 = new MySleepThread();
        MySleepThread t2 = new MySleepThread();
        MySleepThread t3 = new MySleepThread();

        t1.setName("张咪");
        t2.setName("张梦云");
        t3.setName("刘梦云");

        t1.start();
        t2.start();
        t3.start();
    }
}
  • 线程加入

public class MyJoinThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

/*
        加入线程:其他线程等待调用该方法的线程死亡,再执行
            调用join()方法在该线程调用start方法之后调用。
        
        其他线程要为实现线程加入的线程让步。(让其先执行)
*/

public class ThreadJoinDemo {
    public static void main(String[] args) {
        MyJoinThread t1 = new MyJoinThread();
        MyJoinThread t2 = new MyJoinThread();
        MyJoinThread t3 = new MyJoinThread();

        t1.setName("张咪");
        t2.setName("朱佳乐");
        t3.setName("小虎");

        //让t3先执行
        //此时会出现编译异常
        t3.start();
        try {
          //调用join()方法在start()方法之后调用
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //此时t1和t2会等t3先执行完之后
        //再相互抢占CPU的时间片
        t1.start();
        t2.start();
    }
}
  • 线程礼让

public class MyYieldThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":" + i);
            Thread.yield();
        }
    }
}

/*
        礼让线程:public static void yield()
        	它的作用是让线程执行起来之间看起来更加和谐一点(没什么作用)
        
        该方法在哪里调用呢?
        	在重写的run()方法中调用
*/

public class ThreadYieldDemo {
    public static void main(String[] args) {
        MyYieldThread t1 = new MyYieldThread();
        MyYieldThread t2 = new MyYieldThread();

        t1.setName("小虎");
        t2.setName("冯提莫");

        t1.start();
        t2.start();
    }
}
  • 后台线程

public class MyDaemonThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(getName() + ":" + i);
        }
    }
}

/*
        后台线程 public final void setDaemon(boolean on)
        (守护线程)

        java中有两类线程:用户线程(非守护线程),守护线程

        用户线程:我们在学习多线程之前的所有代码,运行起来的时候都是一个个的用户线程

        守护线程:所谓的守护线程,指的就是程序运行的时候在后台提供一个通用的服务线程
            比如说垃圾回收线程,它就是一个称职的守护线程,并且这个线程是程序不可或缺一部分
            或者说只要程序存在非守护线程,程序就不会停止
            如果一个程序都是守护程序,程序就停止了。

       注意:
            设置守护线程必须在启动之前设置
*/

public class ThreadDaemonDemo {
    public static void main(String[] args) {
        MyDaemonThread t1 = new MyDaemonThread();
        MyDaemonThread t2 = new MyDaemonThread();
        MyDaemonThread t3 = new MyDaemonThread();

        //刘备是非守护线程(用户线程)
        t1.setName("刘备");
        t2.setName("关羽");
        //设置关羽为刘备的守护线程
        t2.setDaemon(true);
        t3.setName("张飞");
        //设置张飞为刘备的守护线程
        t3.setDaemon(true);

        t1.start();
        t2.start();
        t3.start();
    }
}
  • 中断线程

import java.util.Date;

public class MyStopThread extends Thread {
    @Override
    public void run() {
        System.out.println("开始执行时间:" + new Date());

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("结束时间:" + new Date());
        System.out.println("hello");
    }
}

/*
        中断线程:
            public final void stop():
                让正在运行的线程停止,run方法剩下的代码不会执行,这个方法过时了,被弃用了。
                
            public void interrupt():
                中断正在运行的线程,被中断的线程将run方法执行完毕,            
                并且抛出异常InterruptedException: sleep interrupted
*/

public class ThreadStopDemo {
    public static void main(String[] args) {
        MyStopThread myStopThread = new MyStopThread();
        myStopThread.start();

        try {
          //让当前主线程main休眠3秒
            Thread.sleep(3000);
          //此时停止线程
//            myStopThread.stop();
          //此时打断线程
            myStopThread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

线程的生命周期

 创建线程的第二种方式:实现Runnable接口

public class MyRunnable1 implements Runnable {
    @Override
    public void run() {
        for(int i=1;i<=100;i++){
            //这里报错是因为实现的Runnable接口中并没有getName()方法(只有一个抽象的run()方法),
            //所以这里无法使用Thread类中的方法
            //System.out.println(getName()+":"+i);
          
            //间接使用,使用currentThread()获取当前执行的线程对象
            //间接调用getName()方法。
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

/*
        创建线程的第二种方式:实现Runnable接口
            1、创建自定义类实现Runnable接口
            2、实现run方法
            3、创建自定义类的对象
            4、创建Thread类的对象,将第三步创建的自定义对象作为参数
                传递到构造方法中。
                
        此时需要Thread类中的构造方法:
        		public Thread(Runnable target)分配一个新的Thread对象。
                    需要传一个实现Runnable接口的类的对象
                    
                public Thread(Runnable target,String name)分配一个新的Thread对象
             	    需要传一个实现Runnable接口的类的对象和线程的名字
        
        实现接口方式的好处
			1、可以避免由于Java单继承带来的局限性。            
			2、适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,
            数据有效分离,较好的体现了面向对象的设计思想。

*/

public class MyRunnableDemo1 {
    public static void main(String[] args) {
        //创建自定义类的对象
        MyRunnable1 myRunnable1 = new MyRunnable1();

        //借助Thread类创建线程对象
        Thread t1 = new Thread(myRunnable1);
        Thread t2 = new Thread(myRunnable1);

        t1.start();
        t2.start();
    }
}

电影售票案例

public class TicketWindow1 extends Thread {
    //每new一次都会有一个100张票
    //private int tickets = 100;

    //所以应该让多个线程窗口共享这100张票,所以我们应该定义为静态的
    private static int tickets = 100;

    @Override
    public void run() {
        //在run方法中定义100张票是有问题的
        //每个线程都会走到这里来,这样的话,每个线程相当于卖的是自己的那100张票
        //所以,我们将票定义在这里不太合理
        //int tickets = 100;
        while (true){
            if(tickets>0){
                System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
            }
        }
    }
}

/*
    某电影院目前正在上映贺岁大片,共有100张票,
    而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

    第一种方式:
        继承Thread类
*/

public class SellTicketDemo1 {
    public static void main(String[] args) {
        //创建三个线程对象,模拟三个窗口
        TicketWindow1 window1 = new TicketWindow1();
        TicketWindow1 window2 = new TicketWindow1();
        TicketWindow1 window3 = new TicketWindow1();

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}
public class TicketWindow2 implements Runnable {
  //使用Runnable接口,创建线程的时候,只new了一次
  //所以不需要加static关键字
    private int tickets = 100;

    @Override
    public void run() {
        while (true){
            if(tickets>0){
                System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
            }
        }
    }
}

/*
    某电影院目前正在上映贺岁大片,共有100张票,
    而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

    第二种方式:
        实现Runnable接口
*/

public class SellTicketDemo2 {
    public static void main(String[] args) {
        TicketWindow2 ticketWindow2 = new TicketWindow2();

        //创建三个线程对象,模拟三个窗口售票
        Thread window1 = new Thread(ticketWindow2);
        Thread window2 = new Thread(ticketWindow2);
        Thread window3 = new Thread(ticketWindow2);

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}
public class TicketWindow3 implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true){
            //产生问题的分析:
            //A:相同的票卖了很多次
            //B:出现了第0张票,负数的票
          
            //A1:一开始window1,window2,window3都进来了
            //A2:一开始的票数是100

            //B1:假设这里tickets是1的时候,三个窗口都进来了
            if(tickets>0){
                //A3:假设window2先抢到CPU的时间片
                //A4:然后window2先休眠100毫秒
                //A5:window1,window3
                try {
                    Thread.sleep(100); //A6:window1开始休息,window2,window3都在休息
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //A7:三个线程休息完后,都变成就绪状态
                //A8:假设window1抢到了CPU执行权

                //A13:当window1正在输出的时候,window2抢到了CPU执行权,此刻tickets值还没有发生变化,依旧是100
                //A14:所以输出:窗口1正在出售第100张票
                //A15:所以输出:窗口2正在出售第100张票
                //A16:当它两个输出完之后,tickets的值减1了以后window3抢到了CPU执行权
                //A17:所以轮到window3输出的时候:窗口3正在出售第99张票
                //A18:这就是为什么会出现重复的原因

                //B2:三个窗口都进来了之后,都会执行这一步
                System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");

                //A9:理想状态下:
                //A10:窗口1正在出售第100张票
                //A11:窗口2正在出售第99张票
                //A12:..

                //A19:出现重复的情况解释:
                //A20:由于CPU的操作都是原子性的
                //A21:由因为tickets--不是原子性操作,因为它是两步操作,先赋值,输出完之后再--
                //A22:在窗口1输出这句话的时候,tickets完成了赋值操作,但是还没有完成--操作,这时候
                //A23:窗口2也执行到这一步,而这时候tickets并没有发生变化,所以会看到有重复的票出现

                //B3:出现0和负数解释:
                //B4:假设window1先抢到CPU执行权
                //B5:窗口1正在出售第1张票
                //B6:window1输出完时候tickets--,tickets=0
                //B7:这时候window2抢到了CPU执行权
                //B8:窗口2正在出售第0张票
                //B9:window2输出完时候tickets--,tickets=-1
                //B10:最后window3才抢到了CPU执行权
                //B11:窗口3正在出售第-1张票
            }
        }
    }
}

/*
    为了模拟更加真实的售票情况,我们加入延迟(让线程休眠一会)
    加入延迟之后。产生了两个问题:
        1、相同的票卖了很多次
            是由CPU的操作是原子性导致的
        2、出现了第0张票,负数的票。 运行结果:(窗口2正在出售第0张票、窗口3正在出售第-1张票)
            是由于线程的执行具有随机性和延迟性导致的,加入了sleep()方法后线程变成了阻塞状态,
            让其他线程执行。
    如何解决这个两个问题呢?
*/

public class SellTicketDemo3 {
    public static void main(String[] args) {
        TicketWindow3 ticketWindow3 = new TicketWindow3();

        //创建三个线程对象,模拟三个窗口售票
        Thread window1 = new Thread(ticketWindow3);
        Thread window2 = new Thread(ticketWindow3);
        Thread window3 = new Thread(ticketWindow3);

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

解决线程安全问题的基本思想及解决同步安全问题的方式1

public class TicketWindow4 implements Runnable {
    private int tickets = 1000;
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (tickets > 0) {

                    try {
                        Thread.sleep(50); //window1开始休息,window2,window3都在休息
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                }
            }
        }
    }
}

/*
        我们刚刚加入延迟的卖票程序后,发现出现问题了,
        这样的问题在多线程中称之为,线程安全问题。
        要向解决这个问题,就应该去判断哪些原因导致的问题所在:
        (三点,也是今后我们去判断一个程序是否存在线程安全的标准,缺一不可)
        1、是否存在多线程环境
        2、是否存在共享数据/共享变量
        3、是否有多条语句操作着共享数据/共享变量

        回想一下,刚刚的案例是否满足以上条件
        1、是否存在多线程环境,是。有三个线程模拟三个窗口
        2、是否存在共享数据/共享变量,是。共享数据是100张票
        3、是否有多条语句操作着共享数据/共享变量,是。tickets--等等

       由此可见,我们上一个案例出现问题是一个正常的现象,因为它同时满足以上三个条件

       如何解决这些问题呢?
       第一,二两个原因条件我们是改变不了的,我们只能想办法改变第三个原因条件,因为只要有一个条件
       不满足,就不是线程安全问题了。

       解决问题的思想:
            要是有一个办法,在其中一个线程执行的时候,其他线程进不来不就好了吗。
            如果可以把多条语句操作共享数据的代码包起来,包成一个整体,在某个线程执行的时候,
            别的线程进不来,直到这个线程执行完毕一次run方法之后,别的线程再进来。

       java提供了一个机制给我们使用,叫做:同步安全机制

       解决同步安全问题的方式1:
            同步代码块:
            格式:
                synchronized(对象){
                    需要同步的代码(这里放多条操作共享数据的代码);
                }

           1、这里的对象是什么呢?
                随便创建一个对象试试
           2、需要同步的代码是什么?
                这里放多条操作共享数据的代码
*/

public class SellTicketDemo4 {
    public static void main(String[] args) {
        TicketWindow4 ticketWindow4 = new TicketWindow4();

        //创建三个线程对象,模拟三个窗口售票
        Thread window1 = new Thread(ticketWindow4);
        Thread window2 = new Thread(ticketWindow4);
        Thread window3 = new Thread(ticketWindow4);

        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");

        window1.start();
        window2.start();
        window3.start();
    }
}

同步的特点 

public class TicketWindow1 implements Runnable{
    //定义100张票
    private int tickets = 100;

    private Object obj = new Object();

    @Override
    public void run() {
      //模拟一直卖票
        while (true){
          //为了线程的安全,加上同步代码块
          //synchronized关键字后面的小括号中到底放什么呢?
            synchronized (obj) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                }
            }
        }
    }
}

/*
        同步的好处:
            解决了多线程的安全问题

        同步的弊端:
            加了一个线程之后,就相当于加了一把锁,每次进入同步代码块的时候都会判断一下
            无形中,降低了执行效率
*/

public class SellTicketDemo1 {
    public static void main(String[] args) {
        //创建实现Runnable接口的自定义对象
        TicketWindow1 ticketWindow1 = new TicketWindow1();

        //创建三个线程对象模拟三个窗口
        //Thread类中的第二种构造方法:
        //Thread(Runnable target, String name)
        //分配一个新的 Thread对象。
        //在创建线程对象的同时,给线程起个名字
        Thread t1 = new Thread(ticketWindow1,"窗口1");
        Thread t2 = new Thread(ticketWindow1,"窗口2");
        Thread t3 = new Thread(ticketWindow1,"窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

同步代码块中的锁对象

public class TicketWindow2 implements Runnable {
    //定义100张票
    private int tickets = 1000;
  
    private Object obj = new Object();
    //创建一个自定义类的对象
  
    private Demo demo = new Demo();

    @Override
    public void run() {
        while (true){
          //将demo放到synchronized关键字后面的小括号中
          //发现也能解决线程同步安全的问题
            synchronized (demo) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                }
            }
        }
    }
}
//我们先自定义一个类
class Demo {
}

/*
    1、同步代码块的锁对象是谁呢?
        任意对象,但是多个线程之间锁对象要一样
*/

public class SellTicketDemo2 {
    public static void main(String[] args) {
        TicketWindow2 ticketWindow2 = new TicketWindow2();

        //创建三个线程对象模拟三个窗口
        //Thread(Runnable target, String name)
        //分配一个新的 Thread对象。
        //在创建线程对象的同时,给线程起个名字
        Thread t1 = new Thread(ticketWindow2,"窗口1");
        Thread t2 = new Thread(ticketWindow2,"窗口2");
        Thread t3 = new Thread(ticketWindow2,"窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

同步方法

public class TicketWindow2 implements Runnable {
   //定义100张票
    private int tickets = 1000;
    //定义一个变量
    int i = 1;

    @Override
    public void run() {
        while (true) {
          //加上判断,让线程一会执行上面的代码,一会执行下面的代码
            if (i % 2 == 0) {//当i为2的倍数的时候,执行下方代码
              
              //为了防止同步方法和同步代码块锁对象不一致
              //将synchronized关键字后面的锁对象换成this
              //运行后发现:线程是同步安全的
              synchronized (this) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                    }
                }
            } else {//否则调用sellTicket()方法
                sellTicket();
            }
          //要让i发生变化
            i++;
        }
    }
  //同步方法:将synchronized关键字放在修饰符和返回值之间
  //那么为了防止同步方法和同步代码块锁对象不一致,而无法保证线程同步安全的问题
  //我们需要知道同步方法的锁对象到底是谁?
  //因为方法是由当前对象调用的,那么我就大胆猜测用this指针指代当前调用方法的对象
    public synchronized void sellTicket() {
        if (tickets > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
        }
    }

/*
    同步方法:
        将synchronized关键字放到方法上
        同步方法的锁对象是this
*/

public class SellTicketDemo2 {
    public static void main(String[] args) {
        TicketWindow2 ticketWindow2 = new TicketWindow2();

        //创建三个线程对象模拟三个窗口
        //Thread(Runnable target, String name)
        //分配一个新的 Thread对象。
        //在创建线程对象的同时,给线程起个名字
        Thread t1 = new Thread(ticketWindow2,"窗口1");
        Thread t2 = new Thread(ticketWindow2,"窗口2");
        Thread t3 = new Thread(ticketWindow2,"窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

当同步方法是静态的时

public class TicketWindow2 implements Runnable {
    //定义100张票
    //private int tickets = 1000;
    //将同步方法改为静态之后,因为静态的只能访问静态的
    //所以将成员变量也变成静态的
    private static int tickets = 1000;
    int i = 1;

    @Override
    public void run() {
        while (true) {
            if (i % 2 == 0) {
              //为了防止同步方法和静态同步代码块锁对象不一致,
              //而无法保证线程同步安全的问题
              //将同步方法的锁对象换成run方法所在类的字节码文件
              //运行后发现:线程是同步安全的
                synchronized (TicketWindow2.class) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
                    }
                }
            } else {
                sellTicket();
            }
            i++;
        }
    }
    //静态方法的锁对象是run方法所在类的字节码文件
    public synchronized static void sellTicket() {
        if (tickets > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
        }
    }
}

/*
    静态方法的锁对象是谁呢?
        class文件,字节码文件对象也是属于一个Object类下面的对象(反射的时候说)
        这个class文件不能是随便一个类的字节码文件
        应该是run方法所在类的字节码文件
*/

public class SellTicketDemo2 {
    public static void main(String[] args) {
        TicketWindow2 ticketWindow2 = new TicketWindow2();

        //创建三个线程对象模拟三个窗口
        //Thread(Runnable target, String name)
        //分配一个新的 Thread对象。
        //在创建线程对象的同时,给线程起个名字
        Thread t1 = new Thread(ticketWindow2,"窗口1");
        Thread t2 = new Thread(ticketWindow2,"窗口2");
        Thread t3 = new Thread(ticketWindow2,"窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

解决线程同步安全问题的方式2

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TicketWindow3 implements Runnable {

    //定义100张票
    private int tickets = 100;

    //创建锁对象
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            //加锁
            lock.lock();
            if (tickets > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
            }
            //释放锁
            lock.unlock();
        }
    }
}

/*
    解决线程同步安全问题的第二种解法:加锁Lock
        在此之前我们解决线程同步安全问题的时候,使用的是synchronized关键字,通过分析
        然后将哪些代码块给包起来,但是,我们并没有直接看到在哪里上了锁,或者说
        在哪里释放了锁让其他线程获取到
        为了更加清晰的表达如何加锁以及如何释放锁,JDK1.5之后提供了一个新的锁对象
        Lock。

        Lock(接口)
            具体的子类:Class ReentrantLock
                void lock()  加锁
                void unlock()  释放锁
*/
public class SellTicketDemo3 {
    public static void main(String[] args) {
        TicketWindow3 ticketWindow3 = new TicketWindow3();

        //创建三个线程对象模拟三个窗口
        //Thread(Runnable target, String name)
        //分配一个新的 Thread对象。
        //在创建线程对象的同时,给线程起个名字
        Thread t1 = new Thread(ticketWindow3,"窗口1");
        Thread t2 = new Thread(ticketWindow3,"窗口2");
        Thread t3 = new Thread(ticketWindow3,"窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

死锁的问题

//自定义一个类创建两把锁
public class MyLock {
    //定义静态的成员变量,方便通过类名调用锁
    //并且被final修饰,锁不可改变
    public static final Object lock1 = new Object();
    public static final Object lock2 = new Object();

}

//定义一个类继承Thread类
public class DieLock extends Thread {
    //定义一个flag判断是哪国人
    private boolean flag;
    //通过构造方法对flag赋值
    public DieLock(boolean flag){
        this.flag = flag;
    }
    /*  
    运行结果:
    	现象1:
            if lock1
            else lock2

        现象2:
            else lock2
            if lock1
            
        现象3:
        理想情况下,不产生死锁
     */
    @Override
    public void run() {
        if(flag){
            //1、假设d1先进来拿到了lock1这把锁
            synchronized (MyLock.lock1){
            //2、然后d1输出"if lock1"
                System.out.println("if lock1");
                //5、然后d1等待d2释放锁lock2
                synchronized (MyLock.lock2){
                    System.out.println("if lock2");
                }
            }
        }else {
            //3、假设此时d2进来拿到了lock2这把锁
            synchronized (MyLock.lock2){
              //4、然后d2输出"else lock2"
                System.out.println("else lock2");
                //6、同时d2等待d1释放锁lock1,这样就出现了死锁现象
                synchronized (MyLock.lock1){
                    System.out.println("else lock1");
                }
            }
        }
    }
}

/*
    死锁:
        是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象

    举例(餐桌问题):
        中国人,美国人吃饭的问题。
        假设中国人必须要有两根筷子才能吃饭
        美国人必须要有一把刀,一把叉才能吃饭

        正常情况下:
            中国人:两支筷子
            美国人:一把刀,一把叉

        现在:
            中国人:一支筷子,一把刀
            美国人:一支筷子,一把叉

       如果出现了同步嵌套,就容易产生死锁问题
*/

public class DieLockDemo {
    public static void main(String[] args) {
        DieLock d1 = new DieLock(true);
        DieLock d2 = new DieLock(false);

        d1.start();
        d2.start();
    }
}

线程间通信 

线程间通信图解

案例

//创建共享数据
public class Student {
    String name;
    int age;
}
//生产者
public class SetThread implements Runnable {
    private Student s;

    public SetThread(Student student){
        this.s = student;
    }

    @Override
    public void run() {
//        Student s = new Student();
        s.name = "张梦云";
        s.age = 18;
    }
}
//消费者
public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student student){
        this.s = student;
    }

    @Override
    public void run() {
//        Student s = new Student();
        System.out.println(s.name + "---" + s.age);
    }
}

/*
    分析:
        共享数据:Student
        生产者:SetThread 给学生的成员变量进行赋值操作
        消费者:GetThread 获取学生的成员变量的信息
        测试类:StudentDemo 创建线程并测试

   问题1:按照我们的思路去写代码,运行后发现每一次的结果都是null---0,这是必然的
        原因:我们再每个线程类中都创建了新的学生对象,而我们实际上要求的是设置成员变量值与
        获取成员变量值的对象应该是同一个。
        如何解决呢?
            在外界将学生对象创建出来,然后通过构造方法传给每个线程
*/

public class StudentDemo {
    public static void main(String[] args) {
        //创建共享的学生对象
        Student student = new Student();

        //创建自定义类对象
        SetThread setThread = new SetThread(student);
        GetThread getThread = new GetThread(student);

        //根据Runnable的对象创建生产者和消费者的线程对象
        Thread proThread = new Thread(setThread);
        Thread cusThread = new Thread(getThread);

        proThread.start();
        cusThread.start();
    }
}

案例plus

//创建共享数据
public class Student {
    String name;
    int age;
}


import java.util.concurrent.locks.Lock;
//生产者
public class SetThread implements Runnable {
    private Student s;
    private int i = 0;
    private Lock lock;

    public SetThread(Student student) {
        this.s = student;
    }

    public SetThread(Student student, Lock lock) {
        this.s = student;
        this.lock = lock;
    }
  
//解决方案1、关键字加锁
//    @Override
//    public void run() {
//        while (true){
//            synchronized (s){
//                if(i%2==0){
//                    s.name = "张梦云";
//                    s.age = 18;
//                }else {
//                    s.name = "咪啊";
//                    s.age = 20;
//                }
//                i++;
//            }
//        }
//    }

//解决方案2:Lock锁
    @Override
    public void run() {
        while (true) {
            lock.lock();
            if (i % 2 == 0) {
                s.name = "张梦云";
                s.age = 18;
            } else {
                s.name = "咪啊";
                s.age = 20;
            }
            i++;
            lock.unlock();
        }
    }
}


import java.util.concurrent.locks.Lock;
//消费者
public class GetThread implements Runnable {
    private Student s;
    private Lock lock;

    public GetThread(Student student) {
        this.s = student;
    }

    public GetThread(Student student, Lock lock) {
        this.s = student;
        this.lock = lock;
    }

//解决方案1、关键字加锁
//    @Override
//    public void run() {
//        while (true){
//            synchronized (s){
//                System.out.println(s.name + "---" + s.age);
//            }
//        }
//    }

//解决方案2:Lock锁
    @Override
    public void run() {
        while (true) {
            lock.lock();
            System.out.println(s.name + "---" + s.age);
            lock.unlock();
        }
    }
}


/*
    分析:
        共享数据:Student
        生产者:SetThread 给学生的成员变量进行赋值操作
        消费者:GetThread 获取学生的成员变量的信息
        测试类:StudentDemo 创建线程并测试

   问题2:我们为了数据出来的效果好一点,我们加入了循环和判断,不同的判断给不同的值
      但是在运行的时候,出现了新的问题
        1、同一条数据出现了很多次
        2、姓名和年龄不匹配

    原因:
        1、同一条数据出现了很多次
            CPU一点点的时间篇就足矣执行很多次。
        2、姓名和年龄不匹配
            这是由于线程执行的时候具有随机性导致的。
            
    产生了线程安全的问题:
        1、是否存在多线程环境 是
        2、是否存在共享数据 是
        3、是否有多条语句操作共享数据  是

    既然都满足条件,说明线程不安全

    解决方案:加锁
        1、关键字加锁
        2、Lock锁

    注意事项:
        1、不同种类的线程类都要加锁
        2、不用种类的线程类中的锁对象要一样
*/

public class StudentDemo {
    public static void main(String[] args) {
        //创建共享的学生对象
        Student student = new Student();
      
        //解决方案2:
        //为了保证锁对象的唯一,在测试类中创建锁对象
        Lock lock = new ReentrantLock();

        //创建自定义类对象
        SetThread setThread = new SetThread(student,lock);
        GetThread getThread = new GetThread(student,lock);

        //根据Runnable的对象创建生产者和消费者的线程对象
        Thread proThread = new Thread(setThread);
        Thread cusThread = new Thread(getThread);

        proThread.start();
        cusThread.start();
    }
}

等待唤醒机制

案例plus+

//共享数据
public class Student {
    String name;
    int age;
  //定义一个flag判断成员变量有没有值
    boolean flag;
}


import java.util.concurrent.locks.Lock;
//生产者
public class SetThread implements Runnable {
    private Student s;
    private int i = 0;
    private Lock lock;

    public SetThread(Student student) {
        this.s = student;
    }

    public SetThread(Student student, Lock lock) {
        this.s = student;
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true){
            synchronized (s){
                //判断学生对象有没有值
                //flag初始的时候是flase,表示没有值,如果是true表示学生对象有值
                //有值对于生产者来说,等待消费者消费
                if(s.flag){
                    try {
                      //调用wait(),线程阻塞
                        s.wait(); 
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if(i%2==0){
                    s.name = "张梦云";
                    s.age = 18;
                }else {
                    s.name = "咪啊";
                    s.age = 20;
                }
                i++;

                //生产者生产完数据之后通知消费者消费数据
                s.notify();
                //并修改flag为true
                s.flag = true;
            }
        }
    }
}


import java.util.concurrent.locks.Lock;
//消费者
public class GetThread implements Runnable {
    private Student s;
    private Lock lock;

    public GetThread(Student student) {
        this.s = student;
    }

    public GetThread(Student student, Lock lock) {
        this.s = student;
        this.lock = lock;
    }

    @Override
    public void run() {
        while (true){
            synchronized (s){
                //判断学生对象有没有值
                //如果flag的值是false,说明没有数据,消费者进if判断等待
                if(!s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println(s.name + "---" + s.age);

                //消费者消费完数据后通知生产者生产数据
                s.notify();
                //并且修改flag为false
                s.flag = false;
            }
        }
    }
}


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
    分析:
        共享数据:Student
        生产者:SetThread 给学生的成员变量进行赋值操作
        消费者:GetThread 获取学生的成员变量的信息
        测试类:StudentDemo 创建线程并测试

    问题3:虽然我们解决线程安全问题,但是经过分析,程序还存在着等待唤醒机制的问题。
        加入等待唤醒机制。

        如何添加等待唤醒机制呢?
            Object类中有三个方法:
                void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
                void notify() 唤醒正在等待对象监视器的单个线程。
                void notifyAll() 唤醒正在等待对象监视器的所有线程。
        这三个方法为什么不定义在Thread类中呢?
            这些方法想要调用,必须通过锁的对象调用,而我们知道 同步代码块的锁对象可以是任意对象
            所以这些方法都在Object类中,因为java中所有类的父类都是Object
*/

public class StudentDemo {
    public static void main(String[] args) {
        //创建共享的学生对象
        Student student = new Student(); //一开始 null---0

        //创建自定义类对象
        SetThread setThread = new SetThread(student);
        GetThread getThread = new GetThread(student);

        //根据Runnable的对象创建生产者和消费者的线程对象
        Thread proThread = new Thread(setThread);
        Thread cusThread = new Thread(getThread);

        cusThread.start();
        proThread.start();
    }
}

线程的状态转换图 

线程组

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

/*
        线程组:
            Java中使用ThreadGroup来表示线程组,
            它可以对一批线程进行分类管理,
            Java允许程序直接对线程组进行控制。
*/

public class ThreadGroupDemo {
    public static void main(String[] args) {
        //创建自定义类对象
        MyRunnable myRunnable = new MyRunnable();

        //创建线程对象
        Thread t1 = new Thread(myRunnable, "朱佳乐");
        Thread t2 = new Thread(myRunnable, "刘生发");

        //我们之前都没有提过线程组,以及分组的概念,想着应该有一个默认的分组
        //Thread类中有个方法可以获取分组
        //ThreadGroup getThreadGroup()
        //返回此线程所属的线程组。
        ThreadGroup tg1 = t1.getThreadGroup();
        System.out.println(tg1);
        ThreadGroup tg2 = t2.getThreadGroup();
        System.out.println(tg2);

        //获取线程组的名字
        //String getName()
        //返回此线程组的名称。
        System.out.println(tg1.getName());//main
        System.out.println(tg2.getName());//main
        System.out.println(Thread.currentThread().getName());//main

        //需求:给线程分组
        //Thread类中有个构造方法
        //Thread(ThreadGroup group, Runnable target, String name)
        //分配一个新的 Thread对象,使其具有 target作为其运行对象,
        //具有指定的 name作为其名称,属于 group引用的线程组。
      
        //创建一个新的线程组
        //ThreadGroup类中的构造方法
        //ThreadGroup(String name)
        //构造一个新的线程组。
        ThreadGroup tg3 = new ThreadGroup("美女组");

        //创建线程对象
        Thread t3 = new Thread(tg3, myRunnable, "杨旭");
        Thread t4 = new Thread(tg3, myRunnable, "朱佳乐");
        Thread t5 = new Thread(tg3, myRunnable, "刘生发");
      
        //获取线程组的名字
        System.out.println(t3.getThreadGroup().getName());
        System.out.println(t4.getThreadGroup().getName());

        //Java允许程序直接对线程组进行控制
        //直接通过组名设置这一组都是守护线程,组里面的线程都是守护线程
        tg3.setDaemon(true);
    }
}

线程池 

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
        程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。
        而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,
        更应该考虑使用线程池。
        线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,
        等待下一个对象来使用。
        在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

        如何实现线程池的代码呢?
            1、创建线程池对象,Executors工厂类下的静态方法
               public static ExecutorService newCachedThreadPool()
               		创建一个具有缓存功能的线程池
					缓存:百度浏览过的信息再次访问
    		   public static ExecutorService newFixedThreadPool(int nThreads)
               		创建一个可重用的,具有固定线程数的线程池
			   public static ExecutorService newSingleThreadExecutor()
					创建一个只有单线程的线程池,相当于上个方法的参数是1
                    
            我们只学习:(其他两种线程池用的少)          
                newFixedThreadPool是其中一种线程池
                public static ExecutorService newFixedThreadPool(int nThreads)
            2、如何往线程池中存放线程?(可以存放哪些线程呢?)
            3、在线程池中的线程如何运行呢?
            4、我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
*/

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(2);

        //ExecutorService类中
        //Future<?> submit(Runnable task)
        //提交一个可运行的任务执行,并返回一个表示该任务的未来。
        MyRunnable myRunnable = new MyRunnable();
        pool.submit(myRunnable);
        pool.submit(myRunnable);
        //提交数超过线程池的数量的时候也会执行,只不过是当有空闲线程位置的时候再去执行
        //newFixedThreadPool最大一次性可执行线程数量为初始设置的数量
        pool.submit(myRunnable);

        //我想结束线程的运行,可不可以手动结束呢?如果可以,怎么结束?
        //void shutdown()
        //启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
        //先提交的先关闭
        pool.shutdown();

        System.out.println("======================================");
        //RejectedExecutionException
        //线程池已经被关闭了,不能再提交任务执行
        pool.submit(myRunnable);
    }
}

创建线程的第三种方式实现Callable接口

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

import java.util.concurrent.Callable;

public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "---" + i);
        }
        return null;
    }
}

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
        创建线程的第三种方式:
            自定义类实现Callable接口,实现call()方法

        <T> Future<T> submit(Callable<T> task)
        提交值返回任务以执行,并返回代表任务待处理结果的Future。
*/

public class ThreadPoolDemo2 {
    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2);
 
        MyRunnable myRunnable = new MyRunnable();
        MyCallable myCallable = new MyCallable();

        //可以执行Runnable对象或者Callable对象代表的线程池
        pool.submit(myRunnable);
        pool.submit(myCallable);

        pool.shutdown();
    }
}

我们学过哪些线程安全的类

import java.util.*;

/*
        到目前为止,我们学过哪些线程安全的类
*/

public class ThreadDemo {
    public static void main(String[] args) {
        //线程安全的类,字符串缓冲
        StringBuffer sb = new StringBuffer();
        //Vector
        Vector<String> strings = new Vector<>();
        //Hashtable
        Hashtable<String, String> stringStringHashtable = new Hashtable<>();

        //Vector虽然是线程安全的,但是我们今后开发也不用他,那用谁呢?
        //我们说过一个工具类Collections
        ArrayList<String> strings1 = new ArrayList<>();
        List<String> strings2 = Collections.synchronizedList(strings);
    }
}

匿名内部类方式使用多线程

/*
        匿名内部类方式使用多线程
*/
public class ThreadDemo2 {
    public static void main(String[] args) {
      //使用匿名内部类的形式创建Thread对象1
      //通过构造方法给线程起名字
        Thread t1 = new Thread("朱佳乐") {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        };
      //通过setName方法给线程起名字
//        t1.setName("张梦云");
        t1.start();

      //使用匿名内部类的形式创建Thread对象2
      //通过构造方法给线程起名字
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        },"刘生发");
        t2.start();
    }
}

定时器 

import java.util.Timer;
import java.util.TimerTask;

/*
    定时器是一个应用十分广泛的线程工具,
    可用于调度多个定时任务以后台线程的方式执行。
    在Java中,可以通过Timer和TimerTask类来实现定义调度的功能
    
    TimerTask类(java.util)
      观察API发现:TimerTask类的构造方法被protected修饰
      所以以自定义子类的形式创建任务并重写抽象的run()方法
      

    如何创建定时器呢?
        java提供了一个类给我们使用实现定时器Timer(java.util)
        	无参构造方法:
                Timer()
        		创建一个新的计时器。
            成员方法:
                public void schedule(TimerTask task, long delay)
                在指定的延迟之后安排指定的任务执行。 定时在未来的某一时刻执行任务
                task--要安排的任务
                delay--执行任务之前以delay为单位的延迟
                (delay这个类型是毫秒)
                
                void schedule(TimerTask task, long delay, long period)
                在指定的延迟之后开始 ,重新执行 固定延迟执行的指定任务。
                
                public void cancel()
                终止此计时器,丢弃任何当前计划的任务
*/

public class TimerDemo {
    public static void main(String[] args) {
        //创建定时器对象
        //Timer()
        //创建一个新的计时器。
        Timer timer = new Timer();

        //调度任务执行
        //在指定的延迟之后安排指定的任务执行。 定时在未来的某一时刻执行任务
        //public void schedule(TimerTask task, long delay)
        //自定义子类的形式创建任务
        //delay这个类型是毫秒
        timer.schedule(new MyTask(timer), 3000);
        //public void cancel()终止此计时器,丢弃任何当前计划的任务
        //不应该在这里写,因为上面的延迟是三秒,但是主线程不会去等
        //还没到三秒的时候计时器就被终止了
        //所以应该写在run()方法里
        //timer.cancel();
      
      //因为run()方法里的timer.cancel();已经终止了此计时器
      //所以根本就读不到
      //timer.schedule(new MyTask(timer), 3000);
    }
}

class MyTask extends TimerTask{
  //为了在该类中使用cancel()方法
  //定义Timer类型的成员变量
    private Timer timer;

  //通过构造方法进行传值
    public MyTask(Timer timer){
        this.timer = timer;
    }

    @Override
    public void run() {
        System.out.println("beng!!爆炸了!!");
        timer.cancel();
    }
}
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

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

        //void schedule(TimerTask task, long delay, long period)
        //在指定的延迟之后开始 ,重新执行 固定延迟执行的指定任务。
        //3秒后执行任务,并且每个2秒执行一次任务
        timer.schedule(new MyTask2(), 3000, 2000);
    }
}

class MyTask2 extends TimerTask {

    @Override
    public void run() {
        System.out.println("beng!!爆炸了!!!");
    }
}
posted @ 2021-12-29 01:22  赤兔胭脂小吕布  阅读(22)  评论(0编辑  收藏  举报