程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
进程则是执行程序的一次执行过程,他是一个动态的概念,一个进程可以有多个线程。是系统资源分配的单位
1、线程的创建-
继承Thread类
// 创建线程方式一:继承Thread类,重写run(),调用start开启线程 // 注意:线程开启不一定立即执行,由CPU调度执行 // 两条线程同时执行 public class TestThread1 extends Thread { @Override public void run() { // run()方法线程体 for (int i = 0; i < 1000; i++) { System.out.println("重写run方法" + i); } super.run(); } public static void main(String[] args) { // 创建一个线程对象 TestThread1 thread1 = new TestThread1(); //调用start()方法开启线程 thread1.start(); for (int i = 0; i < 1000; i++) { System.out.println("main主线程" + i); } } } -
实现Runnable接口
// 线程创建方式二:实现Runnable接口,重写run(),调用start开启线程 public class TestThread2 implements Runnable { @Override public void run() { // run()方法线程体 for (int i = 0; i < 1000; i++) { System.out.println("重写run方法" + i); } } public static void main(String[] args) { // 创建Runable接口的实现类对象 TestThread2 thread2 = new TestThread2(); //创建线程对象,通过线程对象来开启线程,代理。 // Thread thread = new Thread(thread2); // thread.start(); new Thread(thread2).start(); for (int i = 0; i < 1000; i++) { System.out.println("main主线程" + i); } } } -
实现Callable接口,重写Call方法
Callable的好处:可以定义返回值;可以抛出异常。
package com.ls.demo01;
// 多个线程操作同一个对象
// 模拟买火车票的例子
public class TestThread3 implements Runnable {
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
// 模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第" + ticketNums-- + "张票");
}
}
public static void main(String[] args) {
TestThread3 thread3 = new TestThread3();
new Thread(thread3, "学生").start();
new Thread(thread3, "老师").start();
new Thread(thread3, "黄牛党").start();
}
}
发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱。
3、静态代理package com.ls.proxy;
public class StaticProxy {
public static void main(String[] args) {
// 客户端需要租房
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
// 接口
interface Rent {
public void rent();
}
// 真实角色
class Host implements Rent {
@Override
public void rent() {
System.out.println("房东出租房子!");
}
}
// 代理角色(代理对象要代理真实角色)
class Proxy implements Rent {
Host host = new Host();
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
host.rent();
// 代理角色一般都会有一些附属操作
fare();
hetong();
System.out.println("中介替房东出租房子");
}
public void fare() {
System.out.println("中介收取中介费");
}
public void hetong() {
System.out.println("中介和你签合同");
}
}
4、Lambda表达式
函数式接口的定义:任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口。
为什么要使用Lambda?
- 避免匿名内部类定义过多;
- 可以让代码看起来更简洁;
- 去掉了一堆没有意义的代码,只留下核心的逻辑。
public class TestLambda {
public static void main(String[] args) {
UserService service = new UserService() {
@Override
public void show() {
System.out.println("执行了show()...");
}
};
// 使用lambda简化
UserService service1 = () -> {
System.out.println("执行了show()...");
};
service1.show();// 执行了show()...
}
}
5、线程状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aZd7U12B-1637157843636)(D:Typoraimgimage-20211117163257922.png)]
6、停止线程package com.ls.stop;
// 测试线程停止
// 1.建议线程正常停止,利用次数,不建议使用死循环
// 2.建议使用标志位,设置一个标志位
// 3.不要使用stop或者destroy等过时方法
public class TestStop 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) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main" + i);
if (i == 900) {
// 调用stop方法切换标志位,让线程停止
testStop.stop();
System.out.println("线程停止了");
}
}
}
}
7、sleep
模拟网络延时:发大问题的发生行
package com.ls.sleep;
import java.text.SimpleDateFormat;
import java.util.Date;
// 模拟网络延时:发大问题的发生行
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
// 打印当前系统的事件
Date startTime = new Date(System.currentTimeMillis());
while (true) {
Thread.sleep(1000);
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;
}
}
}
}
8、yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转变为就绪状态
- 让cpu重新调度,礼让不一定成功
package com.ls.join;
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 500; i++) {
System.out.println("线程vip来了...");
}
}
public static void main(String[] args) throws InterruptedException {
// 启动自定义线程
TestJoin join = new TestJoin();
Thread thread = new Thread(join);
thread.start();
// 主线程
for (int i = 0; i < 500; i++) {
if (i == 200) {
thread.join();// 插队
}
System.out.println("main主线程" + i);
}
}
}
10、观测线程状态
package com.ls.state;
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("11111111111111111111");
});
// 观察状态
Thread.State state = thread.getState();
System.out.println(state); //NEW
// 观察启动后
thread.start();// 启动线程
state = thread.getState();// 更新状态
System.out.println(state);// RUNNABLE
while (state != Thread.State.TIMED_WAITING) {// 只要线程不终止,就一直输出状态
Thread.sleep(100);
state = thread.getState();
System.out.println(state);// TIMED_WAITING
}
}
}
11、优先级
线程的优先级用数字表示,范围1~10
- Thread.MIN_PRIORITY = 1
- Thread.MAX_PRIORITY = 10
- Thread.NORM_PRIORITY = 5
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,都是看CPU的调度。
package com.ls.state;
public class TestPriority {
public static void main(String[] args) {
// 主线程默认优先级
System.out.println(Thread.currentThread().getName() +"------>"+ Thread.currentThread().getPriority());
MyPriority priority = new MyPriority();
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();
t2.setPriority(6);
t2.start();
t3.setPriority(3);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);// MAX_PRIORITY = 10
t4.start();
t5.setPriority(9);
t5.start();
}
}
class MyPriority implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"------>"+ Thread.currentThread().getPriority());
}
}
12、守护(daemon)线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程(main)执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如:后台记录操作日志,监控内存,垃圾回收等
package com.ls.state;
// 测试守护线程
// 上帝守护你
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);// 默认是false表示用户线程,正常的线程都是用户线程
// 守护线程启动
thread.start();
// 你 用户线程启动
new Thread(you).start();
}
}
class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("上帝守护着你");
}
}
}
class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("一直活着...");
}
// 停止之后守护线程还跑了一会,因为虚拟机停止还需要一些时间
System.out.println("再见,世界*****************************");
}
}
13、同步(重点)
并发:多个线程访问同一个对象
处理多线程问题时,多个线程访问同一个对象,并且某个线程还想修改这个对象,这个时候就需要线程同步,线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面程序使用完毕,下一个线程在使用。
线程同步形成条件:队列 + 锁
队列 + 锁 才能保证线程同步的安全性
- 由于同一进程的多个线程共享同一块存储空间,在带来方便的同时也带来了访问冲突问题,为了保证数据在方法中被访问的正确性,在访问时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,但是存在下列问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题。
用法:synchronized方法和synchronized方法块
同步方法:public synchronized void method(int args){}
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
缺点:
如果将一个很大的方法申明为synchronized会影响效率。
锁的对象就是变化的量,需要增删改的对象
// 买票
public synchronized void buy() throws InterruptedException {
if (ticketNum <= 0) {
flag = false;
return;
}
// 模拟延时,放大问题的发生行
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张票");
}
public static void main(String[] args) throws InterruptedException {
List list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
Thread.sleep(100);
System.out.println(list.size());
}
15、死锁
多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有**“两个以上对象的锁”**时,就可能会发生死锁的现象。
package com.ls.dead;
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 {
// 需要的资源只有一份,用static保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;// 选择
String name;// 使用的人
public MakeUp(int choice, String name) {
this.choice = choice;
this.name = name;
}
@Override
public void run() {
// 使用
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {// 获得口红的锁
System.out.println(Thread.currentThread().getName() + "获得口红的锁");
Thread.sleep(1000);// 1秒钟后获得镜子
synchronized (mirror) {
System.out.println(Thread.currentThread().getName() + "获得镜子的锁");
}
}
} else {
synchronized (mirror) {
System.out.println(Thread.currentThread().getName() + "获得镜子的锁");
Thread.sleep(1000);
synchronized (lipstick) {
System.out.println(Thread.currentThread().getName() + "获得口红的锁");
}
}
}
}
}
解决办法,用完锁立即释放锁。
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {// 获得口红的锁
System.out.println(Thread.currentThread().getName() + "获得口红的锁");
Thread.sleep(1000);// 1秒钟后获得镜子
}
synchronized (mirror) {
System.out.println(Thread.currentThread().getName() + "获得镜子的锁");
}
} else {
synchronized (mirror) {
System.out.println(Thread.currentThread().getName() + "获得镜子的锁");
Thread.sleep(1000);
}
synchronized (lipstick) {
System.out.println(Thread.currentThread().getName() + "获得口红的锁");
}
}
}
产生死锁的条件:
- 互斥条件:一个资源一次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对以获得的资源保持不放;
- 不剥夺条件:进程已获夺的资源,在未使用之前,不能强行剥夺;
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
- 从jdk5开始,java提供了更强大的线程同步机制,通过显示定义同步锁对象来实现同步。同步锁使用Lock充当。
**-ReentrantLock(可重入锁)**类实现了Lock,它拥有与synchronized相同的并发性和内存语义,可显示的加锁,释放锁。
class BuyTicket implements Runnable {
int nums = 10;
// 显示的定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();// 加锁
if (nums > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(nums--);
} else {
return;
}
} finally {
lock.unlock();//解锁
}
}
}
}
synchronized和lock的对比:
- Lock是显示锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放;
- Lock只有代码块锁,synchronized有代码块锁和方法锁;
- 使用lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类);
- Lock > 同步代码块 > 同步方法
应用场景:
- 假设仓库中只能存放一件物品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取出消费;
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止;
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中放入产品为止。
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
- 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,要马上通知消费者消费;
- 对于消费者,在消费之后,要通知消费者消费结束,需要生产新的产品以供消费。
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized可阻止并发更新同一个资源,实现了同步;
- synchronized不能用来实现不同线程之间的消息传递(线程通信)。
Java提供了几个方法解决了线程之间的通信问题
- wait()
- 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
- notify()
- 唤醒一个处于等待状态的线程
sleep 和 wait 区别
- sleep 不释放锁, wait 释放锁
- sleep 是 Thread 的静态方法, wait 是 Object 的实例方法, wait 调用时, 需要在 synchronized 块中调用才行
- sleep 达到时间后, 自动恢复可运行状态, wait 需要其他线程 notify, notifyAll 来唤醒
// 测试生产者消费者问题
// 利用缓冲区解决,管程法
// 生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Productor(synContainer).start();
new Customer(synContainer).start();
}
}
//生产者
class Productor extends Thread {
SynContainer synContainer;
public Productor(SynContainer synContainer) {
this.synContainer = synContainer;
}
// 生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产了" + i + "只鸡");
try {
synContainer.push(new Chicken(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Customer extends Thread {
SynContainer synContainer;
public Customer(SynContainer synContainer) {
this.synContainer = synContainer;
}
// 消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了" + i + "只鸡");
try {
synContainer.pop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//产品
class Chicken {
int id;
public 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) {
// 通知消费者消费,生产等待
this.wait();
}
// 如果没有满,就需要丢入产品
chickens[count] = chicken;
count++;
// 通知消费者消费
this.notify();
}
// 消费者消费产品
public synchronized Chicken pop() throws InterruptedException {
// 判断能否消费
if (count == 0) {
// 等待生产者生产,消费者等待
this.wait();
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
// 消费完了,通知生产者生产
this.notify();
return chicken;
}
}
方式二:标志位解决
// 测试生产者消费者问题,标志位解决
public class TestPC2 {
public static void main(String[] args) {
Chicken chicken = new Chicken();
new Productor(chicken).start();
new Customer(chicken).start();
}
}
//生产者
class Productor extends Thread {
Chicken chicken;
public Productor(Chicken chicken) {
this.chicken = chicken;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
if (i % 2 == 0) {
this.chicken.push("鸡肉");
} else {
this.chicken.push("鱼肉");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//消费者
class Customer extends Thread {
Chicken chicken;
public Customer(Chicken chicken) {
this.chicken = chicken;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
chicken.pop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 产品
class Chicken {
// 生产者生产,消费者等待
//消费者消费,生产者等待
String name;// 生产产品
boolean flag = true;
// 生产
public synchronized void push(String name) throws InterruptedException {
if (!flag) {
this.wait();
}
System.out.println("生产者生产了" + name);
//通知消费者消费
this.notifyAll();
this.name = name;
this.flag = !this.flag;//取反
}
//消费
public synchronized void pop() throws InterruptedException {
if (flag) {
this.wait();
}
System.out.println("消费者消费了" + name);
// 通知生产者生产
this.notifyAll();
this.flag = !this.flag;//取反
}
}
18、使用线程池
- 背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池子中。可以避免频繁创建和销毁、实现重复利用。
- 好处
- 提高响应速度,减少了创建新线程的时间
- 降低资源消耗,重复利用线程池,不需要每次都创建
- 便于线程管理
使用线程池
- jdk5.0起提供了线程池相关的API:ExecutorService和Executors
- ExecutorService:真正的线程池接口。
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
// 创建服务,创建线程池
//newFixedThreadPool 参数为线程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//关闭连接
service.shutdown();
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}



