Java-34 多线程
进程和线程:
- 进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源
- 线程:
是进程中的单个顺序控制流,或者说就是一个单独执行路径
一个进程如果只有一条执行路径,称之为单线程程序,一个进程如果有多条执行路径,称之为多线程程序。
线程是包含在进程中的。
并行和并发:
- 并发:指的是逻辑上同时发生,指在某一时间段内同时运行多个程序
- 并行:指的是物理上同时发生,指在某一时间点上同时运行多个程序
Java程序的运行原理:
java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。
在此之前的所有程序都是单线程的
注意:JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
创建线程的方式:
创建线程的第一种方式:继承Thread类
创建线程的第二种方式:实现Runnable接口
创建线程的第二种方式:实现Callable接口
继承Thread类:
/** * 为什么要重写run()方法呢? * 只有当需要被线程执行的时候,再把需要的代码加入到run()方法里 * */ public class MyThread1 extends Thread { @Override public void run() { //写我们自己逻辑代码 // System.out.println("数加学院,yyds"); //一般来说,被线程执行的代码都是比较耗时的,为了模拟耗时,我这里用循环模拟 for(int i =0;i<10;i++){ System.out.println(i); } } }
public class MyThreadDemo2 { public static void main(String[] args) { //创建子类对象 //每new一次就相当于创建一个线程 // MyThread1 myThread1 = new MyThread1(); //模拟多线程环境 //至少创建2个及以上的线程数 MyThread1 myThread1 = new MyThread1(); MyThread1 myThread2 = new MyThread1(); myThread1.start(); myThread2.start(); } }
注意:调用run()与调用start()的区别
run()的调用,仅仅是封装了被线程执行的代码,但是直接调用的话是普通的方法调用
start()方法的调用,首先单独启动了一个线程,然后再由JVM去调用该线程的run()方法
获取和设置线程名字:
- 通过构造方法给线程起名字:
Thread(String name) 分配一个新的 Thread对象。创建一个带参数的构造方法,参数传递线程名称,调用父类的带参构造方法,把线程名称传递给父类,让父类給子线程起一个名字
public class MyThread2 extends Thread{ public MyThread2() { } public MyThread2(String name) { super(name); } @Override public void run() { //获取线程的名称 String name = Thread.currentThread().getName(); System.out.println(name); } }
- 通过方法给线程起名字:
void setName(String name) 将此线程的名称更改为等于参数 name 。
//通过构造方法给线程起名字 // MyThread2 myThread2 = new MyThread2("小花"); // myThread2.start(); //void setName(String name) 将此线程的名称更改为等于参数 name 。 // MyThread2 myThread1 = new MyThread2(); // myThread1.setName("小瓜");
- 获取线程名字:
Thread类中提供了一个方法叫getName()
public static Thread currentThread()返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName());
多线程实现原理:
线程调度:
- 分时调度模型 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
设置和获取线程优先级
public final int getPriority()
public final void setPriority(int newPriority)
/* 获取线程的优先级 public final int getPriority()返回此线程的优先级。 设置线程的优先级 public final void setPriority(int newPriority)更改此线程的优先级 参数newPriority的范围为1-10之间 newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY 总结: 1、线程的默认优先级为5 2、线程优先级的范围是1-10 3、线程优先级高仅仅表示的是获取CPU时间片的几率会高一些,但是不是绝对一定会获取 */ public class MyPriorityThreadDemo { public static void main(String[] args) { MyPriorityThread t1 = new MyPriorityThread(); MyPriorityThread t2 = new MyPriorityThread(); //获取t1和t2的优先级 System.out.println(t1.getPriority());//5 System.out.println(t2.getPriority());//5 //IllegalArgumentException t1.setPriority(1); t2.setPriority(10); t1.start(); t2.start(); } }
线程控制:
线程休眠 public static void sleep(long millis)
public class MySleepThread extends Thread{ @Override public void run() { for(int i =0;i<100;i++){ System.out.println(getName()+":"+i); //加入休眠方法 //睡一秒钟,1秒=1000毫秒 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
线程加入 public final void join()
/* 加入线程: public final void join():其他线程等待这个线程死亡 注意事项: 在线程设置为加入线程之前,先将该线程变为就绪状态,也就是调用start()方法 */ 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(); try { t1.join(); } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); t3.start(); } }
线程礼让 public static void yield()
public class MySleepThread extends Thread{ @Override public void run() { for(int i =0;i<10;i++){ System.out.println(getName()+":"+i); System.out.println("--------------"); Thread.yield(); } } }
/* 礼让线程: public static void yield() 暂停当前正在执行的线程对象,并执行其他线程 它的作用是为了让多个线程之间更加和谐一点,并不能一定保证多个线程一人一次执行 */ 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("小草"); t1.start(); t2.start(); } }
后台线程 public final void setDaemon(boolean on)必须在线程启动前设置
Java中有两类线程:用户线程、守护线程(后台线程)
用户线程:我们在学习多线程之前所有程序代码,运行起来都是一个个的用户线程。
守护线程:所谓的守护线程,指的就是程序运行的时候在后台提供了一种通用服务的线程。比如说
垃圾回收线程,它就是一个称职的守护者,并且这种线程并不属于程序不可或缺的部分。所以
非守护线程结束的时候,程序也就终止了,同时会杀死进程种所有的守护线程。
反过来说,只要程序中存在非守护线程,程序就不会终止。
/* 后台线程:(守护线程) public final void setDaemon(boolean on) 通过这个方法将该线程对象标记为守护线程或者非守护线程。 当运行的程序只有一个线程且是守护线程的时候,Java虚拟机退出 注意: 将线程设置为守护线程这一步骤,必须在启动前设置。 */ 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("小草"); t2.setDaemon(true); t3.setName("小树"); t3.setDaemon(true); t1.start(); t2.start(); t3.start(); } }
中断线程 public final void stop()
public void interrupt()
import java.util.Date; public class MyStopThread extends Thread { @Override public void run() { System.out.println("开始执行时间:" + new Date()); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("结束时间:" + new Date()); System.out.println("hello"); } }
/* 中断线程: public final void stop():让正在运行的线程停止,run方法剩下的代码不会执行,这个方法过时了,被弃用了,但是还能使用。 public void interrupt():中断正在运行的线程,被中断的线程将run方法执行完毕,并抛出异常 java.lang.InterruptedException: sleep interrupted */ public class ThreadStopDemo { public static void main(String[] args) { MyStopThread t1 = new MyStopThread(); t1.start(); try { Thread.sleep(3000); t1.stop(); // t1.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程的生命周期图
实现Runnable接口:
1、创建自定义类实现Runnable接口
2、重写run()方法
public class MyRunnbale1 implements Runnable { @Override public void run() { for (int i =1;i<=100;i++){ //由于实现的Runnable接口中并没有getName()方法,所以这里无法使用Thread类中的 //getName()方法 //间接调用,因为currentThread()是静态的,可以直接通过类名调用获取当前正在 //执行run方法的线程对象(Thread类型),所以可以调用getName() System.out.println(Thread.currentThread().getName()+":"+i); } } }
3、创建自定义类的对象
4、创建Thread类的对象,将第三步创建的自定义类对象作为参数传递到构造方法中
public class MyRunnableDemo1 { public static void main(String[] args) { //创建自定义类的对象 MyRunnbale1 myRunnbale1 = new MyRunnbale1(); //创建Thread类的对象 Thread t1 = new Thread(myRunnbale1); Thread t2 = new Thread(myRunnbale1); t1.setName("小花"); t2.setName("小草"); t1.start(); t2.start(); } }
简化代码:
匿名内部类方法:
/* 匿名内部类方式实现线程的创建 格式: new 父类/接口(){ 重写父类和接口中的方法 } */ public class MyThreadDemo3 { public static void main(String[] args) { //线程的父类Thread new Thread(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"---"+"lycc"); } } }.start(); //线程的接口 new Thread(new Runnable(){ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"---"+"余路"); } } }).start(); } }
二、解决线程安全问题:电影票案例
判断一个程序是否存在线程安全的标准:
1、是否存在多线程环境
2、是否存在共享数据/共享变量
3、是否有多条语句操作着共享数据/共享变量
例:
解决问题的思想:
要是有一个办法可以让多条语句控制共享数据包成一个整体,在某个线程执行的时候,别的线程进不来
知道某个线程执行完毕一次run方法之后,别的线程再进来。
解决问题的方式:同步安全机制
1、使用同步代码块;
格式:
synchronized(对象){
需要同步的代码;
}
public class TicketWindow4 implements Runnable { //定义100张票 private int tickets = 100; Object obj = new Object(); @Override public void run() { while (true) { //窗口1,窗口2,窗口3 synchronized (obj) {//起到了一个锁的作用 if (tickets > 0) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 正在出售第 " + (tickets--) + " 张票"); } } } } }
public class SellTicketDemo4 { public static void main(String[] args) { //创建Runnable对象 TicketWindow4 ticktetWindow2 = new TicketWindow4(); //创建三个线程对象,模拟三个窗口 Thread window1 = new Thread(ticktetWindow2, "窗口1"); Thread window2 = new Thread(ticktetWindow2, "窗口2"); Thread window3 = new Thread(ticktetWindow2, "窗口3"); window1.start(); window2.start(); window3.start(); } }
2、定义一个同步方法:
注意:
如果锁对象是this,就可以考虑使用同步方法。 否则能使用同步代码块的尽量使用同步代码块。
public class RunableDemo2 implements Runnable { private int ticket = 100; @Override public void run() { //同步方法中的锁对象就是new runabledemo2,也就是this System.out.println("this" + this); while (true) { run1(); } } public synchronized void run1() { if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket + "张票"); ticket--; } } // public /*synchronized*/ void run1() { // synchronized (this) { // if (ticket > 0) { // try { // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket + "张票"); // ticket--; // } // } // // } }
public class RunableText2 { public static void main(String[] args) { //创建runable实例对象,多线程共享电影票资源 RunableDemo2 runableDemo2 = new RunableDemo2(); System.out.println("run"+runableDemo2); //创建多线程 Thread t0 = new Thread(runableDemo2); Thread t1 = new Thread(runableDemo2); Thread t2 = new Thread(runableDemo2); //开启线程 t0.start(); t1.start(); t2.start(); } }
定义一个同步静态方法:
/* 同步方法的静态形式 */ public class RunableDemo3 implements Runnable { private static int ticket = 100; @Override public void run() { while (true) { run1(); } } //静态方法访问静态变量 //这里的锁对象是本类的.class public static/* synchronized */void run1() { synchronized (RunableDemo3.class){ if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket + "张票"); ticket--; } } } }
3、Lock锁:
我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
import java.util.concurrent.locks.ReentrantLock;
public class RunableDemo4 implements Runnable {
private int ticket = 100;
ReentrantLock r = new ReentrantLock();
@Override
public void run() {
// while (true) {
// r.lock();
// if (ticket > 0) {
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket + "张票");
// ticket--;
// r.unlock();
// }
//
// }
//通过finally代码块无论程序是否异常都释放资源
while (true) {
try {
r.lock();
if (ticket > 0) {//t1
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "窗口正在卖第" + ticket-- + "张票");
}
}finally {
r.unlock();
}
}
}
}
public class RunableText4 { public static void main(String[] args) { //创建runable实例对象,多线程共享电影票资源 RunableDemo4 runableDemo4 = new RunableDemo4(); //创建多线程 Thread t0 = new Thread(runableDemo4); Thread t1 = new Thread(runableDemo4); Thread t2 = new Thread(runableDemo4); //开启线程 t0.start(); t1.start(); t2.start(); } }
同步的弊端:
1、效率低
2、容易产生死锁
/* 死锁问题: 两个或者两个以上的线程在争夺资源的过程中,出现了相互等待的现象,叫死锁现象。 举例: 中国人,美国人吃饭的问题。 正常情况下: 中国人:两支筷子 美国人:一把刀,一把叉 现在: 中国人:一支筷子,一把刀 美国人:一支筷子,一把叉 */ 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 DieLock extends Thread { private boolean flag; public DieLock(boolean flag){ this.flag = flag; } @Override public void run() { if(flag){ //d1 synchronized (MyLock.lock1){ System.out.println("if lock1");//情况是d1走到这里等死锁 synchronized (MyLock.lock2){ System.out.println("if lock2"); } } }else { //d2 synchronized (MyLock.lock2){//d2 System.out.println("else lock2");//当d2走到这里等死锁 synchronized (MyLock.lock1){ System.out.println("else lock1"); } } } } } class MyLock { public static final Object lock1 = new Object(); public static final Object lock2 = new Object(); }
回顾以前的线程安全的类
A:StringBuffer
B:Vector
C:HashTable
D:如何把一个线程不安全的集合变成一个线程安全的集合类
用Collections 工具类的方法即可。
以List集合为例:其他集合创建方法类似,具体看API
import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; public class CollectionsSafety { public static void main(String[] args) { //这样创建的集合是不安全的 List<String> list1 = new ArrayList<String>(); //Collections工具类提供的方法,同步安全的 List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); list2.add("1"); list2.add("2"); synchronized (list2){ Iterator<String> iterator = list2.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } } // for (String s : list2) { // System.out.println(2); // } } }
线程之间的通信问题:(不同种类的线程针对同一资源的操作)
以学生作为资源来实现的
资源类:student
设置数据类:SetThread(生产者)
获取数据类:GetThread(消费者)
测试类:StudentDemo
代码:
基础版本,只有一个数据
Student类
public class Student { String name; int age; }
SetThread类
public class SetThread implements Runnable{ private Student s; //为了保证多个线程操作同一个学生,重写有参构造方法 public SetThread(Student s) { this.s = s; } @Override public void run() { s.name="lyc"; s.age=18; } }
Getthread类
public class GetThread implements Runnable { private Student s; public GetThread(Student s) { this.s = s; } @Override public void run() { System.out.println(s.name + "---" + s.age); } }
测试类:
public class StudentDemo { public static void main(String[] args) { Student student = new Student(); SetThread setThread = new SetThread(student); GetThread getThread = new GetThread(student); Thread thread = new Thread(setThread); Thread thread1 = new Thread(getThread); thread1.start(); thread.start(); } }
但是运行多次我们发现,有出现姓名为null或者年龄为0的情况出现
这是因为CPU进行的操作都是原子操作,有情况是当对年龄进行赋值,还没来得及对姓名赋值,下一个线程就进行输出。从而导致有null的情况,其他情况类推。
改进版本,给出不同数据,并加入同步:
学生类:
public class Student { String name; int age; }
SetThread;
public class SetThread implements Runnable{ private Student s; private int x = 0; //为了保证多个线程操作同一个学生,重写有参构造方法 public SetThread(Student s) { this.s = s; } @Override public void run() { while(true){ if (x%2==0){ s.name="lyc"; s.age=18; }else{ s.name="lycc"; s.age=19; } x++; } } }
GetThread:
public class GetThread implements Runnable { private Student s; public GetThread(Student s) { this.s = s; } @Override public void run() { while(true){ System.out.println(s.name + "---" + s.age); } } }
测试类
public class StudentDemo { public static void main(String[] args) { Student student = new Student(); SetThread setThread = new SetThread(student); GetThread getThread = new GetThread(student); Thread thread = new Thread(setThread); Thread thread1 = new Thread(getThread); thread1.start(); thread.start(); } }
我们发现了问题
1:同一个数据出现多次
CPU的一点点时间片的执行权,就足够执行很多次
2:姓名和年龄不匹配
线程运行的随机性
怎么判断一个线程是不是安全的:
1:是否是多线程环境 是
2:是都有共享数据 是
3:是否有多条语句操作共享数据 是
等待与唤醒机制:让数据能够实现依次的出现
线程之间的通信:生产者和消费者案例
Object类中有三个方法:
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
void notify() 唤醒正在等待对象监视器的单个线程。
void notifyAll() 唤醒正在等待对象监视器的所有线程。
为什么这些方法不定义在Thread类中呢?
这些方法想要调用,必须通过锁对象进行调用,而我们知道锁对象可以是任意锁对象
所以这些方法,就必须定义在Object类中。
注意:1、不同种类的线程都要加锁。 2、不同种类的线程加的锁必须是同一把。
student:
public class Student { String name; int age; boolean flag = false; }
SetThread;
public class SetThread implements Runnable { private Student s; private int x = 0; //为了保证多个线程操作同一个学生,重写有参构造方法 public SetThread(Student s) { this.s = s; } @Override public void run() { while (true) { synchronized (s) { if (s.flag == false) { if (x % 2 == 0) { s.name = "lyc"; s.age = 18; } else { s.name = "lycc"; s.age = 19; } x++; s.flag = true; s.notify(); } else { try { s.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
GetThread:
public class GetThread implements Runnable { private Student s; public GetThread(Student s) { this.s = s; } @Override public void run() { while (true) { synchronized (s) { if (s.flag == false) { try { s.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.name + "---" + s.age); s.flag = false; s.notify(); } } } }
测试类:
public class StudentDemo { public static void main(String[] args) { Student student = new Student(); SetThread setThread = new SetThread(student); GetThread getThread = new GetThread(student); Thread thread = new Thread(setThread); Thread thread1 = new Thread(getThread); thread1.start(); thread.start(); } }
优化:把数据及操作都写在了资源类中,同步方法实现
student:
public class Student { String name; int age; boolean flag = false; public synchronized void set(String name, int age) { if (this.flag == false) { this.name = name; this.age = age; flag = true; this.notify(); } else { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void get(){ if (flag == false) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(this.name + "---" + this.age); flag = false; this.notify(); } }
SetThread
public class SetThread implements Runnable { private Student s; private int x = 0; //为了保证多个线程操作同一个学生,重写有参构造方法 public SetThread(Student s) { this.s = s; } @Override public void run() { while(true){ if (x % 2 == 0) { s.set("lyc",18); } else { s.set("lycc",19); } x++; } } }
GetThread:
public class GetThread implements Runnable { private Student s; public GetThread(Student s) { this.s = s; } @Override public void run() { while (true) { s.get(); } } }
包子铺案例:
//创建包子类 public class BaoZI { String pi; String xian; Boolean flag = false; }
//创建包子铺类 public class BaoZIStore extends Thread{ private BaoZI bz; public BaoZIStore(BaoZI bz) { this.bz = bz; } @Override public void run() { int count = 0;//用于生产不同的包子 while(true){ synchronized (bz){ if (bz.flag==false){ //包子铺没有包子,生产包子 if (count%2==0){ bz.pi= "薄皮"; bz.xian="三鲜馅"; }else{ bz.pi= "冰皮"; bz.xian="牛肉馅"; } count++; System.out.println("包子铺正在生产"+bz.pi+bz.xian+"包子"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } bz.flag=true; bz.notify(); System.out.println("包子铺生产好"+bz.pi+bz.xian+"包子唤醒消费者吃包子"); }else{ try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
//创建消费者类 public class BaoZIEat extends Thread{ private BaoZI bz; public BaoZIEat(BaoZI bz) { this.bz = bz; } @Override public void run() { while(true){ synchronized (bz){ if(bz.flag==false){ //包子铺没有包子,消费者进入wait等待 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println("顾客正在吃"+bz.pi+bz.xian+"包子"); bz.flag=false; bz.notify(); System.out.println("顾客吃完"+bz.pi+bz.xian+"包子"); System.out.println("============================"); } } } } }
public class BaoZIMain { public static void main(String[] args) { BaoZI bz = new BaoZI(); new BaoZIStore(bz).start(); new BaoZIEat(bz).start(); } }
线程的状态转换图:
线程组:
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
默认情况下,所有的线程都属于主线程组。
public final ThreadGroup getThreadGroup()
我们也可以给线程设置分组
Thread(ThreadGroup group, Runnable target, String name)
/* 线程组:把多个线程组合到一起 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, "影山"); //默认情况下,所有的线程都属于主线程组。 //public final ThreadGroup getThreadGroup() ThreadGroup tg1 = t1.getThreadGroup(); ThreadGroup tg2 = t2.getThreadGroup(); //public final String getName()返回此线程组的名称。 System.out.println(tg1.getName());//main System.out.println(tg2.getName());//main System.out.println(Thread.currentThread().getThreadGroup().getName());//main //Thread(ThreadGroup group, Runnable target, String name) //给线程分组 //先创建一个新的线程组 //ThreadGroup(String name) //构造一个新的线程组。 ThreadGroup tg3 = new ThreadGroup("单细胞组"); //创建线程对象 Thread t3 = new Thread(tg3, myRunnable, "翔阳"); Thread t4 = new Thread(tg3, myRunnable, "影山"); System.out.println(t3.getThreadGroup().getName());//单细胞组 System.out.println(t4.getThreadGroup().getName());//单细胞组 //直接通过组名设置一个组都是守护线程组,组里面的线程都是守护线程 tg3.setDaemon(true); } }
public class MyRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
线程池:
程序启动一个新的线程成本是比较高的,因为它涉及到要与操作系统进行交互,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
1、线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
2、在JDK5之前,我们必须手动实现自己的线程池,从JDk5开始,Java内置支持线程池。
如何实现线程池的代码
1、创建一个线程对象,控制要创建几个线程对象。
public static ExecutorService newFixedThreadPool(int nThread)
2、这种线程池的线程可以执行:
可以执行Runnable对象或者Callable对象代表的线程
做一个类实现Runnable接口。
3、调用下面的方法即可
Future submit(Runnable task)
<T> future<T> submit(Callable<T> task)
4、我就要结束,可以吗? 可以。
shutdown()
实现Runable接口:
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; public class ThreadPoolDemo { public static void main(String[] args) { //创建一个线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); ExecutorService pool1= Executors.newCachedThreadPool(); //Future<?> submit(Runnable task) //提交一个可运行的任务执行,并返回一个表示该任务的未来。 pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); pool1.submit(new MyRunnable()); pool1.submit(new MyRunnable()); pool1.submit(new MyRunnable()); pool1.submit(new MyRunnable()); //结束线程池 pool.shutdown(); pool1.shutdown(); //线程池已经被关闭,不能再重复提交运行。 // pool.submit(myRunnable); } }
实现Callable接口
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; public class ThreadPoolDemo2 { public static void main(String[] args) { //创建线程池对象 ExecutorService pool = Executors.newFixedThreadPool(2); //可以执行Runnable对象或者Callable对象代表的线程 pool.submit(new MyRunnable()); pool.submit(new MyCallable()); pool.shutdown(); } }
Callable的案例(计算1+....的总和 线程池实现)
实现Callable接口
import java.util.concurrent.Callable; public class MyCallable implements Callable<Integer> { private int number; public MyCallable(int number) { this.number = number; } @Override public Integer call() throws Exception { int sum = 0; for (int x = 1; x <= number; x++) { sum += x; // System.out.println(Thread.currentThread().getName()+":"+sum); } // System.out.println(Thread.currentThread().getName()+":"+sum); return sum; } }
测试类:
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class MyCallableDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { //创建线程池 ExecutorService pool = Executors.newFixedThreadPool(3); Future<Integer> f1 = pool.submit(new MyCallable(50)); Future<Integer> f2 = pool.submit(new MyCallable(100)); Future<Integer> f3 = pool.submit(new MyCallable(200)); int i1 = f1.get(); int i2 = f2.get(); int i3 = f3.get(); System.out.println(i1); System.out.println(i2); System.out.println(i3); pool.shutdown(); } }
多线程的面试题
a:多线程有几种实现方案,分别是哪几种?
两种。(面试答2种)
继承Thread类
实现Runnable接口
扩展一种,实现Callable接口,这个要和线程池结合使用。
b:同步有几种方式,分别是是什么?
两种。
同步代码块 锁是任意对象锁
同步方法 锁是this
同步静态方法 锁是当前类的二进制字节码文件
c:启动一个线程是run()还是start()?它们的区别?
start()
run():封装了被线程执行的代码块,直接调用仅仅是一个普通方法的调用
start():启动线程,并由JVM自动调用run()方法
d:sleep()和wait()方法的区别
sleep():必须指定时间,不释放锁。
wait():可以不指定时间,也可以指定时间,但是它释放锁。
e:为什么wait(), notify(), notifyAll()等方法都定义在Object类中?
因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。而Object代表任意对象,所以定义在里面。
f:线程的生命周期图(如上)
新建 -- 就绪 -- 运行 -- 死亡
新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡
线程中需要注意的问题:
1、Thread中start()方法的功能就是创建一个新的线程,并自动调用该线程的run()方法,直接调用run()方法是不会创建一个新的线程的,直接调用相当于调用一个普通方法。
2、执行一个线程实际就是执行该线程run方法中的代码
3、一个Thread对象只能代表一个线程。
一个Thread对象不能调用两次start()方法,否则会抛出java.lang.IllegalThreadStateException异常