一、导入:什么是进程?
Tip: 进程是由程序、数据及系统资源组成的一次程序运行活动的实体
Tip: 一个程序可以包含多个进程,一个进程可以包含多个线程
二、导入:为什么要使用多线程
多任务的处理方式
Tips: 多线程是实现多个线程并发执行的技术。
三、线程的创建方式
方式一(继承Thread)
1.创建一个线程类
建个类
继承Thread
重写run方法
2.创建子线程
创建线程类对象
调用start()方法启动子线程
package test;
public class MyThread1 extends Thread{
/**
* run方法是子线程执行任务的单元
*/
@Override
public void run() {
while(true) {
//查询线程的名称
String name = getName();
System.out.println(name);
}
}
}
package test;
public class T1 {
public static void main(String[] args) {
/**
* 创建子线程一
*/
//创建线程类对象-- 相当于创建了一个子线程
MyThread1 myThread1 = new MyThread1();
//给线程取名称
myThread1.setName("---子线程1---");
//启动子线程
myThread1.start();
/**
* 创建子线程二
*/
//创建线程类对象-- 相当于创建了一个子线程
MyThread1 myThread2 = new MyThread1();
myThread2.setName("@@@子线程2@@@");
myThread2.start();
while(true) {
System.out.println("***主线程***");
}
}
}
方式二(实现Runnable接口)
1.创建一个线程类
建个类
实现Runnable
重写run方法
2.创建子线程
创建自定义线程类对象
创建Thread类对象
调用Thread类对象的start()方法启动子线程
package test;
public class MyThread2 implements Runnable {
@Override
public void run() {
//获取线程名称
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
package test;
public class T2 {
public static void main(String[] args) {
//创建自定义线程类对象
MyThread2 myThread2 = new MyThread2();
/**
* 创建子线程一
*/
//创建Thread类对象
Thread t1 = new Thread(myThread2,"###子线程一###");
//调用Thread类对象的start方法启动线程
t1.start();
/**
* 创建子线程二
*/
Thread t2 = new Thread(myThread2,"***子线程二***");
t2.start();
}
}
方式三(匿名内部类)
package test;
public class T3 {
public static void main(String[] args) {
//创建Thread匿名对象
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println("我是一个子线程");
}
}
}).start();
}
}
四、线程的抢占和生命周期
线程是在cpu上执行,因为cpu单位时间里面能够执行的线程数量是有线的,所以电脑里面所有的线程不是同时执行的,而是交替执行的
线程抢占机制: 多个线程去抢占cpu资源,谁抢占到谁执行
线程生命周期的五大状态
新生(创建线程对象)
就绪(调用线程对象的start方法,线程不会立马执行,线程需要去抢占cpu资源)
运行(抢占到cpu资源)
阻塞(线程运行的过程中释放cpu资源)
死亡(线程的任务执行完毕后,进入死亡状态)
五、线程的优先级
线程的优先级分十个等级,用1-10这样的10个数字表示,数字越小优先级越低,数字越大优先级越高
优先级代表线程抢占到cpu资源的概率,不代表线程就会先执行
所有的线程,默认的优先级都是5
setPriority(1);设置线程优先级
getPriority();获取线程优先级
public class MyThread implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name);
}
}
package test;
public class T4 {
public static void main(String[] args) {
//创建自定义线程类对象
MyThread myThread = new MyThread();
//创建线程对象
Thread t1 = new Thread(myThread,"线程一");
Thread t2 = new Thread(myThread,"线程二");
Thread t3 = new Thread(myThread,"线程三");
//修改线程优先级
t1.setPriority(1);
t2.setPriority(10);
//获取线程的优先级
int t1Priority = t1.getPriority();
int t2Priority = t2.getPriority();
int t3Priority = t3.getPriority();
System.out.println("数字越小优先级越低");
System.out.println("线程一的优先级:"+t1Priority);
System.out.println("线程二的优先级:"+t2Priority);
System.out.println("线程三的优先级:"+t3Priority);
t1.start();
t2.start();
t3.start();
}
}
六.线程的常用方法
getName()--获取线程的名称
run()--线程运行代码的方法
start()--线程的启动方法
getId()--获取线程的唯一标识
sleep()--线程的休眠
yield()--线程让步,释放cup资源,重新抢占cpu资源,谁抢到谁执行
join()--线程抢占,抢占cpu资源,指的是在哪个线程里面调用该方法,其他线程就需要让出cpu资源,等待该线程执行完毕后再重新抢占cpu资源
线程休眠
package test;
public class MyThread3 implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
while(true) {
try {
//现场睡眠1秒钟
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name);
}
}
}
package test;
public class T5 {
public static void main(String[] args) {
//创建自定义线程类对象
MyThread3 myThread = new MyThread3();
//创建线程对象
Thread t = new Thread(myThread,"线程一");
t.start();
}
}
线程让步
强行让线程释放掉占用的CPU资源,再重新抢占cpu资源
public class MyThread implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 5; i++) {
//线程的让步
Thread.yield();
System.out.println(name);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread,"线程一");
Thread t2 = new Thread(myThread,"线程二");
t1.start();
t2.start();
}
}
线程抢占
线程A抢占线程B,线程A抢占的是线程B的cpu资源,线程B必须要等到线程A的代码全部执行完了才能继续执行
public class MyThread implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name);
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread,"线程一");
t1.start();
//线程的抢占(子线程t1抢占主线程的CPU资源,主线程需要等待子线程全部执行完后,再继续执行)
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println("主线程@@@@@@@@@@@@@@@@@@@");
}
}
七、线程安全[重点]
多个线程同时操作一个变量的时候,会有线程安全问题(例如票被重复卖出,票被超卖)
案例(有线程安全问题的代码)
解决线程安全问题
原理:让某个线程把一段代码全部执行完,有一个线程在执行,其它线程都不允许执行
方式1:同步代码块
把可能出现线程安全的代码放到同步代码块中
package sale;
public class SaleTicket implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
while(true) {
/**
* 同步代码块
* 只有抢到同步锁的线程才能执行同步代码块中的代码,
* 同步代码块中的代码执行完后会释放同步锁,
* 多个线程再同时抢同步锁,谁抢到谁执行
*/
synchronized (Test.lock) {
//当票卖完后,就不再卖票,结束循环
if(Test.ticketNum<=0) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//卖票
Test.ticketNum--;
System.out.println(name+"已出票,库存还剩"+Test.ticketNum+"张票");
}
}
}
}
package sale;
public class Test {
//定义变量存储票的总数量
public static int ticketNum = 100;
//创建同步锁对象
public static Object lock = new Object();
public static void main(String[] args) {
//创建线程类对象
SaleTicket saleTicket = new SaleTicket();
//创建子线程
Thread t1 = new Thread(saleTicket, "窗口一");
Thread t2 = new Thread(saleTicket, "窗口二");
Thread t3 = new Thread(saleTicket, "窗口三");
//启动子线程
t1.start();
t2.start();
t3.start();
}
}
方式2:同步方法
package sale2;
public class SaleTicket implements Runnable {
boolean f = true;
@Override
public void run() {
while(f) {
sale();
}
}
/**
* 同步方法:使用当前类对象(this)作为同步锁,保证线程安全
*/
public synchronized void sale() {
//当票卖完后,就不再卖票,结束循环
if(Test.ticketNum<=0) {
f = false;
return;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
//卖票
Test.ticketNum--;
System.out.println(name+"已出票,库存还剩"+Test.ticketNum+"张票");
}
}
package sale2;
public class Test {
//定义变量存储票的总数量
public static int ticketNum = 100;
public static void main(String[] args) {
//创建线程类对象
SaleTicket saleTicket = new SaleTicket();
//创建子线程
Thread t1 = new Thread(saleTicket, "窗口一");
Thread t2 = new Thread(saleTicket, "窗口二");
Thread t3 = new Thread(saleTicket, "窗口三");
//启动子线程
t1.start();
t2.start();
t3.start();
}
}
方式3:ReentrantLock
使用ReentrantLock对象加锁和解锁
package sale3;
public class SaleTicket implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
while(true) {
//上锁
Test.lock.lock();
//当票卖完后,就不再卖票,结束循环
if(Test.ticketNum<=0) {
//开锁
Test.lock.unlock();
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//卖票
Test.ticketNum--;
System.out.println(name+"已出票,库存还剩"+Test.ticketNum+"张票");
//开锁
Test.lock.unlock();
}
}
}
package sale3;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
//定义变量存储票的总数量
public static int ticketNum = 100;
//创建同步锁
public static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
//创建线程类对象
SaleTicket saleTicket = new SaleTicket();
//创建子线程
Thread t1 = new Thread(saleTicket, "窗口一");
Thread t2 = new Thread(saleTicket, "窗口二");
Thread t3 = new Thread(saleTicket, "窗口三");
//启动子线程
t1.start();
t2.start();
t3.start();
}
}
结论:
线程不安全的代码,效率高
线程安全的代码,效率低
八、知识点扩展
8.1、死锁
同步锁使用的弊端:当线程任务中出现了多个同步代码块(多个锁)时,如果同步代码块中嵌套了其他的同步代码块。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
案例:编写死锁实例
创建2个线程类
public class DeadLock1 implements Runnable {
@Override
public void run(){
try{
System.out.println("Lock1 running");
while(true){
synchronized(Test.obj1){
System.out.println("Lock1 lock obj1");
Thread.sleep(3000);//获取obj1后先等一会儿,让Lock2有足够的时间锁住obj2
synchronized(Test.obj2){
System.out.println("Lock1 lock obj2");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
public class DeadLock2 implements Runnable{
@Override
public void run(){
try{
System.out.println("Lock2 running");
while(true){
synchronized(Test.obj2){
System.out.println("Lock2 lock obj2");
Thread.sleep(3000);
synchronized(Test.obj1){
System.out.println("Lock2 lock obj1");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
创建测试类
public class Test {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args){
Thread a = new Thread(new DeadLock1());
Thread b = new Thread(new DeadLock2());
a.start();
b.start();
}
}
运行的结果如图所示:
可以看到,Lock1获取obj1,Lock2获取obj2,但是它们都没有办法再获取另外一个obj,因为它们都在等待对方先释放锁,这时就是死锁。
8.2、线程的互斥与同步,异步
互斥:一个公共资源同一时刻只能被一个线程使用,多个线程不能同时使用公共资源。
同步:两个或两个以上的线程在运行过程中协同步调,按预定的先后次序运行
异步:多个线程的运行过程互不相干,它们之间关系为异步。
九、综合案例
FeiYuqing.java
/**
* 费玉清
*
* @author qin
*/
public class FeiYuqing extends Thread {
@Override
public void run() {
int race = 100;
while(race > 0){
try {
int x = (int) (Math.random()*20);
if(race - x >= 0){
race = race - x;
sleep(1*1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("¥费玉清还有"+race+"米就逃跑成功啦");
}
System.out.println("**********费玉清逃跑成功");
}
}
Chimpanzee.java
/**
* 大猩猩
*
* @author qin
*/
public class Chimpanzee extends Thread {
@Override
public void run() {
int race = 100;
while(race > 0){
try {
int x = (int) (Math.random()*20);
if(race - x >= 0){
race = race - x;
sleep(1*1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("!大猩猩还有"+race+"米就抓到费玉清啦");
}
System.out.println("**********大猩猩抓到了费玉清,祝你们幸福");
}
}
Gym.java
/**
* 健身房
*
* @author qin
*/
public class Gym {
public static void main(String[] args) {
FeiYuqing fei = new FeiYuqing();
fei.start(); //费玉清跑
Chimpanzee chi = new Chimpanzee();
chi.start(); //大猩猩追
}
}