创建线程的几种方式:
一:继承
1. 创建一个类,继承Thread类
2. 重写父类中的run方法 -> 自己编写线程对象的任务
3. 创建子类的对象,并调用start()方法启动线程
创建线程类
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName()+"执行:"+i);
}
}
}
测试:
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt = new MyThread("线程1");
mt.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程执行:"+i);
}
}
}
从图中可知线程交替执行。
线程执行的内存图👇
二:实现
Thread类中的构造方法:
Thread(Runnable target)
1. 准备一个Runnable接口的实现类对象(拿类实现/匿名内部类) ,重写run方法
2. 创建Runnable的实现类对象
3. 创建Thread类对象并把Runnable的实现类对象传递进去
4. 启动线程
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"hello"+i);
}
}
},"线程名");
thread.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"PHP"+i);
}
}
}
线程的第三种开启方式(有结果的线程任务):
Callable<V> 接口 -> 线程的任务接口 V: 线程完成任务后,结果的数据类型
有且仅有一个方法: V call()
中间类 : FutureTask
FutureTask(Callable<V> callable)
FutureTask(Runnable runnable, V result)
类和接口之间的关系:
1. Runnable接口 是 FutureTask 的父接口
2. FutureTask(Callable<V> callable)
3. Thread(Runnable target)
1. 创建Callable<V>的实现类对象,重写call方法编写线程的任务
2. 创建FutureTask对象,并把任务对象传递给FutureTask对象
3. 创建Thread对象,并把FutureTask对象传递给Thread对象
4. 线程对象调用start方法,启动线程
FutureTask类中的V get()方法 : (阻塞方法)
获取线程执行完毕后的结果,必须要等到线程执行完毕后返回结果才执行!
public class ThreadDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> call = new Callable<String>() {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("有返回值的线程内部执行");
}
return "返回值内容";
}
};
FutureTask<String> ft = new FutureTask<String>(call);
Thread thread = new Thread(ft);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main主线程执行:"+i);
}
System.out.println("ft.get() = " + ft.get());
}
}
注意:ft.get()方法要放在希望线程交替执行的最后位置,不然会出现
线程并没有交替执行,main主线程执行是在有返回值的线程执行完后再执行了。
线程的名称设置和获取方法:
线程对象名的设置:
1. 构造方法:
Thread(String name)
Thread(Runnable target, String name)
2. 成员方法:
void setName(String name)
获取线程对象的名称:
Thread类中的成员方法: String getName()
//若不能直接调用getName(),可以先使用Thread类中的静态方法:
static Thread currentThread(): 获取当前线程对象
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"hello"+i);
}
}
},"线程名");
thread.setName("自定义线程名");
thread.start();
Thread thread1 = new Thread("自定义线程名2");
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"PHP"+i);
}
}
}
获取线程名:
String name = thread1.getName();
Thread.currentThread().getName()
线程的优先级:
线程的默认优先级 : 5
线程的最小优先级 : 1
线程的最大优先级 : 10
//优先级越高的线程 优先执行的概率越大
获取线程优先级的方法:
int getPriority()
设置线程优先级的方法:
void setPriority(int newPriority)
这个有点玄学了,真的运行也并不一定按照设定的优先级来。。。
守护线程:
void setDaemon(boolean on) : 传入true 设置为守护线程
public class ThreadDemo4 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("a ?");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("shuile");
}
});
t1.setDaemon(true);
t1.start();
t2.start();
}
}
守护线程就是保姆线程,在其他所有线程结束后再执行的线程。你吃肉他喝汤刷碗。
线程休眠的方法:
static void sleep(long millis) : 让线程休眠 -> 休眠多久由传入的毫秒值决定
休眠 : 线程对象仅仅释放CPU的执行权,不释放锁资源
线程的安全问题(重点):
解决线程安全问题的方法有:
1.同步操作
同步操作 : 就是上锁的意思
锁对象具备的特点:
1. 唯一
2. 能控制所有的线程对象
3. 用于做锁的元素必须是引用数据类型 -> 优先推荐: Object
同步后的代码线程对象要执行必须:
1. CPU的执行权
2. 锁资源
2.同步代码块
public class SellTicket1 implements Runnable{
//总票数 : 共享数据
int ticket = 100;
//定义锁对象
Object obj = new Object();
@Override
public void run() {
//火车站不关门
while(true){
synchronized (obj){
//判断
if (ticket > 0){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票~");
//真正的买票
ticket--;
}
}
}
}
}
3.同步方法
public class SellTicket2 implements Runnable {
//总票数 : 共享数据
int ticket = 100;
@Override
public void run() {
//火车站不关门
while (true) {
this.sell();
}
}
//同步方法
//同步方法的锁对象是谁? this
//静态同步方法的锁对象是谁? 本类的字节码对象 .class对象
public synchronized void sell() {
//判断
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票~");
//真正的买票
ticket--;
}
}
}
4.同Lock接口(面向对象的方式上锁)
public class SellTicket3 implements Runnable{
//总票数 : 共享数据
int ticket = 100;
//共享的lock对象
Lock lock = new ReentrantLock();
@Override
public void run() {
//火车站不关门
while(true){
//上锁
lock.lock();
//判断
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票
System.out.println(Thread.currentThread().getName() + "买了第" + ticket + "张票~");
//真正的买票
ticket--;
}
//解锁
lock.unlock();
}
}
}
线程的生命周期:
NEW : 新建 -> 线程对象创建出来了但是没有调用start
BLOCKED : 阻塞 -> 线程醒着但是没有锁资源或者没有CPU执行权
TIMED_WAITING -> 限时等待
场景1 : 线程对象被调用了sleep(毫秒值) -> sleep: 仅仅丢失CPU执行权,拥有锁资源
场景2 : 线程对象被调用了wait(毫秒值) -> wait: 丢失锁资源和CPU执行权
WAITING -> 无限等待
场景 : 线程对象被调用了wait() -> 只有等待被唤醒才能从无限等待状态中复活
RUNNABLE : 运行 -> 拥有CPU执行权,拥有锁对象 正在执行的线程对象
TERMINATED : 死亡 -> 已经死亡的线程状态
等待和唤醒的方法:
等待和唤醒的方法来自于 Object 类
一般都是使用锁对象控制线程的执行!!
唤醒方法: 只是唤醒线程,线程醒了还需要去抢夺CPU资源和锁资源
void notify() : 随机唤醒一个"沉睡"的线程对象
void notifyAll() : 唤醒所有"沉睡"的线程对象
等待方法: wait : 释放锁和CPU资源
void wait() : 无限等待 不被调用notify系列方法线程就不会醒
void wait(long timeout) : 限时等待 时间到自己醒或者在中途被唤醒
void wait(long timeout, int nanos)