多线程-进程、线程、多线程实现方式一:thread类、线程调度、线程控制、生命周期;二:实现Runnable接口;线程安全解决:同步代码块、同步方法;lock锁;线程通信;线程组、线程池、定时器

1 多线程

1.1 概述

  • 一个程序如果只有一条执行路径,就是单线程程序。
  • 一个程序如果有多条执行路径,就是多线程程序。
  • 要想更深入的了解多线程,必须先了解进程。因为线程是依赖进程存在的。

1.2 进程

  • 概述
    通过任务管理器,我们可以很直观的看到进程的存在。
    我们发现只有运行的程序才会出现进程。
    进程:就是正在运行的程序。
  • 进程是系统进行资源分配和调用的独立单位。每一个进程都有自己的内存空间和系统资源。
  • 通过任务管理器我们还发现计算机能同时运行多个程序,也就是计算机支持多进程的。
  • 问:多进程有什么意义?
    假设计算机只支持单进程,也就是说计算机在同一时刻只能干一件事情。听歌的时候就不能打游戏。
    你打游戏的时候就不能看电影。会造成很大的资源浪费。
    多进程的意义在于可以提高cpu的使用率。
  • 问:我们现在的电脑可以一边听音乐,一边玩游戏。。。。。,这些程序真的是同时运行的码?
    不是。因为一般的电脑只有一个cpu,它在某一个时间点上只能做一件事情。
    同时运行多个程序的时候,其实是cpu在多个程序之间进行着快速的切换。

1.3 线程

  • 是进程中单个执行流程,是进程中的单个执行路径。
  • 一个程序如果只有一条执行路径,就是单线程程序。
  • 一个程序如果有多条执行路径,就是多线程程序。
  • 在同一个进程内又可以执行多个任务(比如kk,录屏,录音,计时) 这每一个任务就可以看做是一个线程。
  • 线程:就是程序的执行单元,是程序使用cpu的基本单位。

1.4 多线程有什么意义?

  • 多线程不是提高程序的执行速度,而是为了提高程序的使用率。
  • 程序的执行其实都是在抢夺cpu的资源。cpu的执行权。
    多个进程在强多cpu的资源时,如果其中有一个进程的执行路径比较多。它
    就会有更高的概率可以抢到cpu的执行权。
    比如: 只有一个苹果 共有10个人在抢 其中有8个人都是龙哥的人。
  • 但是我们不能保证哪一个线程在哪个时间段就一定能抢到cpu的执行权。所以线程的执行具有随机性。

1.5 java虚拟机启动是单线程还是多线程?

  • 通过java命令启动jvm,jvm启动相当于启动了一个进程。
    多线程。
    有一个线程执行main方法
    还有一个线程会执行垃圾回收器,否则内存很容易溢出
    根据简单的分析,至少都有2个线程,所以jvm的启动是多线程的。
package com.momo.demo;

public class Demo {
    public static void main(String[] args) {
        System.out.println("adfdsf");

        String s = new String("asdf");

        System.out.println(s);
        System.out.println(s.length());
        s = null;

        System.out.println("adfdsf");
        System.out.println("adfdsf");
        for(int i=0;i<1000;i++){
            System.out.println(i);
            System.out.println(new String("aaa").length());
        }


        System.out.println("adfdsf");
        System.out.println("adfdsf");
    }
}
  • 我们如何实现多线程程序?
    线程是依赖进程存在的,所以应该先要创建一个进程。
    而进程是系统创建的,所以我们要调用系统功能创建进程。
    但是java不能直接调用系统功能,所以没有办法直接实现多线程。

    但是java可以调用c或者c++写好的程序来实现多线程。
    然后由c或者c++调用系统功能创建进程。然后java调用它。

    为了实现这样的效果,java就给我们提供了一个类:Thread类

    通过查看API,我们发现实现多线程的方式由2种:

2 多线程实现方式一:thread类

2.1 继承java提供给我我们的类:Thread

  • 步骤:
    自定义类MyThread继承Thread
    重写Thread类中的 run方法
    创建对象
    启动线程

  • 问:为什么要重写run方法?
    类中有很多代码,但是不是说所有的代码都需要被多线程执行。所以为了区分哪些代码将来需要被多线程执行,java就在Thread类中提供了run方法,来包含那些需要被多线程执行的代码。

package com.momo.demo;

import com.momo.thread.MyThread;
/*
* 注意:启动线程的方法不是run。是start方法、。
* 问:调用run方法和start方法的区别。
*
* void start()
导致此线程开始执行; Java虚拟机调用此线程的run方法。
* */
public class Demo2 {
    public static void main(String[] args) {
        //MyThread mt = new MyThread();
        //启动线程
       /* mt.run();
        mt.run();*/
        //调用run没有看到多线程的效果。
        //因为调用run方法相当于是普通方法的调用。所以还是单线程的效果。
        //这样相当于是一个对象调用了2次方法。

        //启动线程的方法是start方法
       // mt.start();
        //IllegalThreadStateException 非法的线程状态异常
        //为什么?因为我们这样做相当于是把mt线程启动了2次导致的。
        //mt.start();

        //创建2个线程对象
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        mt1.start();
        mt2.start();
    }
}
package com.momo.thread;
/*
* 为什么要重写run方法?
* 重写应该写什么?
*   类中有很多代码,但是不是说所有的代码都需要被多线程执行。
*   所以为了区分哪些代码将来需要被多线程执行,java就在Thread类中
*   提供了run方法,来包含那些需要被多线程执行的代码。
*
*   一般来说被多线程执行的代码都是比较耗时的代码。
* */
public class MyThread extends Thread{
    @Override
    public void run() {
        //用循环替代一下执行起来比较耗时的代码
        for(int i=0;i<100;i++){
            System.out.println(i);
        }
    }

    public void show(){
        //有很多代码
        int a = 5;
        String s = "adf";
        int b = a+5;
        for(int i=0;i<100;i++){
            new Integer(111);
            System.out.println(i);
        }
        System.out.println("sdfdsf");
        //.....
    }
}

2.2 如何获取和设置线程的名称呢?

  • 使用Thread类中提供的方法
    无参+set
    Thread() 分配一个新的 Thread对象。
    String getName() 返回此线程的名称。
    void setName(String name) 将此线程的名称更改为等于参数 name 。
    带参
    Thread(String name) 分配一个新的 Thread对象。

  • 注意这个方法:
    static Thread currentThread()
    返回对当前正在执行的线程对象的引用。

package com.momo.demo;

import com.momo.thread.MyThread;
import com.momo.thread.MyThread2;
/*
* 线程对象其实有默认的名字。 默认的名字是  Thread-编号  编号是从0开始的。
* 为什么是 Thread-编号?
*class Thread{
*    private static int threadInitNumber=0;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
    *
*   public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
*
* private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }
* }
* */
public class Demo3 {
    public static void main(String[] args) {
        MyThread2 mt1 = new MyThread2();
        MyThread2 mt2 = new MyThread2();

       // mt1.setName("宝强");
       // mt2.setName("宋吉吉");

        mt1.start();
        mt2.start();

        System.out.println("---------------");
      /*  MyThread2 mt1 = new MyThread2("宝强");
        MyThread2 mt2 = new MyThread2("宋吉吉");

        mt1.start();
        mt2.start();*/
        System.out.println("----------");
        //我要获取执行main方法的线程的名称
       /* Thread thread = Thread.currentThread();
        System.out.println(thread.getName());*/
    }
}
package com.momo.thread;

public class MyThread2 extends Thread{
    public MyThread2(){}
    public MyThread2(String name){
        super(name);
    }
    @Override
    public void run() {
        //用循环替代一下执行起来比较耗时的代码
        for(int i=0;i<100;i++){
            System.out.println(getName()+"-----------"+i);
        }
    }
}

2.3 线程调度

  • 我们现在大部分计算机只有一个cpu,cpu在一个时间点上只能执行一条指令,线程只有得到了cpu的执行权,才能执行。那么在java中是如何对线程进行调用的呢?
  • 线程有2种调用模式
    分时调用模式:所有线程轮流使用cpu的执行权。平均分配每个线程占用cpu的时间。
    抢占式调用模式:优先让优先级高的线程使用cpu,如果优先级一样,那么就随机选择一个执行。 优先级高的线程获取cpu的概率大一些。
  • java使用的是抢占式调用模式
  • 默认优先级:5    优先级范围:1-10
  • 如何设置和获取优先级?有没有默认的优先级?优先级的范围?
    int getPriority() 返回此线程的优先级。
    void setPriority(int newPriority) 更改此线程的优先级。
package com.momo.demo;

import com.momo.thread.MyThread3;
import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

/*
* 默认优先级:5
* 优先级范围:1-10
* */
public class Demo4 {
    public static void main(String[] args) {
        MyThread3 mt1 = new MyThread3("哈尼克孜");
        MyThread3 mt2 = new MyThread3("迪丽热巴");
        MyThread3 mt3 = new MyThread3("古力娜扎");

       /* System.out.println(mt1.getPriority());
        System.out.println(mt2.getPriority());
        System.out.println(mt3.getPriority());*/

       //IllegalArgumentException 非法参数异常
       // mt1.setPriority(1000);
       /* System.out.println(Thread.MIN_PRIORITY);
        System.out.println(Thread.MAX_PRIORITY);*/

       mt1.setPriority(1);
       mt3.setPriority(10);

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

2.4 线程控制

void interrupt() 线程终止
void join() 线程加入
void setDaemon(boolean on) 后台线程 守护线程
static void sleep(long millis) 线程休眠
static void yield() 线程礼让

package com.momo.demo;

import com.momo.thread.ThreadSleep;

/*
* 线程休眠
* */
public class Demo5 {
    public static void main(String[] args) {
        ThreadSleep t1 = new ThreadSleep();
        ThreadSleep t2 = new ThreadSleep();
        ThreadSleep t3 = new ThreadSleep();

        t1.setName("刘备");
        t2.setName("关羽");
        t3.setName("张飞");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.momo.thread;

import java.util.Date;

public class ThreadSleep extends Thread {
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(getName()+"---"+new Date());
            //睡觉
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

package com.momo.demo;

import com.momo.thread.ThreadJoin;
import com.momo.thread.ThreadSleep;

/*
* 线程加入
* */
public class Demo6 {
    public static void main(String[] args) {
        ThreadJoin t1 = new ThreadJoin();
        ThreadJoin t2 = new ThreadJoin();
        ThreadJoin t3 = new ThreadJoin();

        t1.setName("刘备");
        t2.setName("关羽");
        t3.setName("张飞");

        t1.start();
        try {
            t1.join();//等待t1线程结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
        t3.start();
    }
}
package com.momo.thread;

import java.util.Date;

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

package com.momo.demo;

import com.momo.thread.ThreadJoin;
import com.momo.thread.ThreadYield;

/*
* 线程礼让
*   暂停当前正在执行的线程,执行其他现场。
*   让多个线程的执行变得和谐一点,但是不能保证达到一人一次的效果。
* */
public class Demo7 {
    public static void main(String[] args) {
        ThreadYield t1 = new ThreadYield();
        ThreadYield t2 = new ThreadYield();
        ThreadYield t3 = new ThreadYield();

        t1.setName("刘备");
        t2.setName("关羽");
        t3.setName("张飞");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.momo.thread;

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

package com.momo.demo;

import com.momo.thread.ThreadDaemon;
import com.momo.thread.ThreadYield;

/*
* 守护线程
*   lol  守护水晶
*   如果当前运行的线程都是守护线程,java虚拟机退出。
* */
public class Demo8 {
    public static void main(String[] args) {

        ThreadDaemon t2 = new ThreadDaemon();
        ThreadDaemon t3 = new ThreadDaemon();

        t2.setName("关羽");
        t3.setName("张飞");

        t2.setDaemon(true);
        t3.setDaemon(true);

        t2.start();
        t3.start();

        Thread.currentThread().setName("刘备");
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
    }
}
package com.momo.thread;

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

package com.momo.demo;

import com.momo.thread.ThreadStop;

/*
* 线程终止
* */
public class Demo9 {
    public static void main(String[] args) {
        ThreadStop ts = new ThreadStop();
        ts.start();

        try {
            Thread.sleep(5000);
            ts.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.momo.thread;

import java.util.Date;

public class ThreadStop extends Thread {
    @Override
    public void run() {
        System.out.println(getName()+":开始执行---"+new Date());
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("线程终止");
        }
        System.out.println("结束:"+new Date());
    }
}

2.5 线程的生命周期

  • 新建:创建对象
  • 就绪:调用start方法,有了执行资格,没有执行权。抢夺cpu的执行权。
  • 运行:抢到了cpu的执行权。有执行资格,有执行权。
     阻塞:由于一些操作会让线程进入该状态。没有了执行资格,也没有了执行权。
    又会因为一些其他操作,可以让阻塞的线程再次进入就绪状态。
  • 死亡:线程对象变成垃圾,等待回收。

在这里插入图片描述

3 多线程的实现方式二:实现Runnable接口

3.1 实现Runnable接口

  • 步骤:
    自定义类MyRunnable实现Runnable接口
    重写run方法
    创建MrRunnable对象
    创建Thread类对象,把自定义对象作为参数传递给Thread
    启动线程
package com.momo.thread;

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            //因为我们现在是实现接口,接口中没有get方法,所以不能直接使用,
            //但是我们可以间接使用。
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}
package com.momo.demo;

import com.momo.thread.MyRunnable;

/*
* 多线程实现方式二
*
*       Thread(Runnable target)
        分配一个新的 Thread对象。
        Thread(Runnable target, String name)
        分配一个新的 Thread对象。
* */
public class Demo10 {
    public static void main(String[] args) {
        MyRunnable m = new MyRunnable();

       /* Thread t1 = new Thread(m);
        t1.setName("默默");*/

        Thread t1 = new Thread(m,"默默");
        Thread t2 = new Thread(m,"小宝");

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

3.2 方式二优点

  • 方式一: class My extends Thread{}
    my1 = new My();
    my2 = new My();

  • 方式二: class My implements Runnable{}
    m = new My();
    Thread t1 = new Thread(m);
    Thread t2 = new Thread(m);

  • 方式二可以避免java单继承的局限性。

  • 方式二更加适合多个线程处理同一个资源的情况。
    把线程程序和数据有效的分离开了。较好的体现了面向对象的设计思想。

3.3 案例

  • 最近某电影院在上映 神龙 电影,假设一共有100张票。影院有3个售票窗口。
    设计一个程序模拟电影院买票。

  • 分析:
    有2种实现方式:

      1. 继承Thread类
      2. 实现Runnable接口
    
package com.momo.demo;

import com.momo.thread.MyMovie;

/*
* 模拟卖票方式一实现
* */
public class Demo11 {
    public static void main(String[] args) {
      /*  System.out.println(MyMovie.tickets);
        MyMovie.tickets = 1000;
        System.out.println(MyMovie.tickets);*/


        //创建3个对象模拟三个窗口
        MyMovie m1 = new MyMovie();
        MyMovie m2 = new MyMovie();
        MyMovie m3 = new MyMovie();

        m1.setName("窗口一");
        m2.setName("窗口二");
        m3.setName("窗口三");

        m1.start();
        m2.start();
        m3.start();
    }
}
package com.momo.thread;
/*
* 根据简单分析,写完了代码,运行出问题了:
*   100张票被卖成了300张。 相同的票出现了多次
*
*
* */
public class MyMovie extends Thread {
    //定义总票数
   // int tickets = 100;
    //为了让多个独享共享这个数据,应该加上static
   // public static int tickets = 100;
    //为了保证这个数据的安全 加上private
    private static int tickets = 100;

    @Override
    public void run() {
        //定义总票数
       // int tickets = 100;
        /*
        * 每个线程进来都会走这里,那么相当于每个线程卖的都是自己的100张票
        * 所以这个不能定义到里面,应该定义到方法外面。
        * */
        while (tickets>0){
            System.out.println(getName()+":出售了第:"+tickets+" 张票。");
            tickets--;
        }
       /* //模拟卖票(使用死循环来表示即使票卖完了,窗口还会正常开启)
        while (true){
            if(tickets>0){
                System.out.println(getName()+":出售了第:"+tickets+" 张票。");
                tickets--;
            }
        }*/
    }
}

package com.momo.demo;

import com.momo.thread.MyMovie2;

/*
* 模拟卖票方式二
* */
public class Demo12 {
    public static void main(String[] args) {
        MyMovie2 m = new MyMovie2();

        Thread t1 = new Thread(m,"窗口一");
        Thread t2 = new Thread(m,"窗口二");
        Thread t3 = new Thread(m,"窗口三");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.momo.thread;

public class MyMovie2 implements Runnable{
    //定义总票数
    private int tickets = 100;

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

3.4 我们刚才的代码其实还有问题没有解决?

  • 现实生活种卖票的时候,要通过网络卖票。网络不可能做到实时传输,总会存在延时的情况。
    所以我把刚才的代码稍微改进一下,加入一点点的延时效果。让这个问题更加明显一点。
package com.momo.demo;

import com.momo.thread.MyMovie2;
import com.momo.thread.MyMovie3;

/*
* 卖票案例,问题分析。
* 加入网络延时效果后,问题就更加明显了
*   相同的票被卖了多次
*       因为cpu的每次操作都是原子性操作。
*   出现了0票和负数票
*       因为线程的随机性和延时导致的
* */
public class Demo13 {
    public static void main(String[] args) {
        MyMovie3 m = new MyMovie3();

        Thread t1 = new Thread(m,"窗口一");
        Thread t2 = new Thread(m,"窗口二");
        Thread t3 = new Thread(m,"窗口三");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.momo.thread;

public class MyMovie3 implements Runnable{
    //定义总票数
    private int tickets = 100;  //1

    @Override
    public void run() {
        while (true){
            if(tickets>0) {
                //t1  t2  t3
                //为了模拟网络延时效果,在这里稍微休息一下。
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }                                                       //t1==1   --  0      t2==0     t3==-1
                System.out.println(Thread.currentThread().getName() + ":出售了第:" + (tickets--) + " 张票。");
                /*
                * 理想状态:
                *   窗口一 第100
                *   窗口二 第99
                *   ....
                * 但是cpu的每一次操作都是一个原子性(最简单最基本)操作
                * 先输出后--
                *
                * t1 出售了第100张票
                * 还没有来得及--
                * 结果被t2抢到了cpu的执行权
                * 这个时候tickets还是100
                * .....
                * */
               // tickets--;
            }
        }
    }
}

3.5 针对问题的解决方案

  • 相同的票被卖了多次
    因为cpu的每次操作都是原子性操作。

  • 出现了0票和负数票
    因为线程的随机性和延时导致的

  • 我们把这种问题也称为 线程安全问题。

  • 线程安全问题一旦出现对软件的影响是非常大的。

  • 想为什么出现问题?这个也就是我们以后判断我们的代码会不会出现问题的标准。
    a:是否多线程环境
    b:是否有共享数据
    c:是否有多条语句操作共享数据

  • 如何解决线程安全问题?
    a和b的问题没办法。只能在c上想办法
    所有的问题都是因为一个线程还没有执行完代码就被其他线程抢去了。
    基本思路,一次只能让一个线程进来执行,只有当他执行完成后,其他线程才能进来。
    也就是把多条语句操作共享数据的代码包成一个整体,一次只让一个进来。

同步机制,同步代码块:我们使用同步代码块就可以把他们包成一个整体。
synchronized (对象){需要被同步的代码}
  解决线程安全问题的根本就在这个对象上。
  这个对象就好比是一把锁。

  • 注意:多个线程必须使用的是同一个对象。
    同步的代码:多条语句操作共享数据的代码
package com.momo.demo;

import com.momo.thread.MyMovie2;
import com.momo.thread.MyMovie3;

/*
* 卖票案例,问题分析。
* 加入网络延时效果后,问题就更加明显了
*   相同的票被卖了多次
*       因为cpu的每次操作都是原子性操作。
*   出现了0票和负数票
*       因为线程的随机性和延时导致的
* */
public class Demo13 {
    public static void main(String[] args) {
        MyMovie3 m = new MyMovie3();

        Thread t1 = new Thread(m,"窗口一");
        Thread t2 = new Thread(m,"窗口二");
        Thread t3 = new Thread(m,"窗口三");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.momo.thread;

public class MyMovie3 implements Runnable{
    //定义总票数
    private int tickets = 100;  //1
    //定义一个对象
   // private Stu s = new Stu();
    private Object o = new Object();
    @Override
    public void run() {
        while (true){
           // synchronized (new Object()) {
           // t2 进不去
            synchronized (o) {
                //t1
                if (tickets > 0) {
                    //t1  t2  t3
                    //为了模拟网络延时效果,在这里稍微休息一下。
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }                                                       //t1==1   --  0      t2==0     t3==-1
                    System.out.println(Thread.currentThread().getName() + ":出售了第:" + (tickets--) + " 张票。");
                    /*
                     * 理想状态:
                     *   窗口一 第100
                     *   窗口二 第99
                     *   ....
                     * 但是cpu的每一次操作都是一个原子性(最简单最基本)操作
                     * 先输出后--
                     *
                     * t1 出售了第100张票
                     * 还没有来得及--
                     * 结果被t2抢到了cpu的执行权
                     * 这个时候tickets还是100
                     * .....
                     * */
                    // tickets--;
                }
            }

        }
    }
}

4 解决线程安全问题

4.1 解决线程安全问题方式一:同步代码快

  • 同步代码块
    格式:synchronized (对象) {要同步的代码}

  • 注意:
    对象:可以是任意对象。
    解决线程安全问题的根本就在这个对象上。
    这个对象就好比是一把锁。
    注意:多个线程必须使用的是同一个对象。
    同步的代码:多条语句操作共享数据的代码

  • 比如:
    公共场所上厕所

  • 同步前提: 多线程

  • 同步的好处: 可以解决线程安全问题

  • 同步的弊端: 比如线程比较多,每个线程都要判断锁,就会降低程序的执行效率。

package com.momo.demo;
import com.momo.thread.MyMovie4;

/*
* 解决线程安全问题方式二
*  同步方法:把同步关键字加到方法上  public synchronized void fun()
*  注意:同步方法的锁对象是 this
*
* 静态同步方法: public static synchronized void fun()
* 注意:锁对象是  类的字节码文件对象(反射)
* */
public class Demo14 {
    public static void main(String[] args) {
        MyMovie4 m = new MyMovie4();

        Thread t1 = new Thread(m,"窗口一");
        Thread t2 = new Thread(m,"窗口二");
        Thread t3 = new Thread(m,"窗口三");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.momo.thread;
//class  是 Class类 的一个对象
public class MyMovie4 implements Runnable{
    //定义总票数
    //private int tickets = 100;
    private static int tickets = 100;
    //锁对象
    private Object o = new Object();
    private int i = 0;
    @Override
    public void run() {
        while (true){
            if(i%2==0){
              /*  synchronized (o) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }                                                       //t1==1   --  0      t2==0     t3==-1
                        System.out.println(Thread.currentThread().getName() + ":出售了第:" + (tickets--) + " 张票。");
                    }
                }*/
                fun();
            }else{
               /* synchronized (o) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }                                                       //t1==1   --  0      t2==0     t3==-1
                        System.out.println(Thread.currentThread().getName() + ":出售了第:" + (tickets--) + " 张票。");
                    }
                }*/
                fun();
            }
            i++;
        }
    }
    //如果一个方法一进去里面的代码就都被同步了,那么其实我们可以把这个同步写到方法上
    // 就变成了一个同步方法
   /* public void fun(){
        synchronized (o) {
            if (tickets > 0) {
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }                                                       //t1==1   --  0      t2==0     t3==-1
                System.out.println(Thread.currentThread().getName() + ":出售了第:" + (tickets--) + " 张票。");
            }
        }
    }*/

    //同步方法
   /* public synchronized void fun(){
        if (tickets > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":出售了第:" + (tickets--) + " 张票。");
        }
    }*/

   //静态同步方法
    public static synchronized void fun(){
        if (tickets > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":出售了第:" + (tickets--) + " 张票。");
        }
    }
}

4.2 解决线程安全问题方式二:同步方法

  • 同步方法
      把同步关键字加到方法上 public synchronized void fun()
      注意:同步方法的锁对象是 this

静态同步方法: public static synchronized void fun()
  注意:锁对象是 类的字节码文件对象(反射)

  • 我们到底该使用那种方式?
    如果锁对象是this,就可以使用同步方法
    否则能用同步代码块就用同步代码块。

5 Lock锁

5.1 引入原因

同步机制可以解决线程安全问题。但是它是在哪里加的锁,最后又是在哪里释放的锁,看不出来。为了能够更加清楚的表示加锁和释放锁,后来java就提供了一个新的锁对象—Lock锁

5.2 Lock 接口

void lock() 加锁
void unlock()  释放锁
  • 实现类:ReentrantLock
package com.momo.demo;

import com.momo.domain.MyMoive;

//Lock 锁的使用
public class Demo {
    public static void main(String[] args) {
        MyMoive m = new MyMoive();

        Thread t1 = new Thread(m,"小美");
        Thread t2 = new Thread(m,"小甜");
        Thread t3 = new Thread(m,"小倩");

        t1.start();
        t2.start();
        t3.start();
    }
}
package com.momo.domain;

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

public class MyMoive implements Runnable {
    private int tickets = 100;
    private Lock lock = new ReentrantLock();

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

6 同步解决线程安全问题的注意事项

6.1 同步的弊端 效率低

6.2 注意死锁问题

指的是2个或者2个以上的线程在执行的过程种,因为争夺资源产生的一种相互等待的现象。
主要是因为同步嵌套导致的。

package com.momo.demo;

import com.momo.domain.MyThread;
/*
* 死锁问题
* */
public class Demo1 {
    public static void main(String[] args) {
        MyThread m1 = new MyThread(true);
        MyThread m2 = new MyThread(false);

        m1.start();
        m2.start();
    }
}
package com.momo.domain;

public class MyThread extends Thread {
    private boolean boo;
    public MyThread(boolean boo){
        this.boo = boo;
    }
    @Override
    public void run() {
        if(boo){
            synchronized (MyLock.a){
                System.out.println("ifaaaaaaaa");
                //t1
                synchronized (MyLock.b){
                    System.out.println("ifbbbbbbbb");
                }
            }
        }else{
            synchronized (MyLock.b){
                System.out.println("elsebbbbbbb");
                //t2
                synchronized (MyLock.a){
                    System.out.println("elseaaaaaaaaaaaaa");
                }
            }
        }
    }
}
package com.momo.domain;

public class MyLock {
    //创建2个锁对象
    public static final Object a = new Object();
    public static final Object b = new Object();
}

7 线程之间的通信

7.1 针对同一个资源的操作有不同种类的线程。

比如:有买票的,也会有退票的。 有卖包子的,有买包子的。

7.2 代码举例

  • 设置一个生产者线程和一个消费者线程 操作 包子
    资源类:包子
    生产包子类:set
    消费包子类:get
    测试类:
package com.momo.demo;

import com.momo.domain.Bun;
import com.momo.domain.GetBun;
import com.momo.domain.SetBun;

/*
* 我们根据简单分析写代码,结果出现问题:
* 一:发现每次消费的都是null----0   为什么?
*   因为我们在不同的线程中都创建了对象,他们各自操作的都是自己的那个对象。
*   应该是生产和消费操作的是同一个对象。
*   所以这个对象应该是在外部创建,作为参数分别传递给生产和消费线程。
* */
public class Demo2 {
    public static void main(String[] args) {
        //创建资源
        Bun b = new Bun();

        //创建生产者和消费者
        SetBun sb = new SetBun(b);
        GetBun gb = new GetBun(b);

        //创建线程
        Thread t1 = new Thread(sb,"生产者");
        Thread t2 = new Thread(gb,"消费者");

        t1.start();
        t2.start();
    }
}
package com.momo.domain;
//生产者
public class SetBun implements Runnable{
    private Bun b;
    public SetBun(Bun b){
        this.b = b;
    }
    @Override
    public void run() {
        //Bun b = new Bun();
        b.setName("肉包子");
        b.setPrice(2);
        System.out.println(Thread.currentThread().getName()+":生产了一个:"+b.getName()+",价格是:"+b.getPrice());
    }
}
package com.momo.domain;
//消费者
public class GetBun implements Runnable{
    private Bun b;
    public GetBun(Bun b){
        this.b = b;
    }
    @Override
    public void run() {
       // Bun b = new Bun();
        System.out.println(Thread.currentThread().getName()+":消费了一个:"+b.getName()+"---"+b.getPrice());
    }
}
package com.momo.domain;
//资源类   包子
public class Bun {
    private String name;
    private int price;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

在这里插入图片描述


  • 代码改进
package com.momo.demo;

import com.momo.domain.Bun;
import com.momo.domain.GetBun;
import com.momo.domain.SetBun;

/*
* 我们根据简单分析写代码,结果出现问题:
* 一:发现每次消费的都是null----0   为什么?
*   因为我们在不同的线程中都创建了对象,他们各自操作的都是自己的那个对象。
*   应该是生产和消费操作的是同一个对象。
*   所以这个对象应该是在外部创建,作为参数分别传递给生产和消费线程。
*
* 为了让效果更加明显,加入循环和判断
*    同一个数据被消费了多次   为什么?
*       因为cpu的一点点时间就足够代码执行很多遍
*    出现了数据不匹配    为什么?
*       因为线程运行的随机性
*    也就是说我们的代码出现了线程安全问题。
*    标准:
*       是否多线程
*       是否有共享数据
*       是否有多条语句操作共享数据
*    使用同步机制解决安全问题
*       注意:不同种类的线程都要加锁
*            锁对象必须是同一个
*
*  通过加锁安全问题解决了,但是一个数据被消费了多次还没有解决。
*   应该是生产者生产一个,消费者消费一个,然后在生产,在消费。
*     生产一个肉  消费一个肉   生产一个菜  消费一个菜
*  看图
*   使用java给我们提供的等待唤醒机制。
*
* 等待唤醒机制:Object类中几个方法
*   void notify()
    唤醒正在等待对象监视器的单个线程。
    void notifyAll()
    唤醒正在等待对象监视器的所有线程
    void  wait()
    导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
    void wait(long timeout)
    导致当前线程等待,直到另一个线程调用 notify()方法或该对象的 notifyAll()方法,或者指定的时间已过。
    void wait(long timeout, int nanos)
    导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法,或者某些其他线程中断当前线程,或一定量的实时时间。
*
* */
public class Demo2 {
    public static void main(String[] args) {
        //创建资源
        Bun b = new Bun();

        //创建生产者和消费者
        SetBun sb = new SetBun(b);
        GetBun gb = new GetBun(b);

        //创建线程
        Thread t1 = new Thread(sb,"生产者");
        Thread t2 = new Thread(gb,"消费者");

        t1.start();
        t2.start();
    }
}
package com.momo.domain;
//生产者
public class SetBun implements Runnable{
    private Bun b;
    private int i = 0;
    public SetBun(Bun b){
        this.b = b;
    }
    @Override
    public void run() {
        while (true){
            //Bun b = new Bun();
            synchronized (b) {
                if (i % 2 == 0) {
                    b.setName("肉包子");
                    b.setPrice(2);
                } else {
                    b.setName("菜包子");
                    b.setPrice(1);
                }
                i++;
                 System.out.println(Thread.currentThread().getName()+":生产了一个:"+b.getName()+",价格是:"+b.getPrice());
            }
        }
    }
}
package com.momo.domain;
//消费者
public class GetBun implements Runnable{
    private Bun b;
    public GetBun(Bun b){
        this.b = b;
    }
    @Override
    public void run() {
       // Bun b = new Bun();
        while (true) {
            synchronized (b) {
                System.out.println(Thread.currentThread().getName() + ":消费了一个:" + b.getName() + "---" + b.getPrice());
            }
        }
    }
}

package com.momo.demo;

import com.momo.domain.Bun;
import com.momo.domain.GetBun;
import com.momo.domain.SetBun;

/*
* 我们根据简单分析写代码,结果出现问题:
* 一:发现每次消费的都是null----0   为什么?
*   因为我们在不同的线程中都创建了对象,他们各自操作的都是自己的那个对象。
*   应该是生产和消费操作的是同一个对象。
*   所以这个对象应该是在外部创建,作为参数分别传递给生产和消费线程。
*
* 为了让效果更加明显,加入循环和判断
*    同一个数据被消费了多次   为什么?
*       因为cpu的一点点时间就足够代码执行很多遍
*    出现了数据不匹配    为什么?
*       因为线程运行的随机性
*    也就是说我们的代码出现了线程安全问题。
*    标准:
*       是否多线程
*       是否有共享数据
*       是否有多条语句操作共享数据
*    使用同步机制解决安全问题
*       注意:不同种类的线程都要加锁
*            锁对象必须是同一个
*
*  通过加锁安全问题解决了,但是一个数据被消费了多次还没有解决。
*   应该是生产者生产一个,消费者消费一个,然后在生产,在消费。
*     生产一个肉  消费一个肉   生产一个菜  消费一个菜
*  看图
*   使用java给我们提供的等待唤醒机制。
*
* 等待唤醒机制:Object类中几个方法
*   void notify()
    唤醒正在等待对象监视器的单个线程。
    void notifyAll()
    唤醒正在等待对象监视器的所有线程
    void  wait()
    导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
    void wait(long timeout)
    导致当前线程等待,直到另一个线程调用 notify()方法或该对象的 notifyAll()方法,或者指定的时间已过。
    void wait(long timeout, int nanos)
    导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法,或者某些其他线程中断当前线程,或一定量的实时时间。

*   为什么这几个方法是定义在Object类中的?不是Thread类中?
*       因为这些方法的调用是通过锁对象来掉用的。
*       而我们同步代码块的锁对象可以是任意对象。
*       所以这些方法都定义在Object中。
* */
public class Demo2 {
    public static void main(String[] args) {
        //创建资源
        Bun b = new Bun();

        //创建生产者和消费者
        SetBun sb = new SetBun(b);
        GetBun gb = new GetBun(b);

        //创建线程
        Thread t1 = new Thread(sb,"生产者");
        Thread t2 = new Thread(gb,"消费者");

        t1.start();
        t2.start();
    }
}
package com.momo.domain;
//生产者
public class SetBun implements Runnable{
    private Bun b;
    private int i = 0;
    public SetBun(Bun b){
        this.b = b;
    }
    @Override
    public void run() {
        while (true){
            //Bun b = new Bun();
            synchronized (b) {
                //判断有没有数据
                if(b.getBoo()){
                    try {
                        b.wait();//如果有数据就等,释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                if (i % 2 == 0) {
                    b.setName("肉包子");
                    b.setPrice(2);
                } else {
                    b.setName("菜包子");
                    b.setPrice(1);
                }
                i++;
                 System.out.println(Thread.currentThread().getName()+":生产了一个:"+b.getName()+",价格是:"+b.getPrice());

                //通知消费者
                b.setBoo(true);
                b.notify();//唤醒t2  唤醒之后不是说它就马上能执行,还是需要抢夺cpu的执行权。
            }
        }
    }
}
package com.momo.domain;
//消费者
public class GetBun implements Runnable{
    private Bun b;
    public GetBun(Bun b){
        this.b = b;
    }
    @Override
    public void run() {
       // Bun b = new Bun();
        while (true) {
            synchronized (b) {
                if(!b.getBoo()){
                    try {
                        b.wait();//如果没有数据,就等待。释放锁。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + ":消费了一个:" + b.getName() + "---" + b.getPrice());
                //通知生产者
                b.setBoo(false);
                b.notify();//唤醒t1。
            }
        }
    }
}
package com.momo.domain;
//资源类   包子
public class Bun {
    private String name;
    private int price;
    private boolean boo;//默认值false,表示没有数据,如果是真表示有数据

    public boolean getBoo() {
        return boo;
    }

    public void setBoo(boolean boo) {
        this.boo = boo;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }
}

7.3 线程状态的转换

在这里插入图片描述

  • 练习:改进我们早上写的生产者和消费者的代码
    把设置和获取的操作封装成功能,加同步
    就可以在设置和获取线程中调用方法即可。

8 线程组

8.1 概述

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

  • 线程组就是把多个线程放到一起。
  • 默认情况下所有的线程都是主线程组的。
    ThreadGroup getThreadGroup()
    返回此线程所属的线程组。
  • 也可以给线程分组
    Thread(ThreadGroup group, Runnable target)
    分配一个新的 Thread对象。
    Thread(ThreadGroup group, Runnable target, String name)
    分配一个新的 Thread对象,使其具有 target作为其运行对象,具有指定的 name作为其名称,属于 group引用的线程组。
    Thread(ThreadGroup group, String name)
    分配一个新的 Thread对象。

8.2 ThreadGroup

ThreadGroup(String name)
构造一个新的线程组。
ThreadGroup(ThreadGroup parent, String name)
创建一个新的线程组。

package com.momo.demo;

import com.momo.domain.MyRunnable;

/*
* 线程组
* */
public class Demo3 {
    public static void main(String[] args) {
       /* MyRunnable m = new MyRunnable();
        Thread t1 = new Thread(m,"小龙女");
        Thread t2 = new Thread(m,"杨过");

        ThreadGroup tg1 = t1.getThreadGroup();
        ThreadGroup tg2 = t2.getThreadGroup();
        System.out.println(tg1.getName());
        System.out.println(tg2.getName());
        //线程默认都属于main线程组
        System.out.println(Thread.currentThread().getThreadGroup().getName());*/
       /* t1.start();
        t2.start();*/

        System.out.println("-----------------");
        ThreadGroup tg = new ThreadGroup("天龙八部");
        MyRunnable m = new MyRunnable();
        Thread t1 = new Thread(tg,m,"乔峰");
        Thread t2 = new Thread(tg,m,"虚竹");
        Thread t3 = new Thread(tg,m,"段誉");

       /* System.out.println(t1.getThreadGroup().getName());
        System.out.println(t2.getThreadGroup().getName());
        System.out.println(t3.getThreadGroup().getName());*/

       tg.setDaemon(true);
    }
}
package com.momo.domain;

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

9 线程池

9.1 引入原因

程序启动一个新线程成本很高,因为它涉及到了和操作系统进行交互。
我们可以使用线程池来解决这个问题,提高性能。
尤其是当程序中需要大量的生命周期比较短的线程时,更应该考虑使用线程池。

  • 线程池中的每一个线程代码结束后,线程不会死亡,而是会再次回到线程池中变成空闲状态
    等待下一个对象来使用。

  • 从jdk5之后java就给我们提供了一个工厂类,使用工厂类可以获取线程池。
    Executors:
    static ExecutorService newCachedThreadPool() 创建一个具有缓存功能的线程池
    static ExecutorService newFixedThreadPool(int nThreads) 创建有指定数量线程的线程池
    static ExecutorService newSingleThreadExecutor() 创建一个只有一个线程的线程池

    ExecutorService这个对象就是线程池。
    可以执行Runnable 或者Callable 对象所表示的线程任务
    <T> Future<T> submit(Callable<T> task)
    提交值返回任务以执行,并返回代表任务待处理结果的Future。
    Future<?> submit(Runnable task)
    提交一个可运行的任务执行,并返回一个表示该任务的未来。

package com.momo.demo;

import com.momo.domain.MyRunnable;

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

/*
* 线程池
* 步骤:
*   创建一个线程池对象
*   执行Runnable或者Callable表示线程
*   可以结束
* */
public class Demo4 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(5);
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        pool.shutdown();
    }
}

9.2 多线程实现方式三

  • 实现Callable接口
  • 步骤和Runnable基本一样
package com.momo.demo;

import com.momo.domain.MyCallable;

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

/*
* Callable接口
* */
public class Demo5 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.submit(new MyCallable());
        pool.submit(new MyCallable());
        pool.shutdown();
    }
}
package com.momo.domain;

import java.util.concurrent.Callable;
/*
* Runnable和Callable的区别:
*   Callable有异常,有返回值
* */
public class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
        return null;
    }
}

package com.momo.demo;

import com.momo.domain.MyCallable;

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

/*
* Callable接口
* */
public class Demo5 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        Future<Integer> f1 = pool.submit(new MyCallable(100));
        Future<Integer> f2 = pool.submit(new MyCallable(200));
        //get()
        //等待计算完成,然后检索其结果。

        Integer i1 = f1.get();
        Integer i2 = f2.get();
        System.out.println(i1);
        System.out.println(i2);

        pool.shutdown();
    }
}
package com.momo.domain;

import java.util.concurrent.Callable;
/*
* Runnable和Callable的区别:
*   Callable有异常,有返回值
*   因为要依赖线程池,比较麻烦,所以一般不太用
* */
public class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num){
        this.num = num;
    }
    @Override
    public Integer call() throws Exception {
        int count = 0;
        for(int i=1;i<=num;i++){
            count += i;
            //System.out.println(Thread.currentThread().getName()+":"+i);
        }
        return count;
    }
}

package com.momo.demo;
/*
* 匿名内部类方式
* */
public class Demo6 {
    public static void main(String[] args) {
       /* //继承Thread类
        new Thread(){
            public void run(){
                for(int i=0;i<10;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }.start();

        System.out.println("----------------");
        //实现Runnable
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }){}.start();
*/
        System.out.println("----------------");
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("小宝:"+i);
                }
            }
        }){
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    System.out.println("默默:"+i);
                }
            }
        }.start();
    }
}

10 定时器

10.1 概述

这个工具也是一个使用比较广泛的线程工具。可以调用多个任务以后台线程的方式执行。

  • 在java中,可以使用Timer和TimerTask来实现。

10.2 Timer

线程调度任务以供将来在后台线程中执行的功能。 任务可以安排一次执行,或定期重复执行。

  • 构造方法
    Timer()
    创建一个新的计时器。
    Timer(String name)
    创建一个新的定时器,其相关线程具有指定的名称。
  • 成员方法
    void schedule(TimerTask task, long delay)
    在指定的延迟之后安排指定的任务执行。
    void schedule(TimerTask task, long delay, long period)
    在指定 的延迟之后开始 ,重新执行 固定延迟执行的指定任务。
    void cancel()
    终止此计时器,丢弃任何当前计划的任务。

10.3 TimerTask

  • 可以由计时器进行一次性或重复执行的任务。
  • 成员方法
    abstract void run()
    该定时器任务要执行的操作。
    boolean cancel()
    取消此计时器任务。
package com.momo.demo;

import com.momo.domain.MyTask;

import java.util.Timer;

/*
* 定时器
* */
public class Demo7 {
    public static void main(String[] args) {
        //创建定时器对象
        Timer t = new Timer();
        //调用方法执行任务
        //t.schedule(new MyTask(),3000);
        t.schedule(new MyTask(t),3000);
    }
}
package com.momo.domain;

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

public class MyTask extends TimerTask {
    private Timer t;
    public MyTask(Timer t){
        this.t = t;
    }
    @Override
    public void run() {
        System.out.println("王炸");
        t.cancel();
    }
}

package com.momo.demo;

import com.momo.domain.MyTask;

import java.util.Timer;

/*
* 定时器
* */
public class Demo7 {
    public static void main(String[] args) {
        //创建定时器对象
        Timer t = new Timer();
        //调用方法执行任务
        //t.schedule(new MyTask(),3000);
        t.schedule(new MyTask(),3000,3000);
    }
}
package com.momo.domain;

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

public class MyTask extends TimerTask {
    private Timer t;
    public MyTask(Timer t){
        this.t = t;
    }

    public MyTask(){

    }
    @Override
    public void run() {
        //System.out.println("王炸");
        System.out.println("来啊,点我。十连抽。。");
        //t.cancel();
    }
}

  • 练习:在指定的时间后删除指定的目录(注意别用有用的文件夹玩)

11 小结

  • 多线程的实现方式
    继承Thread类
    实现Runnable
    扩展一种 实现Callable接口 依赖线程池
  • 前2中的区别?
    避免java单继承带来的局限性
    更适合多个线程处理同一个资源的情况
  • 线程安全问题的解决方式?
    同步代码块
    同步方法
  • 启动线程的方法是? start
  • start和run的区别
  • sleep和wait的区别?
    sleep需要指定时间
    wait不需要指定,也可以指定。
  • 为什么wait,notify。。方法在Object中?
  • 线程的生命周期****
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值