多线程学习笔记
线程简介 为什么要有线程
用一只手做事情显然是效率比较低的 ,两只手,再来一个人 合作去完成某一件事情显然就会效率高了。
就是在程序执行中 出现了 供需不平衡
了
为了使程序更快,更高,更强,所以需要多线程。多个线程操作一个资源的时候,会出现混乱,就需要让线程对资源的操作有序,就需要用到锁,同步。
程序 线程 进程
在操作系统中运行的一些程序,这些程序就是进程, 一个进程可以有多个线程,比如 QQ,微信。可以和多个人一起聊天,B站看视频,又可以播放视频,同时发弹幕,就是多个线程
线程实现方法
-
继承 Thread类
public class TestThread extends Thread{ //重写run方法 @Override public void run() { //方法体、 for (int i = 0; i <20 ; i++) { System.out.println("我是run方法" + i); } } public static void main(String[] args) { //创建线程对象 调用start TestThread testThread = new TestThread(); testThread.start(); //主线程 for (int i = 0; i <20 ; i++) { System.out.println("我是main方法" + i); } } } // 执行结果如下 我是main方法6 我是main方法7 我是run方法0 我是run方法1 我是main方法8 我是main方法9 我是main方法10 我是run方法2 // 多条执行路径,交替执行 cpu 调度线程
-
实现Runable接口
public class TestThread implements Runnable{
//重写run方法
@Override
public void run() {
//方法体、
for (int i = 0; i <20 ; i++) {
System.out.println("我是run方法" + i);
}
}
public static void main(String[] args) {
//创建线程对象 调用start
TestThread testThread = new TestThread();
// testThread.run(); 这样调用也可以执行方法,本质上是顺序执行。
new Thread(testThread).start();
}
}
//多线程就是分时利用CPU,宏观上让所有线程一起执行 ,也叫并发
// 发现问题 实现接口之后 直接创建对象调用run方法也可以执行 是为什么
//结果: 可以运行 但是run() 方法调用还是顺序执行, 调用start() 是线程进入jvm虚拟机调度。这才是多线程执行真正的目的。
推荐实现接口 Runable 因为继承只能单继承,局限性。
- 实现Callable接口(可以有返回值)
package hy.test;
import java.util.concurrent.*;
//买票方法
public class TestCallable implements Callable {
//重写call方法有返回值
@Override
public Boolean call() {
System.out.println(Thread.currentThread().getName() + "执行了call方法");
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 实现callable 接口 调用线程的步骤
TestCallable t1 = new TestCallable();
TestCallable t2 = new TestCallable();
//1 创建执行服务 创建线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//2 提交结果
Future r1 = service.submit(t1);
Future r2 = service.submit(t1);
//3 获取结果
boolean b1 = (boolean) r1.get();
boolean b2 = (boolean) r2.get();
System.out.println("b1 = " + b1);
System.out.println("b2 = " + b2);
//4 关闭服务
service.shutdown();
}
}
// 结论 使用callable 接口需要将线程对象提交到线程池中执行,call方法可以有返回值,可以有抛出异常。
- 线程池 Executors工具类可以创建三种线程
并发问题
多个线程(对象) 对同一个资源进行操作、
线程的状态
- 创建状态
- 线程一旦创建
- 就绪状态
- 调用start() 进入就绪状态
- 运行状态
- 这个状态就是执行run()方法中的代码
- 阻塞状态
- 当调用 sleep() , wait() , yield() 线程就会进入阻塞状态,阻塞状态解除之后,线程会重新进入就绪状态等待CPU调度
- 死亡状态
- 线程中断或者结束,一旦进入死亡状态,就不能再次启动。
方法 | 说明 |
---|---|
setPriority() | 更改线程的优先级 |
static void sleep (long millis) | 在设置的时间内让该线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前线程,执行其他线程(礼让,不一定会成功) |
void interrupt() | 中断线程 不推荐使用 |
boolean inAlive() | 测试线程是否处于活动状态 |
让线程停止
推荐使用一个标志位来控制。
// 使用标志位 停止线程
public class TestThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 0;
while(flag){
System.out.println("run线程执行中" + i++);
}
}
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestCallable t = new TestCallable();
new Thread(t).start();
for (int i = 0; i < 100; i++) {
if(i == 90){
t.stop();
System.out.println("run线程停止了");
}
System.out.println("main线程执行" + i);
}
}
}
线程休眠 sleep
休眠不会释放锁
守护线程(daemon)
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用邓艾守护线程执行完毕
- 如 后台记录操作日志,监控内存,垃圾回收等待。。。
- 守护线程结束就是程序运行结束之后
Tread thread = new Thread(xxx)
thread.setDaemon(true)
线程同步
同一个资源被多个线程操作—并发。
线程同步就是排队。
锁 (synchronized): 拿到锁才可以操作资源。
每个线程都有自己的工作内存,所以会有数据不同步的线程,数据不可见。
Synchonized 关键字 锁对象或者锁模板类Class
这是个关键字,两种用法 synchronized方法 和 synchronized 代码块。 影响性能,,,
- 用法1 关键字synchronized
//买票的安全问解决
public class TestTickets implements Runnable {
private boolean flag = true;
private int tickets = 10;
@Override
public void run() {
while(flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//同步方法 锁 this
public synchronized void buy() throws InterruptedException {
//买票
if(tickets<=0){
flag = false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"拿到票"+ tickets--);
}
public static void main(String[] args) {
TestTickets t1 = new TestTickets();
new Thread(t1,"小明").start();
new Thread(t1,"小红").start();
new Thread(t1,"张三").start();
}
}
用法二 同步块
synchronized(obj){
//代码块
//obj 是同步监视器 可以是任何对象,但是推荐使用共享资源作为同步监视器
//同步方法中无需指定同步监视器,因为同步方法的同步监视器就是把this,就是这个方法本身,或者Class
}
死锁
两个线程持有对方想获取的锁,都拿不到锁,僵持了、、、
//买票方法
public class MakeUp extends Thread {
int choice;
String name;
static Mirror mirror = new Mirror(); //static 保证只有1份资源
static Lipstick lipstick = new Lipstick();
MakeUp(int choice,String name){
this.choice = choice;
this.name = name;
}
@Override
public void run() {
try {
makeUp();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void makeUp() throws InterruptedException {
if(choice == 0){
synchronized (lipstick){
System.out.println(this.name + "获得口红锁");
Thread.sleep(1000);
synchronized(mirror){
System.out.println(this.name + "获得镜子锁");
}
}
}else{
synchronized (mirror){
System.out.println(this.name + "获得口红锁");
Thread.sleep(2000);
synchronized(lipstick){
System.out.println(this.name + "获得镜子锁");
}
}
}
}
public static void main(String[] args) {
MakeUp t1 = new MakeUp(0,"小红");
MakeUp t2 = new MakeUp(1,"小张");
t1.start();
t2.start();
}
}
//镜子
class Mirror{
}
//口红
class Lipstick{
}
- 互斥 条件 一个资源一次只能被一个进程使用’
- 请求与保持条件: 一个人手里拿着西瓜排队打饭,队伍太长,等太久,手里的西瓜别人想要而他不放手
- 不剥夺条件: 一个人正在上厕所,还没上完,其他人只能等着
- 循环等待条件: A要B的东西 B要A的东西 僵持了
Lock JDK5.0+
更细颗粒度的锁
ReentrantLock 可重入锁
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
- 怎么使用
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest implements Runnable{
private int tickets = 10;
//定义可重入锁
Lock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
lock.lock(); //加锁
if(tickets <=0){
break;
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +"=====" + tickets--);
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
new Thread(lockTest).start();
new Thread(lockTest).start();
new Thread(lockTest).start();
}
}
线程通信
线程之间进行交流。生产者消费者模式。 管程法
package hy.test;
//生产者消费者模型 生产者 消费者 缓冲区 产品
public class LockTest{
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
Produce produce = new Produce(synContainer);
Consumer consumer = new Consumer(synContainer);
produce.start();
consumer.start();
}
}
//生产者
class Produce extends Thread{
SynContainer container;
public Produce(SynContainer container){
this.container = container;
}
//生产者生产产品
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Product(i));
System.out.println("生产了"+ i + "个产品");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
//消费者消费产品
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.pop();
System.out.println("消费了"+ i + "个产品");
}
}
}
//产品
class Product{
private int id;
public Product(int id){
this.id = id;
}
}
//缓冲区
class SynContainer{
//一个容器 存放产品
Product[] arr = new Product[10];
int count = 0;
//生产者存放产品
public synchronized void push(Product product){
//如果容器满了就等待消费者取走产品
if(count == arr.length){
//通知消费者消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果容器没有满,就存放进去
arr[count] = product;
count++;
//可以通知消费者消费了
this.notifyAll();
}
//消费者获取产品
public synchronized Product pop(){
//判断是否有产品 如果没有等待生产
if(count == 0 ){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费者消费。。
//如果有产品就消费
count--;
Product product = arr[count];
this.notifyAll();
return product;
}
}
线程池
使用Executors工具类创建 不推荐
package hy.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//创建一个指定数量的线程池
// ExecutorService pool = Executors.newFixedThreadPool(3);
//创建一个线程池
// ExecutorService pool = Executors.newSingleThreadExecutor();
//创建可变线程池
ExecutorService pool = Executors.newCachedThreadPool();
// pool.execute(new MyThread());
// pool.execute(new MyThread());
// pool.execute(new MyThread());
pool.submit(new MyThread());
pool.submit(new MyThread());
pool.submit(new MyThread());
pool.submit(new MyThread());
//关闭连接
pool.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"====== ");
}
}
使用 ThreadPoolExecutor 自定义创建线程池
ThreadPoolExecutor(int corePoolSize, //核心线程数 就是日常备战的
int maximumPoolSize, // 设置最大线程数量
long keepAliveTime, //设置活跃时长 一定时间之后大于核心线程数的线程会去休息
TimeUnit unit, //设置时间单位
BlockingQueue<Runnable> workQueue, //设置阻塞队列。可执行任务数量 就是能有多少人在那排队等待线程执行
// 这个队列将仅保存execute方法提交的Runnable任务
ThreadFactory threadFactory, //线程创建工厂
RejectedExecutionHandler handler) //拒绝策略,就是线程池满了之后无法接受新的任务
创建一个新 ThreadPoolExecutor给定的初始参数。