一、线程简介
- 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
- 一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,线程是CPU调度和 执行的单位。
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的;
- 对用一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销;
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
二、线程创建
- Thread类
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
- 不建议使用,避免OOP单继承局限性
/*
创建线程方式一:继承Thread类,重写run(),调用start()方法执行
注意:线程开启不一定立即执行,由cpu调度执行
*/
public class TestThread1 extends Thread{
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码:"+i);
}
}
public static void main(String[] args) {
//main线程,主线程i
//声明一个线程对象
TestThread1 testThread1=new TestThread1();
//调用start()方法开启线程
testThread1.start();
// testThread1.run();
for (int i = 0; i < 200; i++) {
System.out.println("我们在学习多线程:"+i);
}
}
}
2 .实现Runnable接口
- 定义Runnable接口的类
- 实现run(),编写线程执行体
- 创建线程对象,通过Thread开启线程
- 调用start()方法
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
/*
创建线程方式二:实现Runnable接口,重写run()方法,执行线程需要丢入runnable接口类,启用start()
*/
public class TestThread3 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我们在看代码"+i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
TestThread3 testThread3=new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程
Thread thread=new Thread(testThread3);
thread.start();
for (int i = 0; i < 200; i++) {
System.out.println("我们在学习多线程:"+i);
}
}
}
3.龟兔赛跑
/*
龟兔赛跑:
1.首先来个赛道距离,然后距离终点越来越近
2.判断比赛是否结束
3.打印出胜利者
4.龟兔赛跑开始
5.兔子需要先睡觉,模拟兔子睡觉
6.乌龟赢得比赛
*/
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
//模拟兔子休息
for (int i = 0; i <= 100; i++) {
if (Thread.currentThread().getName().equals("兔子")&& i%10==0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag=gameOver(i);
if (flag==true){
break;
}
System.out.println(Thread.currentThread().getName()+"--->跑了"+i+"步");
}
}
//判断比赛是否完成
private boolean gameOver(int steps){
if(winner!=null){
return true;
}{
if(steps>=100){
winner=Thread.currentThread().getName();
System.out.println("winner is "+winner);
}
}
return false;
}
public static void main(String[] args) {
Race race=new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
4.实现Callable接口
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);
- 提交执行:Future<Bollean> result1=result1.get();
- 关闭服务:ser.shutdownNow();
5.静态代理
/*
静态代理:
(1)真实对象和代理对象都要实现同一个接口
(2)代理对象要代理真实角色
(3)静态代理其实就是多线程的低层实现方式
优点:
(1)代理对象可以做很多真实对象做不了的事情
(2)真实对象专注于做自己的事情
静态代理类似实现Runnable接口,Thread类作为静态代理开启线程
*/
public class StaticProxy {
public static void main(String[] args) {
//实例化一个真实角色
You you=new You();
//把你给到婚庆公司(代理角色)
new Thread(()->System.out.println("幸福")).start();
//婚庆公司帮你结婚(代理角色帮你)
new WeddingCompany(you).HappyMarry();
}
}
//================真实对象和代理对象需要实现接口====
interface Marry{
void HappyMarry();
}
//================真实角色,你去结婚===========
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("要结婚了,开心");
}
}
//==============代理角色,帮助你结婚============
class WeddingCompany implements Marry {
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void before() {
System.out.println("结婚前,布置现场");
}
private void after() {
System.out.println("结婚后,收尾款");
}
}
6.Lamda表达式
- 函数式接口(Functional Interface):
(1) 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
public interfac Runnable{
public void run();
}
(2)对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
public class TestLambda2 {
public static void main(String[] args) {
// ILove love=new ILove();
// ILove love=new ILove() {
// @Override
// public void love(int a) {
// System.out.println("111");
// }
// };
// love.love(2);
//1.lambda简化
ILove love=(int a)->{System.out.println("I love you"+a);
};
love.love(3);
//2.简化1.参数类型
love=(a)->{System.out.println("I love you"+a);
};
love.love(4);
//3.简化2.简化括号
love=a->{System.out.println("I love you"+a);
};
love.love(5);
//4.简化3.去掉花括号
love=a->System.out.println("I love you"+a);
love.love(5);
//总结:
// 去掉花括号是因为只有一行输出
// 前提是接口为函数式接口
// 多个参数可以去掉参数类型,要去掉就都去掉,必须加上括号
}
}
interface ILove{
void love(int a);
}
7.线程状态
(1) 停止线程
- 不推荐使用JDK提供的stop()、destroy()方法;
- 推荐线程自己停止下来;
建议使用一个标志位进行终止变量:当flag=false,则终止线程运行。
/*
测试stop()---停止线程
1.建议线程正常停止-->利用次数,不建议死循环
2.建议使用标志位-->设置一个标志位
3.不要使用stop或者destory等过时或者JDK不建议使用的方法
*/
public class TestStop implements Runnable{
//1.设置标志位,利用循环将线程正常停止
private boolean flag=true;
@Override
public void run() {
int i=0;
while(flag){
System.out.println("run....Thread "+i++);
}
}
//2.声明方法停止线程,转换停止位
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
TestStop testStop=new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main: "+i);
if(i==900){
testStop.stop();
System.out.println("线程该停止了");
}
}
}
(2)线程礼让
/*
线程礼让:
1.礼让不一定成功,看cpu心情
*/
public class TestYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
public static void main(String[] args) {
TestYield testYield=new TestYield();
new Thread(testYield,"a").start();
new Thread(testYield,"b").start();
}
}
(3)Join
/*
Join:
1.join合并线程,待线程执行完成后,再执行其他线程,其他线程阻塞
2.可以想象成插队
*/
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程vip来了"+i);
}
}
public static void main(String[] args) throws InterruptedException {
//启动线程
TestJoin testJoin=new TestJoin();
Thread thread=new Thread(testJoin);
thread.start();
//主线程
for (int i = 0; i < 500; i++) {
if(i==200){
thread.join();//插队
}
System.out.println("main"+i);
}
}
(3) 线程状态观测
- Thread.State
- 一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的 虚拟机状态
(4)线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
- 线程的优先级用数字表示,范围从1~10
(1)Thread.MIN_PRIORITY=1
(2)Thread.MAX_PRIORITY=10
(3)Thread.NORM_PRIORITY=5;
- 使用以下方式改变或者获取优先级
getPriority().setPriority(int xxx)
- 低优先级只是意味着获取调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度
public class TestPriority { public static void main(String[] args) { //主线程的优先级 System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); //设置优先级 MyPrority myPrority=new MyPrority(); Thread t1=new Thread(myPrority); t1.setPriority(1); t1.start(); Thread t2=new Thread(myPrority); t2.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10 t2.start(); Thread t3=new Thread(myPrority); t3.setPriority(6); t3.start(); Thread t4=new Thread(myPrority); t4.setPriority(Thread.MIN_PRIORITY);//MIN_PRIORITY=1 t4.start(); Thread t5=new Thread(myPrority); t5.setPriority(Thread.NORM_PRIORITY);//NORM_PRIORITY=5 t5.start(); Thread t6=new Thread(myPrority); t6.setPriority(2); t6.start(); Thread t7=new Thread(myPrority); t7.setPriority(8); t7.start(); } } class MyPrority implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority()); } }
(5)守护线程(daemon)
-
线程分为用户线程和守护线程
-
虚拟机必须确保用户线程执行完毕
-
虚拟机不用等待守护线程执行完毕
-
后台记录操作日志,监控内存,垃圾回收等待
/* 测试守护线程 */ public class TestDaemon { public static void main(String[] args) { God god=new God(); Yous yous=new Yous(); Thread thread=new Thread(god); thread.setDaemon(true);//设置god为守护线程 thread.start(); new Thread(yous).start(); } } class God implements Runnable { @Override public void run() { while (true) { System.out.println("保佑你"); } } } class Yous implements Runnable{ @Override public void run() { for (int i = 0; i < 36500; i++) { System.out.println("开心的一辈子"); } System.out.println("===googbye! world==="); } }
(6)线程同步
-
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
-
线程同步的形成条件:队列+锁--->保证线程的安全性
-
由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(synchronized) , 当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
<1> 一个线程持有锁会导致其他所有需要此锁的线程挂起;
<2>在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
<3>如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题。
(7)同步方法
- 保证线程安全,我们只需要通过使用synchronized关键字,它包括synchronized方法和synchronized块
- synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。若将一个大的方法申明为synchronized将会影响效率。
- 方法里面需要修改的内容才需要锁。
- synchronized默认为this,针对单个对象
//不安全的买票 public class UnsafeBuyTicket { public static void main(String[] args) { BuyTicket buyTicket=new BuyTicket(); new Thread(buyTicket,"李华").start(); new Thread(buyTicket,"李明").start(); new Thread(buyTicket,"黄牛党").start(); } } //取票 class BuyTicket implements Runnable{ //票数 private int ticketNumber=10; boolean flag=true; @Override public void run() { //取票 while(flag){ buy(); } } //synchronized 同步方法,锁的是本身 private synchronized void buy(){ //判断是否有票 if(ticketNumber<=0){ flag=false; return; } //模拟延时 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //取票 System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNumber--+"张票"); } }
(8)同步块
- 同步块:synchronized(Obj){}
- Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class
- 同步监视器的执行过程:
(1) 第一线程访问,锁定同步监视器,执行其中代码;
(2) 第二个线程访问,发现同步监视器被锁定,无法访问;
(3) 第一个线程访问完毕,解锁同步监视器;
(4)第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
//不安全的取前
//两个人去银行取钱
public class UnsafeBank {
public static void main(String[] args) {
Account account=new Account(100,"基金");
Drawing you=new Drawing(account,50,"你");
Drawing girlFriend=new Drawing(account,100,"妻子");
you.start();
girlFriend.start();
}
}
//账户信息
class Account{
//账户余额
int money;
//卡的名字
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取钱
class Drawing extends Thread {
//账户余额
Account account;
//取了多少钱
int drawingMoney;
//现在手里有多少钱
int nowMoney;
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
@Override
public void run() {
synchronized (account){
//取钱
//判断是够有钱
if (account.money-drawingMoney< 0) {
System.out.println(Thread.currentThread().getName() + "钱不够了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//账户余额
account.money = account.money - drawingMoney;
//手里有多少钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"的余额"+account.money);
System.out.println(this.getName()+"手里现在有"+nowMoney);
}
}
(9)死锁
- 某一同步块同时拥有"两个以上对象的锁"时,就可能会发生“死锁”的问题
-
//死锁:多个线程互相抱着对方需要的资源,然后僵持着 public class DeadLock { public static void main(String[] args) { Makeup g1=new Makeup(0,"A"); Makeup g2=new Makeup(1,"B"); g1.start(); g2.start(); } } //口红 class Lipstick{ } //镜子 class Mirror{ } class Makeup extends Thread{ //需要的资源只有一份,用static来保证 static Lipstick lipstick=new Lipstick(); static Mirror mirror=new Mirror(); int choice;//选择 String girlName;//使用化妆品的人 public Makeup(int choice, String girlName) { this.choice = choice; this.girlName = girlName; } @Override public void run() { try { makeup(); } catch (InterruptedException e) { e.printStackTrace(); } } //化妆,互相持有对方的锁,就是需要拿到对方的资源 private void makeup() throws InterruptedException { if(choice==0){ synchronized (lipstick){ //获取口红的锁 System.out.println(this.girlName+"获取口红的锁"); Thread.sleep(1000); } synchronized (mirror){ //获取镜子的锁 System.out.println(this.girlName+"获取镜子的锁"); } }else{ synchronized (mirror){ //获取镜子的锁 System.out.println(this.girlName+"获取镜子的锁"); Thread.sleep(3000); } synchronized (lipstick){ //获取口红的锁 System.out.println(this.girlName+"获取口红的锁"); } } } }
(10)Lock(锁)
-
//测试Lock锁
public class TestLock{
public static void main(String[] args) {
TestLock2 testLock2=new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 implements Runnable{
int ticketNums=10;
//定义lock锁
//ReentrantLock---可重复锁
private final ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while(true) {
try {
lock.lock();//加锁
if (ticketNums >= 0) {
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticketNums--);
} else {
break;
}
}finally {
//解锁
lock.unlock();
}
}
}
}
(11)线程协作
//生产者消费者模型-->利用缓冲区解决:管程法
//生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
Containor containor=new Containor();
new Productor(containor).start();
new Customer(containor).start();
}
}
//生产者
class Productor extends Thread{
Containor containor;
public Productor(Containor containor) {
this.containor=containor;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了"+i+"只鸡");
containor.push(new Chicken(i));
}
}
}
//消费者
class Customer extends Thread{
Containor containor;
public Customer(Containor containor) {
this.containor=containor;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了-->"+containor.pop().id+"只鸡");
}
}
}
//产品
class Chicken{
int id;//产品编号
public Chicken(int id) {
this.id = id;
}
}
//缓冲区
class Containor{
Chicken[] chickens=new Chicken[10];//缓冲区的大小
int count=0;//计数器
//放入产品
public synchronized void push(Chicken chicken){
//如果缓冲区满了,等待生产,
if(count==chickens.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//缓冲区没有满,生产者生产,消费者进行消费
chickens[count]=chicken;
count++;
//消费者消费
this.notifyAll();
}
//消费
public synchronized Chicken pop() {
//如果缓冲区为空,等待消费
if(count==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//缓冲区不为空
count--;
Chicken chicken=chickens[count];
//开始生产
this.notifyAll();
return chicken;
}
}
//测试生产者消费者问题2:信号灯法,标志位解决
public class TestPc2 {
public static void main(String[] args) {
Tv tv=new Tv();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者--演员
class Player extends Thread{
Tv tv;
public Player(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if(i%2==0){
this.tv.play("快乐大本营");
}
else{
this.tv.play("抖音:记录美好生活");
}
}
}
}
//消费者--观众
class Watcher extends Thread{
Tv tv;
public Watcher(Tv tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品--节目
class Tv{
//演员表演,观众等待;True
//观众观看,演员等待;False
String voice; //表演的节目
boolean flag=true;//标志位,信号灯法
//表演
public synchronized void play(String voice){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:"+voice);
//通知观众观看
this.notifyAll();
this.voice=voice;
this.flag=!this.flag;
}
//观看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了"+voice);
//通知演员表演
this.notifyAll();
this.flag=!this.flag;
}
}
(12)线程池
//线程池
public class TestThreadPool {
public static void main(String[] args) {
//创建线程池
ExecutorService executorService= Executors.newFixedThreadPool(10);
//执行
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
//关闭链接
executorService.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}