Java线程入门
Thread线程的意思
0、绪论
- 分好主次

- 线程执行的顺序和主线程的区别

1、创建线程
三种创建方式
-
继承 Thread 类
-
实现runnable接口
-
实现Callable接口

1.1、方式一、继承Thread类
步骤:
- 继承Thread类:这个类就被开了一个线程
- 重写run()方法:里面写线程的具体实现
- 类的实例调用:.start() 开启线程
public class Thread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我是线程1"+i);
}
}
public static void main(String[] args) {
new Thread01().start();
for (int i = 0; i < 1000; i++) {
System.out.println("我是main线程"+i);
}
}
}
1.2、方式二、实现runnable 接口
步骤:
- 实现runnable接口
- 实现run方法:方法体还是线程具体的执行
- 创建类
- 使用 new Thread(实体类).start();开启线程!
public class RunnableDemo01 implements Runnable {
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("我在面试阿里----------"+i);
}
}
public static void main(String[] args) {
RunnableDemo01 runnableDemo01 = new RunnableDemo01();
new Thread(runnableDemo01).start();
for (int i = 0; i < 1000; i++) {
System.out.println("腾讯在求我去就职-------"+i);
}
}
}

1.3、方法三:实现 Callable接口
- 了解即可
- 实现 callable接口
- 实现 call方法 :类似于run方法
- 使用线程池:用多少线程分多少
- service调用submit();方法提交
public class TestCallable implements Callable<Boolean> {
String url;
String name;
public TestCallable(String url, String name) {
this.url = url;
this.name = name;
}
public Boolean call() throws Exception { //可以抛出异常,而且有返回值
new DownWebUtil().down(url, name);
System.out.println("下载了:"+name);
return true;
}
public static void main(String[] args) throws Exception {
SslUtil.ignoreSsl();
TestCallable t1 = new TestCallable("https://tse1-mm.cn.bing.net/th?q=%e8%b6%b3%e7%90%83&w=120&h=120&c=1&rs=1&qlt=90&cb=1&dpr=1.5&pid=InlineBlock&mkt=zh-CN&adlt=strict&t=1&mw=247", "t1.jpg");
TestCallable t2 = new TestCallable("https://tse2-mm.cn.bing.net/th?q=%e8%a7%86%e9%a2%91&w=120&h=120&c=1&rs=1&qlt=90&cb=1&dpr=1.5&pid=InlineBlock&mkt=zh-CN&adlt=strict&t=1&mw=247", "t2.jpg");
TestCallable t3 = new TestCallable("https://tse4-mm.cn.bing.net/th?q=%e9%9f%b3%e4%b9%90&w=120&h=120&c=1&rs=1&qlt=90&cb=1&dpr=1.5&pid=InlineBlock&mkt=zh-CN&adlt=strict&t=1&mw=247", "t3.jpg");
ExecutorService service = Executors.newFixedThreadPool(3);
Future<Boolean> rs1 = service.submit(t1);
Future<Boolean> rs2 = service.submit(t2);
Future<Boolean> rs3 = service.submit(t3);
Boolean aBoolean = rs1.get();
Boolean aBoolean2 = rs2.get();
Boolean aBoolean3 = rs3.get();
service.shutdown();
}
}
class DownWebUtil {
public static void down(String url, String name) throws Exception {
FileUtils.copyURLToFile(new URL(url), new File(name));
}
}
小结:

2、Lambda表达式
- 前提必须式函数式接口:
- 什么是函数式接口: 只有一个方法,也就是规则的接口,像是函数只有一个表达式!
Lambda表达式发展史:
- 实现类
- 静态内部类
- 局部内部类
- 匿名内部类
- Lambda表达式
public class LambdaDemo01 {
//1、最原始的即使都在外面定义
//2、静态内部类
//3、局部内部类
//4、匿名内部类
//5、lambda表达式
public static void main(String[] args) {
Like like = a -> System.out.println("I like "+ a);
like.like(7);
}
}
//函数式接口
interface Like {
public void like(int a);
}

3、线程的状态
- 就是线程的生老病死

2.1、停止线程
-
不推荐使用JDK的stop();停止
-
也别用已经废弃的 destroy():停止
-
我们推荐使用我们自己定义的一个标志位实现 停止线程

2.2、线程休眠
休眠方法: Thread.sleep(毫秒数);
- 用途:
- 检验线程是否安全,检验是否出现问题
- 模拟倒计时问题

public class ThreadSleep {
public static void main(String[] args) {
while (true) {
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:SS").format(new Date(System.currentTimeMillis())));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
总结:
有个获取当前系统时间的方法和转换时间格式的方法:
new Data(System.currentTimeMillis())获取当前时间的毫秒值!
new SimpleDataFormat("yyyy-MM-DD HH:mm:ss").format(时间)
记住常用格式:
| 符号 | 含义 |
|---|---|
| y | 年 |
| M | 月 |
| d | 天 |
| H | 时 |
| m | 分 |
| s | 秒 |
分大小写!
SimpleDateFormat函数语法:
G 年代标志符
y 年
M 月
d 日
h 时 在上午或下午 (1~12)
H 时 在一天中 (0~23)
m 分
s 秒
S 毫秒
E 星期
D 一年中的第几天
F 一月中第几个星期几
w 一年中第几个星期
W 一月中第几个星期
a 上午 / 下午 标记符
k 时 在一天中 (1~24)
K 时 在上午或下午 (0~11)
z 时区
————————————————
版权声明:本文为CSDN博主「gubaohua」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/gubaohua/article/details/575488
2.4、线程插队
thread.join();方法
强行插队,并且自己执行完才能让别的线程执行
public class ThreadJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 2000; i++) {
System.out.println("线程VIP来了"+i);
}
}
//线程插队
public static void main(String[] args) throws InterruptedException {
ThreadJoin join = new ThreadJoin();
Thread thread = new Thread(join);
thread.start();
//主线程
for (int i = 0; i < 500; i++) {
if (i == 50) {
thread.join();
}
System.out.println("main--------------"+i);
}
}
}
2.5、线程礼让
- 注意礼让不一定成功,还要开CPU选择执行谁
方法:yield();
public class ThreadYield {
public static void main(String[] args) {
Yield yield = new Yield();
new Thread(yield, "a").start();
new Thread(yield, "b").start();
}
}
class Yield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"->start");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"->end");
}
}
2.6、线程的状态观测
- 都是Thread类中的一些常量
- 下面是这个常量监视的线程状态

2.7、线程优先级
线程优先级默认是 5 ,
MIN_PRIORITY :1 MAX_PRIORITY : 10
方法:
- getPriority() 获取线程优先级
- setPriority(int) 设置优先级

package com.bing.线程状态;
/**
* @date 2020/4/20- 21:30
*/
public class TestPriority {
//测试线程优先级
public static void main(String[] args) {
//先打印主线程的优先级
System.out.println(Thread.currentThread().getName() +"-->" + Thread.currentThread().getPriority());
Priority priority = new Priority();
Thread t1 = new Thread(priority);
Thread t2 = new Thread(priority);
Thread t3 = new Thread(priority);
Thread t4 = new Thread(priority);
Thread t5 = new Thread(priority);
t1.start(); //默认是5
t2.setPriority(1);
t2.start();
t3.setPriority(5);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);
t4.start();
t5.setPriority(7);
t5.start();
}
}
class Priority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"-->" + Thread.currentThread().getPriority());
}
}
2.8、守护线程和用户线程
- 什么是守护线程和用户线程?
守护线程一直在守护着java程序,比如说GC垃圾回收线程
用户线程就是用户自己定义的线程比如main()
package com.bing.线程状态;
/**
* @date 2020/4/20- 21:45
*/
public class TestDaemon {
//测试守护线程
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread t1 = new Thread(god);
Thread t2 = new Thread(you);
t1.setDaemon(true); //设置为守护线程
t1.start();
t2.start();
}
}
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("上帝守护着你!");
}
}
}
class You implements Runnable {
@Override
public void run() {
//人生不过3万天
for (int i = 0; i < 30000; i++) {
System.out.println("活着的意义就是,当你后首过往时,不因虚度年华而悔恨,也不因碌碌无为而羞耻!"+i);
}
System.out.println("快乐的死去!");
}
}
4、线程安全
- 很重要,面试会问线程为什么不安全,,有什么解决方案?
4.1、三种不安全的例子
- 买票的例子
理解:广义上讲相当于许多个人一起去买票,一个人把某一张票拿走了,但是买票器没有即使更新数据,导致两个人拿到同一张票!,也就是程序执行是同时的,下面结合代码讲解
package com.bing.sfy;
/**
* @date 2020/4/21- 8:26
*/
public class BuyTickets {
public static void main(String[] args) {
Buy buy = new Buy();
new Thread(buy,"帅气的我").start();
new Thread(buy,"善良").start();
new Thread(buy,"正直").start();
}
}
class Buy implements Runnable {
private boolean flag = true;
private int tickets = 10;
@Override
public void run() {
while (flag) {
//暴露问题让线程等3毫秒,因为CAU太快,让他们三个公平竞争!
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//我们让三个人都排队到这个,这时候flag还是上次的flag
//1、为什么会买到同一张票?tickets还没有--,第二个线程就买了
//2、为什么会买到负数票,因为用的是上次的flag
buy();
}
System.out.println("票没了");
}
private void buy() {
if (tickets <= 0) {
flag = false;
}
System.out.println(Thread.currentThread().getName()+"获得了票"+tickets--);
}
}
- 2、银行取钱
比较重要,两个人一起去银行取一张卡上的钱,银行肯定要线程安全,不然会出现重取,多取的情况
public class TestBank {
public static void main(String[] args) {
Counter marry = new Counter("结婚基金", 100);
DrawMoney you = new DrawMoney(marry, 50, "You");
DrawMoney girlFriend = new DrawMoney(marry, 100, "girlFriend");
you.start();
girlFriend.start();
}
}
class Counter {
String name;
int money;
public Counter(String name, int money) {
this.name = name;
this.money = money;
}
}
class DrawMoney extends Thread {
private Counter counter;
private int getMoney;
private int curMoney;
private boolean flag = true;
public DrawMoney(Counter counter, int getMoney, String name) {
super(name);
this.counter = counter;
this.getMoney = getMoney;
}
@Override
public void run() {
if (flag) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
drawing();
}
System.out.println(Thread.currentThread().getName()+"钱莫得了,快去赚!");
}
private void drawing() {
if (counter.money - getMoney <= 0) {
flag = false;
}
counter.money = counter.money - getMoney;
curMoney = curMoney + getMoney;
System.out.println(counter.name+"余额为" + counter.money);
System.out.println(Thread.currentThread().getName()+" 取的钱"+getMoney);
System.out.println(Thread.currentThread().getName()+"手里的钱:"+curMoney);
}
}
- 3、l集合线程不安全
最后的size不会到10000,因为会有两个线程一起进入集合
public class TestList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
System.out.println(list.size());
}
}
4.2、synchronized 同步
- 也叫同步代码块
记住:什么在变化锁什么!!别锁错了!
每个对象都有一把锁
1、买票程序锁方法,其实就是锁this
class Buy implements Runnable {
private boolean flag = true;
private int tickets = 10;
@Override
public synchronized void run() {
while (flag) {
//暴露问题让线程等3毫秒,因为CAU太快,让他们三个公平竞争!
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//我们让三个人都排队到这个,这时候flag还是上次的flag
//1、为什么会买到同一张票?tickets还没有--,第二个线程就买了
//2、为什么会买到负数票,因为用的是上次的flag
buy();
}
System.out.println("票没了");
}
2、取钱程序锁 counter
而不是锁drawing
- 小错误,我们线程的start();方法都是让线程去执行线程体重的run方法,run方法执行完线程结束!
- 一般run方法会调用其他方法,比如这个方法中的取钱draw()方法!
package com.bing.sfy;
/**
* @date 2020/4/21- 8:56
*/
public class TestBank {
public static void main(String[] args) {
Counter marry = new Counter("结婚基金", 1000);
DrawMoney you = new DrawMoney(marry, 50, "You");
DrawMoney girlFriend = new DrawMoney(marry, 100, "girlFriend");
you.start();
girlFriend.start();
}
}
class Counter {
String name;
int money;
public Counter(String name, int money) {
this.name = name;
this.money = money;
}
}
class DrawMoney extends Thread {
private Counter counter;
private int getMoney;
private int curMoney;
private boolean flag = true;
public DrawMoney(Counter counter, int getMoney, String name) {
super(name);
this.counter = counter;
this.getMoney = getMoney;
}
@Override
public void run() {
if (flag) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
drawing();
} else {
System.out.println(Thread.currentThread().getName()+"钱不够取不了!!");
}
}
private void drawing() {
synchronized (counter) {
if (counter.money - getMoney <= 0) {
flag = false;
return;
}
counter.money = counter.money - getMoney;
curMoney = curMoney + getMoney;
System.out.println(counter.name+"余额为" + counter.money);
System.out.println(Thread.currentThread().getName()+" 取的钱"+getMoney);
System.out.println(Thread.currentThread().getName()+"手里的钱:"+curMoney);
}
}
}
3、集合线程不安全
public class TestList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
//这里为什么会让主线程睡三秒,是必须让上面那个线程执行完毕我们在差list的size才准确!
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
错误:我们必须让主线程睡三秒才能测出准确结果,主线程和上面那个线程共同竞争CPU的使用权!
4.3、死锁
- 为什么会产生死锁?
我们在synchronize 的代码块中又加了另一把锁,只有在synchroinze代码块执行完才会释放自己的锁,所以当另一个对象操作这个代码块里面的锁的对象的时候就会出现死锁!
package com.bing.lock;
/**
* @date 2020/4/22- 8:50
*/
public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup("灰姑凉", 1);
Makeup girl2 = new Makeup("白雪公主", 0);
girl2.start();
girl1.start();
}
}
class Mirrors {
}
class Lipstick {
}
class Makeup extends Thread{
static Mirrors mirrors = new Mirrors();
static Lipstick lipstick = new Lipstick();
private String name;
int choose ;
@Override
public void run() {
making();
}
public Makeup(String name, int choose) {
this.name = name;
this.choose = choose;
}
private void making () {
if (choose == 1) {
synchronized (mirrors) {
//执行完代码块中的代码。锁就会被移除
System.out.println(name+" 镜子是我的!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lipstick) {
System.out.println(name+" 我拿到了口红!");
}
}
} else {
synchronized (lipstick) {
System.out.println(name+" 口红是我的!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirrors) {
System.out.println(name+" 我拿到了镜子!");
}
}
}
}
}
总结:也就是两个对象互相把统一资源锁了
4.4、luck 锁
我们一般用它的实现类reentrantlock,一般定义成私有的,并且只有一把锁!
与 synchronized 不同的是它需要自己释放,一般写在finally中
package com.bing.gaoji;
import java.util.concurrent.locks.ReentrantLock;
/**
* @date 2020/4/22- 10:02
*/
public class TestLock {
public static void main(String[] args) {
BuyTickets buyTickets = new BuyTickets();
new Thread(buyTickets).start();
new Thread(buyTickets).start();
new Thread(buyTickets).start();
}
}
class BuyTickets implements Runnable{
private int tickets = 10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
//一般建议写在try--finally中把不安全的代码全部放在try中,相当于锁了这些代码
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(tickets--);
} else {
break;
}
} finally {
lock.unlock();
}
}
}
}
总结:
我们一般把锁放在try---finall中,锁的是代码块,而synchronized锁的是对象!
相当于是显示的锁!
5、线程通信
什么叫线程通信呢?就是线程依据另一个线程的结果来决定自己是否执行时!!跟上面的区别
就是两个线程操作同一资源的问题,比如说你去买炸鸡,店里没有,要让师傅做,做好了通知你可以买了,如果有炸鸡就可以直接买,这时候有个缓冲区,我们最多同时存储10份炸鸡!
5.1、郝斌透彻例子
必须理解synchronized锁的概念,锁的是this对象,郝斌那个讲的比较透彻,这里分析一下它的代码
public class TestPCC {
public static void main(String[] args) {
SysContainer container = new SysContainer();
Producer2 producer2 = new Producer2(container);
Consumer2 consumer2 = new Consumer2(container);
producer2.start();
consumer2.start();
}
}
class SysContainer {
private char[] arr = new char[6];
private int count;
public synchronized void push(char a) {
if (count == arr.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
arr[count] = a;
count++;
// System.out.printf("正在生产第%d个产品,该产品是%c\n",count,a);
this.notify();
}
public synchronized char pop() {
if (count == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
char t = arr[count];
// System.out.printf("消费consume第%d个产品,消费的产品是%c\n", count + 1, t);
this.notify();
return t;
}
}
class Producer2 extends Thread{
SysContainer sysContainer;
public Producer2(SysContainer container) {
sysContainer = container;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
char ch = (char)('a' + i);
sysContainer.push(ch);
System.out.printf("正在生产第%d个产品,该产品是%c\n", i + 1, ch);
}
}
}
class Consumer2 extends Thread{
SysContainer sysContainer;
public Consumer2(SysContainer container) {
sysContainer = container;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// try {
// Thread.sleep(200);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
char pop = sysContainer.pop();
System.out.printf("消费consume第%d个产品,消费的产品是%c\n", i + 1, pop);
}
}
}
分析:其实就是在线程中使用了组合的方式,让两个线程操作同一个资源,也就是我们去买炸鸡时的柜台时卖家和卖家共享的,这时候两个线程就相互影响,其实最典型的就是这种生产者和消费者的例子!
- 1、run方法才是真正的线程执行体,如果被锁了则里面的代码就被所在一起,除非被wait中断
- 如果想下面代码这样写肯定会出错,打印方法不能再外面。会出现这样的问题

所以打印器要和代码锁在一起!
- 吃鸡问题
package com.bing.gaoji;
/**
* @date 2020/4/22- 11:38
*/
public class TestPC {
public static void main(String[] args) {
Storage storage = new Storage();
Producer producer = new Producer(storage);
Consumer consumer = new Consumer(storage);
producer.start();
consumer.start();
}
}
class Producer extends Thread{
private Storage str;
public Producer(Storage str) {
this.str = str;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
str.push(new Chicken(i));
System.out.printf("生产了第%d只鸡\n", i + 1);
}
}
}
class Consumer extends Thread{
private Storage str;
public Consumer(Storage str) {
this.str = str;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
// try {
// Thread.sleep(200);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
str.pop();
System.out.printf("consume%d只鸡\n", i+1);
}
}
}
class Storage {
Chicken[] goods = new Chicken[6];
int count;
public synchronized void push(Chicken chicken) {
//判断容器是否满了,满了就通知消费者消费
if (count == goods.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
goods[count++] = chicken;
this.notifyAll();
}
public synchronized Chicken pop() {
if (count == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
Chicken chicken = goods[count];
this.notifyAll();
return chicken;
}
}
class Chicken {
int id;
public Chicken(int id) {
this.id = id;
}
}

5.2、标记方法
- 只有一个的时候会用吧
package com.bing.gaoji;
/**
* @date 2020/4/22- 21:52
*/
public class TestTc2 {
public static void main(String[] args) {
TV tv = new TV();
new Actor(tv).start();
new Audience(tv).start();
}
}
class TV {
boolean flag = false;
String name;
public synchronized void player(String voice) {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
flag = !flag;
name = voice;
System.out.println("演员正在表演:"+voice);
}
public synchronized void watching () {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
flag = !flag;
System.out.println("观众正在观看:"+name);
}
}
class Actor extends Thread{
//演员只管表演
TV tv;
public Actor(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if ((i & 1) == 0) {
tv.player("快乐大本营");
} else {
tv.player("朗读者");
}
}
}
}
class Audience extends Thread{
//演员只管表演
TV tv;
public Audience(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watching();
}
}
}
理解:
有点像代理模式,就是必须两个线程操作同一个对象!
6、线程池
相当于:数据库连接池,节省资源,用着方便
package com.bing.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @date 2020/4/22- 22:30
*/
public class TestPool {
public static void main(String[] args) {
//创建一个线程池,传参放7个线程
ExecutorService service = Executors.newFixedThreadPool(7);
bing bing = new bing();
service.submit(bing);
service.submit(bing);
service.submit(bing);
service.submit(bing);
service.submit(bing);
service.shutdown();
}
}
class bing implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行成功");
}
}

浙公网安备 33010602011771号