线程简介
初识
程序
说起进程,就不得不说程序,程序是指令和数据的有序集合,其本身没有任何意义,是一个静态的概念。
进程
进程就是执行程序的一次过程,他是一个动态的概念,是系统资源分配的单位。
线程
通常在一个进程中,可以包含多个线程,一个进程中至少存在一个线程,不然没有存在的意义,线程是cpu调度和执行的单位。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。
举例:比如说一边玩游戏,一边听音乐是同时进行的吗?
不是。因为单CPU在某一个时间点上只能做一件事情。
而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
创建线程的三种方式

继承thread类进行实现多线程
package com.szw;
//创建线程方式一:继承thread,重新run()方法,调用start()开启线程
public class Thread01 extends Thread {
@Override
public void run() {
//run方法线程体,重写父类run方法
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码~~"+i);
}
}
public static void main(String[] args) {
//main主线程
Thread01 thread01 = new Thread01();
//调用start方法,启动多线程
thread01.start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程~~"+i);
}
}
}
问题:启动线程为什么不直接使用run方法,而是start方法?
Thired类用于描述线程,其中有一个定义存储功能run,用于存储线程要运行的代码,主线程的代码在main中,新线程的代码在run中,start除了开启新线程之外,还有就是启动线程的功能。
实现runnable进行多线程
package com.szw;
//创建线程方式2:实现runnable接口,重新run方法
public class Thread02 implements Runnable {
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码~~" + i);
}
}
public static void main(String[] args) {
Thread02 thread02 = new Thread02();
//创建线程对象,通过线程对象来开启我们的线程,代理
new Thread(thread02).start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程~~" + i);
}
}
}
以上两种方式,查看源码可知,thread也是实现了runnable接口,由于java单继承的特性,所以推荐使用runnable进行多线程。
上面两种开启时的区别是:runnable是需要放在Thread中进行start,而thread直接开启即可。
模拟龟兔赛跑
Thread.currentThread表示当前代码段正在被哪个线程调用的相关信息。
package com.szw;
//模拟龟兔赛跑
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
//如果是兔子线程,模拟睡觉
if (Thread.currentThread().getName().equals("兔子")){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1 ;i <= 100; i++) {
//判断比赛是否结束
boolean b = gameOver(i);
if (b){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
}
}
public boolean gameOver(int steps){
//判断是否有胜利者
if (winner!=null){//这里是因为两个进程,一个进程胜利之后,另一个还在跑
return true;
}else {
if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("胜利者是"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
扩展:Lambda表达式
官方定义:Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
由于runnable接口就是函数式接口,所以我们这里扩展Lambda可以让代码更简洁。
package com.szw;
//Lambda表达式
public class Lambda {
//第二种方法:静态内部类
static class Love implements ILove{
@Override
public void ILove(int a) {
System.out.println("I Love you 序号2"+a);
}
}
public static void main(String[] args) {
//第三种方法:局部内部类
class Love implements ILove{
@Override
public void ILove(int a) {
System.out.println("I Love you 序号3"+a);
}
}
//第四种方法:匿名内部类
ILove love = new Love(){
@Override
public void ILove(int a) {
System.out.println("I Love you 序号4"+a);
}
};
//第五种方法Lambda表达式
ILove love2 = (int a) -> {
System.out.println("I Love you 序号5"+a);
};
//简化一:简化Lambda表达式
ILove love3 = (a) -> {
System.out.println("I Love you 序号6"+a);
};
//简化二:去掉a的括号,继续简化Lambda表达式
ILove love4 = a -> {
System.out.println("I Love you 序号7"+a);
};
//简化三:去掉花括号,(注意这里只是一行代码的情况)继续简化Lambda表达式
ILove love5 = a -> System.out.println("I Love you 序号8"+a);
//Lambda表达式:
//前提:1、只有一行代码,否则使用代码块包裹。
//2、必须是函数式接口,接口中只有一个方法
//3、多个参数也可以去掉int,但是要加括号
love.ILove(0);
love2.ILove(0);
love3.ILove(0);
love4.ILove(0);
love5.ILove(0);
}
}
//第一种方法:首先定义一个接口,实现它,然后在主方法中进行调用(普通的方法)
interface ILove{
void ILove(int a);
}
class Love implements ILove{
@Override
public void ILove(int a) {
System.out.println("I Love you 序号1"+a);
}
}
线程状态

注意:查看源码,可知线程有六种状态!
线程的一些方法:
线程停止
线程停止
1、建议线程停止--->利用次数,不建议死循环
2、建议使用标志位--->设置一个标志位
3、不建议使用stop和destroy等过时的方法,jdk官方不推荐
package com.szw;
//测试线程停止
//1、建议线程停止--->利用次数,不建议死循环
//2、建议使用标志位--->设置一个标志位
//3、不建议使用stop和destroy等过时的方法,jdk官方不推荐
public class ThreadStop implements Runnable {
//设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("run...+Thread" + i++);
}
}
//设置一个公开的标志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
ThreadStop threadStop = new ThreadStop();
new Thread(threadStop).start();//线程开始
for (int i = 0; i < 200; i++) {
System.out.println("主线程" + i);
if (i == 100) {
//调用stop方法,停止
threadStop.stop();
System.out.println("线程停止...");
}
}
}
}
线程休眠
模拟时钟思路:首先获取系统时间,然后进行while循环,输出系统时间,线程休眠1s,然后更新获取系统时间,再输出。。
package com.szw;
import java.text.SimpleDateFormat;
import java.util.Date;
//线程休眠模拟倒计时
public class TestSleep {
public static void main(String[] args) {
Date startTime = new Date(System.currentTimeMillis());//获取系统时间
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());//更新时间
}
}
//模拟倒计时
public static void tenDown() throws InterruptedException {
int num = 10;
while (true) {
Thread.sleep(1000);
System.out.println(num--);
if (num < 0) {
break;
}
}
}
}
线程礼让
- 线程礼让,让当前的线程暂停,但是不堵塞。
- 将线程的运行状态重新定义为就绪状态。
- 相当于让cpu重新调度,但是礼让不一定成功!看cpu
详细解释
yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。
线程强制执行
- Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
- 类似于插队,让被强制执行的线程执行完毕后,才可以允许其他线程进行。
package com.szw;
public class ThreadJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程插队"+i);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadJoin threadJoin = new ThreadJoin();
Thread thread = new Thread(threadJoin);
thread.start();
//主线程
for (int i = 0; i < 500; i++) {
if (i==200){
thread.join();//强行让线程执行
System.out.println("线程执行完毕,主线程才开始~~~~~~~~~~~~~~~~~~~~~~~");
}
System.out.println("主线程"+i);
}
}
}
线程状态
- NEW
尚未启动线程处于此状态。
- Runnable
在Java虚拟机中执行的线程处于此状态。
- BLOCKED
被阻塞监视等待锁定的线程,就处于此状态。
- WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
- TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
- TERMINATED
已经退出的线程处于此状态;
package com.szw;
//线程状态
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{//这里的意思就是让线程运行5s
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("------------");
});
//观察状态
Thread.State state = thread.getState();
System.out.println("未启动的线程状态"+state);
//启动以后的状态
thread.start();
state = thread.getState();
System.out.println("启动以后的线程状态"+state);
while (state!=thread.getState().TERMINATED){//线程状态不是停止的时候
for (int i = 0; i < 5; i++) {
thread.sleep(1000);//每隔一秒输出线程的状态
state = thread.getState();
System.out.println(state);
}
}
System.out.println("线程停止~~~");
}
}
线程优先级
优先级具有随机性:一般优先级较高的线程先执行run()方法,但是这个不能说的但肯定,因为线程的优先级具有 “随机性”也就是较高线程不一定每一次都先执行完。
package com.szw;
//线程优先级
public class ThreadPriority {
public static void main(String[] args) {
//主线程
System.out.println("main"+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
t1.setPriority(2);//2
t2.setPriority(3);//3
t3.setPriority(Thread.NORM_PRIORITY);//5
t4.setPriority(Thread.MAX_PRIORITY);//10
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
守护线程daemon
- 线程分为用户线程和守护线程
- 虚拟机必须保护用户线程完成
- 虚拟机不用等待守护线程完毕
通俗易懂的就是说:守护线程是维护用户线程的,等用户线程跑完之后,守护线程也会停止。
package com.szw;
public class ThreadDaemon {
public static void main(String[] args) {
Gad gad = new Gad();
You you = new You();
Thread thread = new Thread(gad);
thread.setDaemon(true);
thread.start();
//启动你
new Thread(you).start();
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("快乐的每一天~~"+i);
}
}
}
class Gad implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保护你~~");
}
}
}
多线程暴露的问题
三人买票问题
使用多线程三人买票,运行输出发现票的数据严重不正确。
package com.szw;
public class Thread03 implements Runnable {
//定义票数有10张
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
//模拟延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums + "票");
ticketNums--;
}
}
public static void main(String[] args) {
Thread03 thread03 = new Thread03();
new Thread(thread03, "小明").start();
new Thread(thread03, "小孩").start();
new Thread(thread03, "黄牛党").start();
}
}

银行取钱问题
我和妻子同时取钱:出现错误。
package com.szw;
import com.sun.org.apache.bcel.internal.generic.NEW;
public class UnSafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(100,"结婚基金");
new Drawing(account,100,"你").start();
new Drawing(account,100,"妻子").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 drawMoney;//取了多少钱
int nowMoney; //剩余的钱
public Drawing(Account account,int drawMoney,String name){
super(name);
this.account = account;
this.drawMoney = drawMoney;
}
@Override
public void run() {
synchronized(account){
//判断有没有钱
if (account.money-drawMoney < 0){
System.out.println(Thread.currentThread().getName()+"钱不够了,取不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内的余额 = 余额 - 你取的钱
account.money = account.money - drawMoney;
//你手里的钱
nowMoney = nowMoney + drawMoney;
System.out.println(account.name+"余额为"+account.money);
System.out.println(this.getName()+"手里的钱"+nowMoney);
System.out.println(account.name+"余额为"+account.money);
System.out.println("------------------------------");
}
}
}

线程同步
同修饰私有属性的关键词private一样,线程同步也有修饰词synchronized,加上该修饰词后,只允许一个对象去操作,每个对象有一把锁,当对象访问时,把锁加上,后面的进程就会形成阻塞。
synchronized有synchronized方法,和synchronized块。
缺陷:若将一个大的方法申明为synchronized将会影响效率。
注意:synchronized块的使用方法是:
synchronized(obj){
//这里的obj是需要锁的对象,为增删改的对象
}
我们在上面的取钱的问题上加上,synchronized修饰,问题就修改了。

synchronized方法上使用的是:
private synchronized void buy(){
}
//这里不需要指明被锁的对象,默认是this,就是这个方法的对象本身
死锁
解释: 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形.某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
通俗的讲:就是两个线程运行时,都需要对面的资源进行完成指令,但是都等待对方的资源释放,从而形成停止执行的情况。
package com.szw;
public class DeadLock {
public static void main(String[] args) {
Makeup makeup = new Makeup(0,"白雪公主");
Makeup makeup1 = new Makeup(1,"灰姑娘");
makeup.start();
makeup1.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Makeup extends Thread{
int choice;//选择
String girlName;//姓名
Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
//化妆需要的镜子和口红都只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
@Override
public void run() {
makeup();
}
private void makeup(){
if (choice == 0){
synchronized (lipstick){
System.out.println(this.girlName+"获得了口红");
synchronized ((mirror)){
System.out.println(this.girlName+"获得了镜子");
}
}
}else {
synchronized (mirror){
System.out.println(this.girlName+"获得了镜子");
synchronized ((lipstick)){
System.out.println(this.girlName+"获得了口红");
}
}
}
}
}
可以运行代码,发现两个对象都没有继续获得镜子或者口红,这里修改的方法就是,分别把0获取镜子的线程同步方法分开。1的口红与镜子分开。这样实现了两个对象不会产生抱死的状态。
Lock
和synchronized功能一样。
这里写一下加lock锁以后的取票问题,注意的是用lock锁有加锁解锁的过程,写在try...finally...中
package com.szw;
import java.util.concurrent.locks.ReentrantLock;
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 ReentrantLock lock = new ReentrantLock();
private int ticketNum = 10;
boolean flag = true;
@Override
public void run(){
while (true){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
privatevoid buy() throws InterruptedException {
try {
lock.lock();//加锁
//判断是否有票
if (ticketNum<=0){
flag = false;
return;
}
//模拟买票
Thread.sleep(100);
//拿到票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNum--);
}finally {
//解锁
lock.unlock();
}
}
}
线程协作
线程通信
生产消费者模式

package com.szw;
//测试:生产者消费者模型->利用缓冲区解决:管程法
//生产者、消费者、产品、缓冲区
public class TestPc {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread {
//缓冲区
SynContainer container;
public Producer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
container.push(new Chicken(i));
System.out.println("生产了" + i + "只鸡");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Consumer extends Thread {
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
System.out.println("消费了-->" + container.pop().id + "");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//产品
class Chicken {
int id;
Chicken(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer {
//需要一个容器大小
Chicken[] chickens = new Chicken[10];
int Count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken) throws InterruptedException {
if (Count == chickens.length) {
//如果有10只鸡,就先停止生产,让消费者消费
this.wait();
}
chickens[Count] = chicken;
Count++;
//通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop() throws InterruptedException {
if (Count == 0) {
//通知生产者生产,先停止消费
this.wait();
}
//有鸡:就进行消费
Count--;
Chicken chicken = chickens[Count];
this.notifyAll();//唤醒全部线程
return chicken;
}
}

package com.szw;
//测试生产消费者问题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();
}
}
}
//产品,TV
class TV {
//演员表演,观众等待
//观众观看,演员等待
String voice;
boolean flag = true;//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;
}
}
线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
◆提高咆应速度(减少了创建新线程的时间)
◆降低资源消耗(重复利用线程池中线程,不需要每次都创建)
◆便于线程管理
网友评论