Java多线程的使用和总结


1、线程概述

1.1 什么是线程?

  • 线程是程序执行的一条路径, 一个进程中可以包含多条线程
  • 一个应用程序可以理解成就是一个进程
  • 多线程并发执行可以提高程序的效率,可以同时完成多项工作

1.2 多线程应用场景

➢ VNC同时共享屏幕给多个电脑
➢ 迅雷开启多条线程一起下载
➢ QQ同时和多个人一起视频
➢ 服务器同时处理多个客户端请求

2、Java中线程的实现方式?

2.1 方式一、继承Thread

使用步骤:

  • 1.定义类继承Thread
  • 2.重写run方法
  • 3.把新线程要做的事写在run方法中
  • 4.创建线程对象
  • 5.开启新线程, 内部会自动执行run方法

**
代码:

public class Demo02 {
    public static void main(String[] args) {
        /*主线程,程序员不能创建,程序员只能创建子线程*/

        //1.创建子线程对象
        MyThread myThread1 = new MyThread();
        /**不能通过下面的方式来执行任务
         * 因为这种试的任务是在主线程执行的*/
        //myThread1.run();
        //2.正确的执行任务的方式,调用start,内部会开启新线程,调用run方法
        myThread1.start();
        //3.再创建子线程
        MyThread myThread2 = new MyThread();
        myThread2.start();
        //4.循环创建子线程
        for (int i = 0; i < 5; i++) {
            MyThread myThread = new MyThread();
            myThread.start();
        }

    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("银行信用卡还款短信任务..." + Thread.currentThread());
        System.out.println("线程名称:" + this.getName());
    }
}

2.2 方式二、实现Runnable接口

实现步骤:
1.定义类实现Runnable接口
2.实现run方法
3.把新线程要做的事写在run方法中
4.创建自定义的Runnable的子类对象,创建Thread对象传入Runnable的实例对象
5.调用start()开启新线程, 内部会自动调用Runnable的run()方法

在这里插入图片描述

代码:

public class Demo03 {
    public static void main(String[] args) {
        //1.创建runable对象
        BankTask bankTask = new BankTask();
        //2.创建Thread对象
        Thread thread = new Thread(bankTask);
        thread.start();
//        thread.run();

    }
}

class BankTask implements Runnable {

    @Override
    public void run() {
        System.out.println("银行储蓄卡自动结算利息任务..." + Thread.currentThread());
        //System.out.println("线程名称:" + this.getName());
        System.out.println("线程名称:" + Thread.currentThread().getName());
    }
}

2.3 两种方式的区别?

➢继承Thread : 由于子类重写了Thread类的run(), 当调用start()时直接找子类的run()方法
➢实现Runnable: 构造函数中传入了Runnable的引用, 有个成员变量记住了它, 调用run()方法时内部判断成员变量Runnable的引用是否为空。
①继承Thread

  • ➢好处:可以直接使用Thread类中的方法,代码简单
  • ➢弊端:如果已经有了父类,就不能用这种方法

②实现Runnable接口

  • ➢好处:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,代码更灵活
  • ➢弊端:不能直接使用Thread中的方法,需要先获取到线程对象后,才能得到Thread的方法,代码复杂

2.4 匿名内部类实现线程的两种方式

public class 匿名内部类 {
    public static void main(String[] args) {
        //匿名内部类实现线程的两种方式
		/*Thread t1 = new Thread(){
			@Override
			public void run() {
				System.out.println("任务1...." + Thread.currentThread());
			}
		};
		t1.start();*/
//        第一种
        new Thread(){
            @Override
            public void run() {
                System.out.println("任务1...." + Thread.currentThread());
            }
        }.start();
        /*Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
			  			}
		});
		t2.start();*/
//         第二种
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("任务2...." + Thread.currentThread());
            }
        }).start();
    }
}

2.5 获取线程名字和设置名字

➢通过Thread的getName()方法获取线程对象的名字。
➢通过setName(String)方法可以设置线程对象的名字。
➢通过构造函数可以传入String类型的名字。
➢每个线程系统都会默认分配个名字,主线程:main,子线程thread-0 …

public class 获取线程名字 {
    public static void main(String[] args) {
        //1.获取主线程对象
        Thread mainThread = Thread.currentThread();
        System.out.println("修改前的名字:"+ mainThread.getName());
        //2.重新设置主线程的名称
        mainThread.setName("主线程");
        System.out.println("修改后的名字:"+ mainThread.getName());
        //3.设置子线程的名称
        for (int i = 0; i < 5; i++) {
            MyThread1 myThread = new MyThread1("子线程"+i);
            myThread.start();
        }

    }
}
class MyThread1 extends Thread{
    //子线程的构造方法
    public  MyThread1(String name){
        super(name);
    }
    @Override
    public void run() {
        System.out.println("银行代发工资任务..." + Thread.currentThread());
    }
}

2.6 获取当前线程的对象

Thread.currentThread()方法用于获取当前线程对象
➢在不同的方法中,获取的线程对象名称是有可能不一样的
➢在main中获取的是主线程对象
➢在子线程的run方法中获取的是子线程对象

public class 获取线程对象 {
    public static void main(String[] args) {
        //获取当前线程的对象(掌握)
        Thread mainThread = Thread.currentThread();
        mainThread.setName("主线程");
        //打印主线程对象
        System.out.println(mainThread);
        //打印主线程对象类名
        System.out.println(mainThread.getClass());
        System.out.println("-------------------------");
        //开启子线程
        MyThread2 myThread = new MyThread2();
        myThread.start();

    }
}
class MyThread2 extends Thread{
    @Override
    public void run() {
        System.out.println("任务....");
        Thread subThread = new Thread();
        //打印子线程对象
        System.out.println(subThread);
        //打印子线程对象类名
        System.out.println(subThread.getClass().getName());
    }
}

3、线程的其它方法?

3.1 线程休眠

➢Thread.sleep(毫秒), 控制当前线程休眠若干毫秒
➢1秒= 1000毫秒
➢1秒 = 1000毫秒* 1000微妙 * 1000纳秒(1_000_000_000 )
主线程休眠和子线程休眠

public class 线程休眠 {
    public static void main(String[] args) throws InterruptedException {
        /*** 主线程休眠 */
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
            //休眠0.5秒,间隔输出i
            Thread.sleep(500);//主线程休眠,毫秒为单位
        }
        System.out.println("主线程休眠 finished!");
        /*** 子线程休眠:重写run()方法 */
        new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        System.out.println("子线程休眠 finished!");
    }
}

3.2 守护线程

setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
➢特点:男守护女,女的死,男的也不想活了

3.3 加入线程

join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int), 可以等待指定的毫秒之后继续

3.4 线程让出

➢yield() 让出cpu

3.5 线程优先级

setPriority()设置线程的优先级
➢默认优先级是5,最小优先级1,最高优先级10
➢可以设置2,3,4
➢Thread里面有静态常量
➢开发几乎不用,了解
在这里插入图片描述

4、线程与同步

4.1 什么是同步

➢同步就是加锁,不让其它人访问
➢synchronized指的就是同步的意思
什么情况下需要同步
➢当多线程并发, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步,否则会有线程安全问题.

4.2 同步代码块

➢使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块
➢多个同步代码块如果使用相同的锁对象, 那么他们就是同步的
➢使用同步锁时,应该尽是让锁的范围小点,才能提高性能
在这里插入图片描述

4.3 同步方法

➢使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
➢非静态同步方法的锁是:this
➢静态同步方法的锁是:字节码对象(xx.class)

经典案例:卖火车票
➢需求,有A\B\C\D4个窗口同时买票,只有100张票可买
➢多线程会有安全问题

public class 火车票问题 {
    //火车站卖票【问题】

    /**
     * 湖南到广州火车票:今天13:00 ,100张
     * 火车站有4个窗口在同时卖票,要保证一张票只能被卖一次
     * 
     * 搞4个线程表示4个窗口
     * 
     * 通过加锁可以解决被多次卖同一张票的问题
     * 
     * 使用同步代码块
     */


    public static void main(String[] args) {
        //创建卖票的任务
        TicketTask ticketTask = new TicketTask();
        //模拟窗口
        Thread t1 = new Thread(ticketTask);
        t1.setName("窗口A");
        Thread t2 = new Thread(ticketTask);
        t2.setName("窗口B");
        Thread t3 = new Thread(ticketTask);
        t3.setName("窗口C");
        Thread t4 = new Thread(ticketTask);
        t4.setName("窗口D");
        //开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();

    }
}

class TicketTask implements Runnable {
    int tickets = 100;

//    //第一种方式:使用同步方法
//    @Override
//    public synchronized void run() {
//        while (true) {
//            if (tickets <= 0) {
//                System.out.println("对不起,票已经卖完!");
//                break;
//            } else {
//                System.out.println(Thread.currentThread() + "恭喜您购票成功,票号:" + tickets);
//                tickets--;
//                //便于观察,休眠0.2秒
//                try {
//                    Thread.sleep(200);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        }
//    }
    //第二种方式:使用同步代码块
    @Override
    public void run() {
        // TODO Auto-generated method stub
         /*同步代码块括号里参数可以传任意对象
         this是一个锁对象,不同的一把锁,卖相同的票总是还是存在
        */
        //卖票
        while (true) {
            synchronized (String.class) {// 同步:加锁
                if (tickets <= 0) {
                    System.out.println("对不起,票已经卖完!");
                    break;
                } else {
                    System.out.println(Thread.currentThread() + "恭喜您购票成功,票号:" + tickets);
                    tickets--;
                }
            }
        }
    }
    //第三种方式:使用同步代码块
//    @Override
//    public void run() {
//        // TODO Auto-generated method stub
//        /**
//         * 同步代码块括号里参数可以传任意对象,开发中一般是使用this
//         */
//        synchronized (this) {
//            //卖票
//            while (true) {
//                if (tickets <= 0) {
//                    System.out.println("对不起,票已经卖完!");
//                    break;
//                } else {
//                    System.out.println(Thread.currentThread() + "恭喜您购票成功,票号:" + tickets);
//                    tickets--;
//                }
//            }
//        }
//    }
}

4.4 线程锁使用总结

 1.锁问题:
      	同步中,锁最好同一个对象,如果不是同一对象,还是会有线程安全问题
        锁:this,代表当前对象
        锁:如果 new 对象,就不是同一把锁
        锁:字节码对象 String.class,内存中,只有一个字节码对象
        开发中:一般都是this
     
    2.在方法内部声明synchronized的就是 “同步代码块”
     
    3.在声明方法的时候,添加 synchronized,就是同步方法
         》如果是非静态方法,锁就是this
         》如果是静态方法,锁就当前类的字节码对象
           //TicketTask.class
         public static synchronized void test1(){}
    4.同步使用的建议:
        同步加锁的时候,尽量让锁住的代码范围小一点,这样可以让其它线程等待时间少一点,性能高。
    5.加锁
     只能写在同步代码块中
     锁只对子线程有效

4.4 死锁

➢死锁就是大家都抱着锁,不释放

public class 线程死锁 {
    static String s1 = "筷子左";
    static String s2 = "筷子右";

    public static void main(String[] args) {
        //开启两个线程
        new Thread() {
            @Override
            public void run() {
                synchronized (s1) {
                    System.out.println("线程A 拿到" + s1 + " 等待" + s2);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2) {
                        System.out.println("线程A 拿到" + s2 + " 开吃");
                    }
                }
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                synchronized (s2) {
                    System.out.println("线程B 拿到" + s2 + " 等待" + s1);
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1) {
                        System.out.println("线程B 拿到" + s1 + " 开吃");
                    }
                }
            }
        }.start();
    }
}

5、单例设计模式

5.1 什么是单例

➢保证类在内存中只有一个对象。
➢对象是new出来的,因此也就是说在程序中只能new一次对象

5.2 单例实现的基本步骤

1.声明一个类,类中有一个静态属性,类型与类名相同
2.把空参构造方法声明为私有
3.在类中提供一个公共静态访问方法来返回该对象实例

5.3 单例的多种写法

写法一:饿汉式

class Singleton{
    //此处定义类变量实例并直接实例化,在类加载的时候就完成了实例化并保存在类中
	private static Singleton instance = new Singleton();
	//定义无参构造器,用于单例实例
	private Singleton(){}
    //定义公开方法,返回已创建的单例
	public static Singleton getInstance(){
		return instance;
	}
}

写法二:懒汉式

//先创建好实例
class Singleton{
    //定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取
	private static Singleton instance = null;
	//定义私有构造器,表示只在类内部使用,亦指单例的实例只能在单例类内部创建
	private Singleton(){}
	//定义一个公共的公开的方法来返回该类的实例,由于是懒汉式,需要在第一次使用时生成实例,所以为了线程安全,使用synchronized关键字来确保只会生成单例
	public static Singleton getInstance(){
		if(instance == null){
			instance = new Singleton();
		}
		return instance;
	}
}

写法三:另一种简单

class Singleton{
	public static final Singleton instance = new Singleton();
	private Singleton(){}
}

5.4 饿汉式和懒汉式的区别

➢饿汉式是空间换时间,懒汉式是时间换空间
➢在多线程访问时,饿汉式不会创建多个对象,而懒汉式有可能会创建多个对象
➢如果考虑线程安全问题,用饿汉式
➢如果不考虑线程安全问题,用懒汉式

5.5 Runtime类的使用

➢Runtime类是一个单例类
➢每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。通过 getRuntime 方法获取当前运行时间。
➢案例:自动关机
Runtime r = Runtime.getRuntime();
r.exec(“shutdown -s -t 300”);//300秒后关机
r.exec(“shutdown -a”); //取消关机

6、Timer定时器

Timer是一种定时工作工具,用于在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。


public class 定时器 {
    public static void main(String[] args) {
        //1、创建定时器timer1
        Timer timer1 = new Timer();
        //2、3秒后执行任务,执行完任务没有退出
        timer1.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务1"+ Thread.currentThread().getName());
            }
        }, 3000);


        //1、创建定时器timer2
        Timer timer2 = new Timer();
        //2、3秒后执行任务,间隔2秒再次执行,一直循环
        timer2.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println("任务2"+ Thread.currentThread().getName());
            }
        }, 3000, 2000);


        /**定时器的细节
         * 1.定时器在子线程中执行
         * 2.timer.cancel(); 取消定时器
         */
        Timer timer3 = new Timer();
        timer3.schedule(new TimerTask() {
            int count = 5;

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("任务3:" + count + "..." + Thread.currentThread().getName());
                count--;
                if (count == 0) {
                    //取消定时器
                    timer3.cancel();
                }
            }
        }, 1000, 2000);
        //timer.cancel();//主线程
    }
}

7、线程间的通讯?

7.1 什么时候需要通信?

多个线程并发执行时, 在默认情况下CPU是随机切换线程的,如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印。

7.2 线程怎么通信?

如果希望线程等待, 就调用wait()
如果希望唤醒等待的线程, 就调用notify();
注:notify是随机唤醒一个线程,notifyAll是唤醒所有线程。这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用,如果方法中没有同步锁,会有异常IllegalMonitorStateException

7.3 案例:两个线程间的通讯

public class 线程通信 {
    public static void main(String[] args) {
        //1.创建任务对象
        MyTask task = new MyTask();
        //2.开启两个线程执行2个任务
        new Thread() {
            public void run() {
                while (true) {
                    try {
                        task.task1();
                    } catch (InterruptedException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                while (true) {
                    try {
                        task.task2();
                    } catch (InterruptedException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

class MyTask {

    //标识 1:可以执行任务1
    //标识 2:可以执行任务2
    int flag = 1;

    public synchronized void task1() throws InterruptedException {
        if (flag != 1) {
            this.wait();//当前线程等待
        }

        System.out.println("1.银行信用卡自动还款任务...");
        flag = 2;
        this.notify();//唤醒其它线程

    }

    public synchronized void task2() throws InterruptedException {

        if (flag != 2) {
            this.wait();//线程等待
        }

        System.out.println("2.银行储蓄卡自动结算利息任务...");
        flag = 1;
        this.notify();//唤醒其它线程
    }
}

7.4 案例:三个线程间的通讯


public class 线程通信 {
    public static void main(String[] args) {
        //三个线程间的通讯
        MyTask task = new MyTask();
        new Thread() {
            public void run() {
                while (true) {
                    try {
                        task.task1();//执行任务一
                    } catch (InterruptedException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

            ;
        }.start();
        new Thread() {
            public void run() {
                while (true) {
                    try {
                        task.task2();//执行任务二
                    } catch (InterruptedException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

            ;
        }.start();
        new Thread() {
            public void run() {
                while (true) {
                    try {
                        task.task3();//执行任务三
                    } catch (InterruptedException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}
class MyTask {

    //标识 1:可以执行任务1
    //标识 2:可以执行任务2
    //标识 3:可以执行任务3
    int flag = 1;

    public synchronized void task1() throws InterruptedException {
        if (flag != 1) {
            this.wait();//当前线程等待
            //this.wait(timeout);
        }

        System.out.println("1.银行信用卡自动还款任务...");
        flag = 2;
        //this.notify();//唤醒随机线程
        this.notifyAll();//唤醒所有等待线程

    }

    public synchronized void task2() throws InterruptedException {

        if (flag != 2) {
            this.wait();//线程等待
        }

        System.out.println("2.银行储蓄卡自动结算利息任务...");
        flag = 3;
        //this.notify();//唤醒其它线程
        this.notifyAll();
        //Thread.sleep(millis);
    }

    public synchronized void task3() throws InterruptedException {
        if (flag != 3) {
            this.wait();//线程等待
        }

        System.out.println("3.银行短信提醒任务...");
        flag = 1;
        //this.notify();//唤醒其它线程
        this.notifyAll();
    }
}

7.5 线程通讯的一些疑问

1.在同步代码块中,用哪个对象锁,就用哪个对象调用wait()方法。
2.为什么wait()方法和notify()方法定义在Object这类中?

因为锁对象可以是任意对象,Object是所有的类的基类,所以wait()方法和notify()方法需要定义在Object这个类中
3.sleep方法和wait方法的区别?

  • sleep()方法必须传入参数,参数就是时间,时间到了自动醒来
  • wait()方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待
  • sleep()方法在同步函数或同步代码块中,不释放锁,睡着了也抱着锁睡。
  • wait()方法在同步函数或者同步代码块中,释放锁。

7.6 JDK1.5新特性互斥锁

ReentrantLock介绍:
➢使用ReentrantLock类也可以实现同步加锁
ReentrantLock叫[互斥锁],使用lock()unlock()方法进行同步
使用ReentrantLock类使用要点:
➢使用ReentrantLock类的newCondition()方法可以获取Condition对象
➢需要等待的时候使用Conditionawait()方法, 唤醒的时候用signal()方法
➢不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

互斥锁的使用步骤:

  • 1.创建互斥锁对象
  • 2.创建3个Condition
  • 3.加锁、解锁
  • 4.线程等待-Condition的await方法
  • 5.线程唤醒-Condition的signal方法

案例

public class 互斥锁 {
    public static void main(String[] args) {
        MyTask_ myTask_ = new MyTask_();
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        myTask_.task1();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        myTask_.task2();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        myTask_.task3();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

class MyTask_ {
    //创建互斥锁对象
    ReentrantLock rl = new ReentrantLock();
    //创建3个Condition
    Condition c1 = rl.newCondition();
    Condition c2 = rl.newCondition();
    Condition c3 = rl.newCondition();
    //标识 1:可以执行任务1,2:可以执行任务2, 3:可以执行任务3
    int flag = 1;

    public void task1() throws InterruptedException {
        rl.lock();//加锁
        if (flag != 1) {
            c1.await();//当前线程等待
        }
        System.out.println("1.银行信用卡自动还款任务...");
        flag = 2;
        //指定唤醒线程2
        c2.signal();
        rl.unlock();//解锁
    }

    public void task2() throws InterruptedException {
        rl.lock();//加锁
        if (flag != 2) {
            c2.await();//线程等待
        }
        System.out.println("2.银行储蓄卡自动结算利息任务...");
        flag = 3;

        //唤醒线程3
        c3.signal();
        rl.unlock();
    }

    public void task3() throws InterruptedException {
        rl.lock();//加锁
        if (flag != 3) {
            c3.await();//线程等待
        }

        System.out.println("3.银行短信提醒任务...");
        flag = 1;

        //唤醒线程1
        c1.signal();
        rl.unlock();
    }
}

8、线程组

8.1 概述

1.Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
2.默认情况下,所有的线程都属于主线程组。
3.public final ThreadGroup getThreadGroup() 通过线程对象获取他所属于的组。
4.public final String getName() 通过线程组对象获取组的名字。
5.我们也可以给线程设置分组ThreadGroup(String name) 创建线程组对象并给其赋值名字。

8.2 创建线程对象

Thread(ThreadGroup?group, Runnable?target, String?name)

8.3 案例

重点:

  • 1.如何获取一个线程所属的线程组
  • 2.如果在创建一个子线程时,设置它所属的线程组
public class 线程组 {
    public static void main(String[] args) {
        //主线程
        Thread mainThread = Thread.currentThread();
        /**
         * [main,5,main]
         * main:线程名称
         * 5:优先级
         * main:当前线程所属的组名
         */
        System.out.println("主线程:" + mainThread);

        //获取线程的“线程组”对象
        ThreadGroup tg1 = mainThread.getThreadGroup();
        System.out.println("tg1线程组:" + tg1.getName());

        //创建子线程
        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("线程A...");
            }
        };
        System.out.println("t1子线程所属的线程组:" + t1.getThreadGroup());

        //创建一个线程组
        ThreadGroup tg2 = new ThreadGroup("tg2");
        System.out.println("tg2线程组:" + tg2.getName());
        //创建子线程对象
        Thread t2 = new Thread(tg2, new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                System.out.println("线程B");
            }
        });
        System.out.println("t2子线程所属的线程组:" + t2.getThreadGroup());
    }
}

9、线程池

9.1 线程池概述

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

9.2 Java的内置线程池

1.JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:

public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()

2.这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法:

Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)

3.使用步骤:

1.创建线程池对象。
2.创建Runnable实例。
3.提交Runnable实例。
4.关闭线程池es.shutdown();

4.Runnable和Callable的区别?

Runnable的run方法没有返回值
Callable的call方法有返回值,一般返回值也没用

案例:

public class 线程池 {
    public static void main(String[] args) {
        //案例:5个线程完成10个洗车的任务
        //1.创建线程池
        //newFixedThreadPool(5);参数为线程池的大小
        ExecutorService es = Executors.newFixedThreadPool(5);
        //2.添加任务-方式一
        for (int i = 0; i < 10; i++) {
            es.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("洗车任务正在由 " + Thread.currentThread().getName() + " 完成 ");
                }
            });
        }

        //3.添加任务-方式二
/*        for(int i=0;i<10;i++){
            es.submit(new MyTask_0());
        }*/

    }
}
//添加任务-方式二所需要使用的MyTask_0类
/*class MyTask_0 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("洗车任务正在由 " + Thread.currentThread().getName() + " 完成 ");
        return 1;
    }

}*/

10、线程的五种状态?

  1. 新建(NEW):新创建了一个线程对象。

  2. 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。

  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。

  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。
    阻塞的情况分三种:

  • 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
  • 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lockpool)中。
  • 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态
  1. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值