目录
- 1、线程概述
- 1.1 什么是线程?
- 1.2 多线程应用场景
- 2、Java中线程的实现方式?
- 2.1 方式一、继承Thread
- 2.2 方式二、实现Runnable接口
- 2.3 两种方式的区别?
- 2.4 匿名内部类实现线程的两种方式
- 2.5 获取线程名字和设置名字
- 2.6 获取当前线程的对象
- 3、线程的其它方法?
- 3.1 线程休眠
- 3.2 守护线程
- 3.3 加入线程
- 3.4 线程让出
- 3.5 线程优先级
- 4、线程与同步
- 4.1 什么是同步
- 4.2 同步代码块
- 4.3 同步方法
- 4.4 线程锁使用总结
- 4.4 死锁
- 5、单例设计模式
- 5.1 什么是单例
- 5.2 单例实现的基本步骤
- 5.3 单例的多种写法
- 5.4 饿汉式和懒汉式的区别
- 5.5 Runtime类的使用
- 6、Timer定时器
- 7、线程间的通讯?
- 7.1 什么时候需要通信?
- 7.2 线程怎么通信?
- 7.3 案例:两个线程间的通讯
- 7.4 案例:三个线程间的通讯
- 7.5 线程通讯的一些疑问
- 7.6 JDK1.5新特性互斥锁
- 8、线程组
- 8.1 概述
- 8.2 创建线程对象
- 8.3 案例
- 9、线程池
- 9.1 线程池概述
- 9.2 Java的内置线程池
- 10、线程的五种状态?
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()方法可以传入参数也可以不传入参数,传入参数就是在参数的时间结束后等待,不传入参数就是直接等待。
7.6 JDK1.5新特性互斥锁
ReentrantLock介绍:
➢使用ReentrantLock
类也可以实现同步加锁
➢ReentrantLock
叫[互斥锁],使用lock()
和unlock()
方法进行同步
使用ReentrantLock类使用要点:
➢使用ReentrantLock
类的newCondition()
方法可以获取Condition对象
➢需要等待的时候使用Condition的await()
方法, 唤醒的时候用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、线程的五种状态?
- 新建(NEW):新创建了一个线程对象。
- 可运行(RUNNABLE):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
- 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
- 阻塞(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)状态
- 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。