1.相关概念:
1.并发和并行:
并行:多个CPU执行多个任务
并发:单个CPU执行多个任务
2.程序:具有一些特定功能的静态的代码段
3.进程:程序的动态运行过程
特点:
动态性:动态执行过程
独立性:进程之间户型独立
并发性:多个进程可以交替执行
4.线程:是进程的一部分
一个进程至少包含一个线程
现成本质是进程的一条执行路径
计算机执行 并发的!
程序 都支持多线程!
2.多线程的实现方式(3种)
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用run方法() |
一:继承Thread*****
创建一个类 ,让他继承Thread类(不是抽象类) Thread类里实现了Runnable接口 ,此接口只有一个run方法。并且被Thread重写了,所以,自己创建的类要 手动重写 run方法
自己创建的类 并且重写run方法
public class MyThread extends Thread{
@Override
public void run() {
// super.run();
for (int i = 0; i < 10; i++) {
System.out.println("MYThread" + i);
}
}
}
测试类开启多线程
public class Demo {
public static void main(String[] args) {
MyThread myThread1 = new MyThread();
// myThread1.run();这里只是调用重写后的方法, 所以不会启动多线程
// 启动线程
myThread1.start();//启动该线程
// myThread1.start();
MyThread thread = new MyThread();
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("main" + i);
}
}
}
注意:
run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
二:接口方式创建多线程*****
Thread构造方法
方法名 | 说明 |
---|---|
Thread(Runnable target) | 分配一个新的Thread对象 |
Thread(Runnable target, String name) | 分配一个新的Thread对象 |
这里实现接口,我们直接创建一个类实现Runnable接口 ,重写里边的run方法
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyRunable" + i);
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
// mr.run();
// 不能 start()
// Thread thread = new Thread(mr);
Thread thread = new Thread(mr,"线程1");//可以通过有参构造直接给该线程取名字
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("main" + i);
}
}
}
三:实现Callable接口 用的很少 ,编写太复杂
方法名 | 说明 |
---|---|
V call() | 计算结果,如果无法计算结果,则抛出一个异常 |
FutureTask(Callable<V> callable) | 创建一个 FutureTask,一旦运行就执行给定的 Callable |
V get() | 如有必要,等待计算完成,然后获取其结果 |
/*
* 里边要传入泛型 要有返回值 ,类型是传递的泛型
* */
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// return null;
for (int i = 0; i < 10; i++) {
System.out.println("表白第" + (i+1) + "次");
}
return "答应你啦!";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
// 传递
FutureTask ft = new FutureTask(mc);
Thread thread = new Thread(ft);
// System.out.println(1);
// System.out.println(ft.get());阻塞
// System.out.println(2);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("main" + i);
}
// 返回值
//
System.out.println(1);
System.out.println(ft.get());
System.out.println(2);
}
}
一般都要用第一种继承和实现Runnable接口
四:创建多线程简单方式:
用lanbda表达式在主方法,直接创建
public class Demo {
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("子线程1=》"+i);
}
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("子线程2=》"+i);
}
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("子线程3=》"+i);
}
}).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println("子线程4=》"+i);
}
}).start();
for (int i = 0; i < 10; i++) {
System.out.println("main=>" + i);
}
}
}
2.设置和获取线程名称
方法名 | 说明 |
---|---|
void setName(String name) | 将此线程的名称更改为等于参数name |
String getName() | 返回此线程的名称 |
Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
也可以通过 构造函数 直接给名字
注意:Thread currentThread 可以直接通过类名直接调用
3.线程休眠状态
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
/*System.out.println("睡觉前");
Thread.sleep(3000);
System.out.println("睡醒了");*/
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
4.进程优先级 了解
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10 |
/*
* 线程的优先级
* 【1-10】 之间
* 默认是5
*
* 优先级高,不代表一定获得CPU时间(执行权)
*
* */
public class Demo {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "=" + i);
}
},"子线程B");
thread.setPriority(1);//设置优先权
// System.out.println(thread.getPriority());//得到优先级
Thread thread1 = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "=" + i);
}
},"子线程A");
thread1.setPriority(10);
thread.start();
thread1.start();
}
}
5.守护线程
方法名 | 说明 |
---|---|
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("女神");
t2.setName("备胎");
//把第二个线程设置为守护线程
//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
t2.setDaemon(true);
t1.start();
t2.start();
}
}
//把第二个线程设置为守护线程
//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.不一定会立即停止
6.线程安全问题
安全问题出现的条件
-
是多线程环境
-
有共享数据
-
有多条语句操作共享数据、
解决方案:
1.同步代码块
2.同步方法
3.Lock锁
1.同步代码块
synchronized(任意对象) {
多条语句操作共享数据的代码
}
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
同步的好处和弊端
好处:解决了多线程的数据安全问题
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
public class SellTicket implements Runnable {
private int tickets = 100;
private Object obj = new Object();
@Override
public void run() {
while (true) {
synchronized (obj) { // 对可能有安全问题的代码加锁,多个线程必须使用同一把锁
//t1进来后,就会把这段代码给锁起来
if (tickets > 0) {
try {
Thread.sleep(100);
//t1休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//窗口1正在出售第100张票
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--; //tickets = 99;
}
}
//t1出来了,这段代码的锁就被释放了
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
2.同步方法
同步方法:就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数) {
方法体;
}
3.Lock锁 (手动创建释放)
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
- ReentrantLock构造方法
ReentrantLock() | 创建一个ReentrantLock的实例 |
void lock() | 获得锁 |
void unlock() | 释放锁 |
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Ticket implements Runnable{
private int count = 100;//100张票
// private Object obj = new Object();
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
System.out.print("");
// 加锁
lock.lock();//手动加锁
if(count > 0){
count--;
// try {
// Thread.sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + count + "张");
}else {
lock.unlock(); //手动释放
break;
}
}
}
}
7.死锁问题
死锁:
* 多个线程互相持有对方想要的锁标记而处于僵持的一种状态
*
* 产生原因:
* 1.抢占式
* 2.锁的嵌套
* 3.锁不释放,对方无法抢夺,处于循环等待过程
*
* 解决:
* 不要写锁嵌套
8.生产者 消费者****
所谓生产者消费者问题,实际上主要是包含了两类线程:
一类是生产者线程用于生产数据
一类是消费者线程用于消费数据
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
/*
* 创建两个线程
* 吃货线程
* 生成线程
* 交替打印
* */
public class Demo {
public static void main(String[] args) {
Object obj = new Object();
int count = 1000;
// 吃货线程
new Thread(()->{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
for (int i = 1; i <= count; i++) {
obj.notifyAll();
System.out.println(Thread.currentThread().getName() + "第" + i + "包子");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"吃了").start();
// 生成线程
new Thread(()->{
synchronized (obj) {
for (int i = 1; i <= count; i++) {
obj.notifyAll();
System.out.println(Thread.currentThread().getName() + "第" + i + "个包子");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"生产").start();
}
}
9.线程池****
线程池: * 好处: 1.帮助我们维护了一些线程的创建和管理 * 节省了创建 线程和销毁的资源开销 * 2.线程的重复利用: * 线程执行完任务后,不是销毁,而是归还线程池,以备下次任务使用
public class Demo {
public static void main(String[] args) {
// 创建一个线程池
ExecutorService pool = Executors.newCachedThreadPool();
// show1(pool);
// 自动创建 多个线程 进行打印程序
for (int i = 0; i < 500; i++) {
// 提交 Lanbdm 表达式
pool.submit(()->{
System.out.println(Thread.currentThread().getName() + "执行");
});
}
}
public static void show1(ExecutorService pool) {
// 线程池的常用方法 自己定义的类 为了创建多个线程
MyRunnable myRunnable = new MyRunnable();
// 提交
pool.submit(myRunnable);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 正在执行!");
System.out.println(Thread.currentThread().getName() + " 执行完毕!");
}
});
pool.submit(()->{
System.out.println(Thread.currentThread().getName() + " 正在执行!");
System.out.println(Thread.currentThread().getName() + " 执行完毕!");
});
}
}
线程池 固定开启线程个数
public class Demo {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
pool.submit(()->{
System.out.println(Thread.currentThread().getName() + ":执行");
});
}
// pool.shutdown();不建议
}
}
自定义线程参数
七个参数 五个必回,两个有默认值
1.核心线程数 3
2.最大线程数 5
3.空闲线程数 空闲时间 2, 销毁
4.空闲线程数空闲时间单位 TimeUnit.SECONDS,
5.阻塞队列 new ArrayBlockingQueue<>(10)
/*
* 1.来了任务后,交给核心线程去处理
* 2.当任务超过了核心线程数:将任务放入阻塞队列
* 3.核心线程在忙,阻塞队列满了 此时启动临时线程
*
*最多十五个 阻塞队列加上最大线程数
*
* */
/*
* 1.来了任务后,交给核心线程去处理
* 2.当任务超过了核心线程数:将任务放入阻塞队列
* 3.核心线程在忙,阻塞队列满了 此时启动临时线程
*
*最多十五个 阻塞队列加上最大线程数
*
* */
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(3,
5,2,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
// 1核心线程数 3
// 2最大线程数 5
// 3空闲线程数 空闲时间 2, 销毁
// 4空闲线程数空闲时间单位 TimeUnit.SECONDS,
// 5.阻塞队列
// new ArrayBlockingQueue<>(10)
// 如果是 16 会有 RejectedExecutionException拒绝执行异常
for (int i = 0; i < 16; i++) {
pool.submit(()->{
System.out.println(Thread.currentThread().getName());
});
}
}
}