线程目录
一、线程简介
1.1 进程与线程
- 运行在内存中的程序就是进程
- 内存通向cpu的执行的路径就是线程
- 单线程:只有一条通向cpu的执行路径就是单线程
- 多线程:多条通向cpu的执行的路径就是多线程
- 进程与线程的关系:一个进程中可以包含多个线程、一个线程只能在一个进程中
- 单线程
- 多线程
1.2 并发与并行
- 并发(高并发):在同一个时间段,执行两个或多个程序在单核cpu中交互执行,因为交换速度过快,可能产生误区是同时执行,其实是交互执行
- 并行:在同一个时间段,执行两个或者多个程序在多核cpu中同时执行(效率高)
- main方法是主线程,只有一条执行路径
1.3 线程调度的方式
- 线程调度的方式分为:分配式调度和抢占式调度
- 分配式调度:多个线程执行任务的时间都是平均分配,每个线程执行的周期都是相同
- 抢占式调度:线程的优先级越高,获取cpu的执行权就越高,抢到cpu的执行权,就优先执行
- 图
1.4 线程创建时内存分配
- 图
- 继承一个Thread类,含有run()方法。当我们调用start()方法时,jvm会自动调用run()方法,此时会在内存中开辟一个新的栈内存,加载run()方法
- 但如果我们在main()中直接调取run()方法,则只是将run()加载到main(),所以获取线程名就为main的线程名
- 总结:线程开启,都会新开辟一个栈内存,在自己的栈内存中执行run()方法(每new一个Thread类或其子类,都会在内存中开辟)
1.5 线程中的方法
- 设置名称和获取线程名
package qf22020308_thread.Demo02;
/**
* 获取方法名称 两种方法:
* 设置方法名称 两种方法:
*/
public class Demo01 {
public static void main(String[] args) {
//设置方法名1
MyThread01 myThread01 = new MyThread01("花生");
//设置方法名2
myThread01.setName("你好");
//获取线程名1
System.out.println(myThread01.getName());
//获取线程名2
System.out.println(Thread.currentThread().getName());
myThread01.start();
}
}
- 线程的睡眠
package qf22020308_thread.Demo02;
/**
1. 倒计时
*/
public class Demo02 {
public static void main(String[] args) {
for (int i = 60; i > 0; i--) {
try {
Thread.sleep(1000);
System.out.println(i + " s");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1.6 守护线程和设置线程的优先级
- 守护线程:A.守护其它线程执行完操作 B.一般守护线程都是守护主线程
- 守护线程的死亡: 被守护的线程执行完操作,或者是死亡,那么守护线程也就是死亡
- 优先级注意点:
A.线程的最高优先级是10 线程的最低优先级是1 范围 1-10
B.线程优先级越高 表示获取cpu执行权越大 并不一定会执行 java 抢占式调度
二、线程创建的三种方式
2.1 继承Thread
- 定义类继承Thread
package qf22020308_thread.Demo03;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
- 测试类
package qf22020308_thread.Demo03;
/**
* 设置线程优先级
*/
public class Demo01 {
public static void main(String[] args) {
//实例化第一个线程
MyThread mt1 = new MyThread();
mt1.setPriority(8);
mt1.start(); //启动线程
//实例化第二个线程
MyThread mt2 = new MyThread();
mt2.setPriority(Thread.MAX_PRIORITY);
mt2.start();
}
}
2.2 实现Runnable接口
- 自定义类实现接口
package qf22020308_runnable.Demo01;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
- 、测试类
package qf22020308_runnable.Demo01;
public class Demo01 {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
// public Thread(Runnable target){};
Thread th = new Thread(mr);
th.start();//因为start()只有创建Thread才能调用
}
}
2.3 上述两种简化-匿名内部类
- 使用匿名内部类创建两种方式
package qf22020308_runnable.Demo02;
public class Demo01 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}).start();
new Thread() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}.start();
}
}
2.4 实现Callable接口-有返回值
- 有返回值的线程操作
- 自定义实现类
package qf22020309_callable;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
int temp = 100;
@Override
public Integer call() throws Exception {
return getSum(temp);
}
public static Integer getSum(int temp) {
if (temp == 1) {
return 1;
}
return temp + getSum(temp - 1);
}
}
- 测试类
package qf22020309_callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//自定义类MyCallable实现Callable接口
MyCallable mc = new MyCallable();
//获取任务对象
FutureTask<Integer> ft = new FutureTask<>(mc);
//将任务对象加入Thread线程中,启动线程
Thread t = new Thread(ft);
t.start();
//任务对象指针获取值
System.out.println(ft.get());
}
}
三、线程同步(通信)
3.1 产生问题的原因
- 例子:如果由a、b 线程一起执行一个1减去1的操作,如果a和b线程同时抢到cpu的执行权,抢到的都是减1的操作,那么就会出现-1的情况
- 如何解决问题:先让一个线程执行完操作,另一个线程才能执行操作
3.2 第一种解决方式-同步代码块
- synchronized (任意一个对象){问题代码}
- 锁的对象可以是任意的对象(可以自行定义),但必须是同一个对象
- 自定义类实现Runnable
package qf22020308_synchronized.Demo01;
public class MyRunnable implements Runnable {
Object obj = new Object();
public int n = 100;
@Override
public void run() {
while (true) {
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
//对象锁 可以自定义、this、类名.class
synchronized (obj) {
if (n > 0) {
System.out.println(Thread.currentThread().getName() + " " + "剩余票数:" + n);
n--;
} else {
break;
}
}
}
}
}
- 测试类
package qf22020308_synchronized.Demo01;
/**
* 对象锁,定义一个任意的对象或者本身this,来对它上锁
*/
public class Demo01 {
public static void main(String[] args) {
Runnable r = new MyRunnable();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
- 执行步骤:A.第一线程锁的对象,再执行操作 B.当第二线程遇到锁的对象时,发现对象被锁,则等待第一线程释放对象后再执行
3.3 第二种解决方式-同步方法
-
同步方法可以是普通成员方法,也可以静态方法
-
说明:
A.成员方法锁的对象就是this
B.静态方法锁的对象是当前类的class对象(类名.class),因为静态优先对象,所以不能使用this关键字 -
以上面两种方法实现
package qf22020308_work.Demo04;
public class MyRunnable implements Runnable {
private int a = 0;
private int b = 0;
@Override
public void run() {
while (a < 100) {
synchronized (Runnable.class) { //静态,因为静态优先对象,所以不能使用this关键字
getVotes1();
}
}
while (b < 100) {
synchronized (this) { //非静态方法
getVotes2();
}
}
}
public static void getVotes1() {
//执行操作语句
}
public void getVotes2() {
//执行操作语句
}
}
3.4 第三种解决方式-实现Lock接口
- Lock简介
A.实现提供了比使用synchronized方法语句可获得的更广泛的锁定操作
B.此实现允许更灵活的操作 - 实现类是:ReentrantLock
- 自定义类实现Runnable接口
package qf22020308_lock.Demo01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyLock implements Runnable {
private int count = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
while (true) {
if (count < 1) {
return;
}
System.out.println(Thread.currentThread().getName() + " " + count);
count--;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
- 测试类
package qf22020308_lock.Demo01;
public class Demo01 {
public static void main(String[] args) {
MyLock ml = new MyLock();
Thread t1 = new Thread(ml);
t1.start();
Thread t2 = new Thread(ml);
t2.start();
Thread t3 = new Thread(ml);
t3.start();
}
}
3.5 总结
- 三种方法都必须实现Runnable接口
- 控制循环都在run()方法内,操作结果可在外面
四、死锁
4.1 简介
- A占用B的锁的对象,B占用A的锁的对象,互不谦让,两个线程都处于等待状态,出现了死锁