多线程笔记
说明:主要是记录自己在学习多线程时候产生的笔记。已完结
1 继承多线程
方式一:继承Thread类
创建线程方式一:继承Thread类,重写run方法,调用start开启线程
idea快捷键:
20.fori等同于for(int i=1;i<=20;i++)
方式二:实现Runnable接口
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用该方法,因为可以避免单继承局限性,方便同一个对象对多个线程使用
案例(龟兔赛跑)
package com.young.demo;
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
boolean flag = false;
for (int i = 0; i <= 100; i++) {
//模拟兔子睡觉,每10步睡一觉
if(i%10==0 && Thread.currentThread().getName().equals("兔子")){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
flag = gameOver(i);
if(flag){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+
i+"步");
//模拟两者的速度,跑一步乌龟需要的时间是兔子的10倍
if(Thread.currentThread().getName().equals("兔子")){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//判断是否完成比赛
private boolean gameOver(int steps){
if(winner!=null){
return true;
}else{
if(steps>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
}
return false;
}
//主线程
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
兔子-->跑了85步
兔子-->跑了86步
兔子-->跑了87步
兔子-->跑了88步
兔子-->跑了89步
乌龟-->跑了99步
winner is乌龟
2 Lambda表达式
函数式接口的定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
格式:(参数表)->{函数体};
参数表中的参数类型可以被忽略
简化版:单个参数名->单条语句
例子
(int a)->{System.out.print(a);};
a->System.out.print("test");
3 线程状态
3.1 线程中止
核心:使用标志位让线程自行终止
步骤:
- 建议线程正常停止–>利用次数,不建议死循环
- 建议使用标志位–>设置一个标志位
- 不要使用stop或destory等过时或者JDK不建议使用的方法
3.2 线程休眠
两个功能:模拟网络延时和模拟倒计时
每一个对象都有一个锁,sleep不会释放锁
3.3 线程礼让
关键字:yield
package com.young.state;
public class TestYield {
public static void main(String[] args) {
//使用lambda表达式
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"线程开始");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程结束");
},"a").start();
//使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始");
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程结束");
}
},"b").start();
}
}
3.4 线程强制执行
线程插队,保证该线程完全执行
关键字:join
package com.young.state;
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("vip线程开始执行"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 1000; i++) {
if(i==300){
thread.join(); //插队
}
System.out.println("main"+i);
}
}
}
3.5 线程状态
可以使用Thread.State state = thread.getState()去获取当前线程的状态
3.6 线程优先级
线程的优先级用数字表示,范围从1~10,数字越大,线程的优先级越高
获取优先级 getPriority,设置优先级setPriority(int xxx)
优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了。这都是看CPU的调度。
package com.young.state;
public class TestPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
TestPriority testPriority = new TestPriority();
Thread t1 = new Thread(testPriority);
Thread t2 = new Thread(testPriority);
Thread t3 = new Thread(testPriority);
//不设置优先级
t1.start();
//设置优先级
t2.setPriority(Thread.MIN_PRIORITY);
t2.start();
t3.setPriority(Thread.MAX_PRIORITY);
t3.start();
}
}
3.7 守护线程
线程分用户线程和守护线程;虚拟机必须确保用户线程执行完毕,但不需要等待守护线程执行完毕。
应用:后台记录操作日志、内存监控、垃圾回收等待……
使用:setDaemon(true)
4 线程同步
对方法添加synchronized关键字可以让方法变成同步方法。但是会影响效率。
在同步方法中,synchronized作用的是该类的对象,即this,这样还是会出现不安全,假设在银行家问题中,有账户account类,以及取款drawing类,取款操作放在drawing类中,如果对该类的run方法加synchronized关键字其实是无用的,这锁的是drawing类,但我们需要锁的是account类。
所以方法二:同步块,结构synchronized(obj){代码块}
总结:同步方法适合与共享变量(或需要锁的对象)与操作该变量的方法是同一个对象,比如说火车站取票问题,而同步块适合与共享对象与操作方法不在同一个类中的情况,比如银行取钱问题
扩充资料:
synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
5 死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。
产生死锁的四个必要条件:
1、互斥条件:一个资源每次只能被一个进程使用。
2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
3、不剥夺条件:进程已获得的资源,在未完全使用完之前,不能强行剥夺
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
破坏上面的一个必要条件就可以避免死锁。
案例:女生化妆产生死锁
package com.young.syn;
/*
*案例:女生化妆产生死锁
* Author:小羊
*/
//女生化妆引起的死锁问题
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(1,"小红");
Makeup g2 = new Makeup(0,"小蓝");
g1.start();
g2.start();
}
}
//化妆必备品:口红
class Lipstick{
}
//化妆必备品:镜子
class Mirror{
}
//化妆类
class Makeup extends Thread{
private static Lipstick lipstick = new Lipstick();
private static Mirror mirror = new Mirror();
private int chioce;
private String name;
public Makeup(int chioce, String name) {
this.chioce = chioce;
this.name = name;
}
@Override
public void run(){
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void makeup() throws InterruptedException {
if(this.chioce==1){
//先拿起口红再拿起镜子
synchronized (lipstick){
System.out.println(this.name+"拿起了口红。");
Thread.sleep(1000);
synchronized (mirror){
System.out.println(this.name+"拿起了镜子。");
}
}
}{
//先拿起镜子再拿起口红
synchronized (mirror){
System.out.println(this.name+"拿起了镜子。");
Thread.sleep(1000);
synchronized (lipstick){
System.out.println(this.name+"拿起了口红。");
}
}
}
}
}
分析:上述情况会产生死锁,是因为一个锁包含在另外一个锁里,他们彼此占用资源但又不释放,从而导致死锁
解决方法:只要将两个锁分开放,即去掉锁中锁的情况即可。
6 Lock锁
7 生产者与消费者
生产者和消费者共享同一个资源,并且生产者与消费者之间相互依赖,互为条件
进程通信需要两个函数:wait和notify
生产者将产品放入缓冲区,消费者从缓冲区中拿数据
7.1 管程法
其实也就是利用缓冲区
package com.young.senior;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.concurrent.locks.ReentrantLock;
//利用缓冲区解决:管程法
public class TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
Productor p = new Productor(synContainer);
Consumer c = new Consumer(synContainer);
p.start();
c.start();
}
}
//生产者
class Productor extends Thread{
private final ReentrantLock lock = new ReentrantLock();
//专心生产
SynContainer container;
public Productor(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
lock.lock();
try{
container.push(new Chicken(i));
System.out.println("生产第-->"+i+"只鸡");
}
finally {
lock.unlock();
}
}
}
}
//消费者
class Consumer extends Thread{
private final ReentrantLock lock = new ReentrantLock();
//专心消费
SynContainer container;
public Consumer(SynContainer container){
this.container = container;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
lock.lock();
try{
Chicken chicken = container.pop();
System.out.println("消费第-->"+chicken.id+"只鸡");
}finally {
lock.unlock();
}
}
}
}
//产品
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){
//首先判断是否缓冲区满了
if(count==chickens.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有满则加入
chickens[count++]=chicken;
this.notifyAll();
}
//消费者从缓冲区中取产品
public synchronized Chicken pop(){
//首先判断是否为空
if(count==0){
//缓冲区为空,则阻塞该进程
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//不为空就取数据
this.notifyAll();
return chickens[--count];
}
}
8 使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。类似与共享单车之!
package com.young.senior;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//2.执行服务
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//3.关闭服务
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}