文章目录
回顾一下Java基本的多线程,为JUC打一下基础
1、线程创建
注意:调用run方法和start方法的区别
1.1 继承Thread类
继承Thread
类,重写run()
方法,编写线程执行体
线程对象使用start()
方法启动线程
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("继承Thread创建==>>"+Thread.currentThread().getName());
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
//1.new一个线程对象 然后执行start方法
MyThread1 t1 = new MyThread1();
t1.start();
//2.或者直接运行
new MyThread1().start();
}
}
1.2 实现Runnable接口
一遍推荐实现Runnable接口来创建线程,因为继承有局限性
Thread类
本身实现了Runnable接口
和继承Thread类一样,需要重写run()
方法,编写线程执行体
线程对象使用start()
方法启动线程
注意:要new一个Thread类来代理启动或者用线程池方式等代理启动
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable创建==>>"+Thread.currentThread().getName());
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
//实现Runnable的类需要一个 Thread代理类帮助其启动
Thread t2 = new Thread(new MyThread2());
t2.start();
//或者直接运行
new Thread(new MyThread2()).start();
}
}
1.3 实现Callable接口
重写call方法
,需要抛出异常
使用线程池方式:
先创建一个服务:ExecutorService threadPool = Executors.newFixedThreadPool(1);
再提交执行:Future submit = threadPool.submit(t3);
可以获取call()函数的返回值
:submit.get();
最后要关闭服务:threadPool.shutdown();
class MyThread3 implements Callable<String>{
/*call类型和Callable<?>一致,可自定义*/
@Override
public String call() throws Exception {
String str = "实现Callable创建==>>" + Thread.currentThread().getName();
System.out.println(str);
return str;
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
MyThread3 t3 = new MyThread3();
//实现Callable的类启动方式:FutureTask或线程池等
FutureTask<String> futureTask = new FutureTask<>(t3);
new Thread(futureTask).start();
//创建一个线程池
ExecutorService threadPool = Executors.newFixedThreadPool(1);
//submit()参数可以为实现Callable类的
Future<String> submit = threadPool.submit(t3);
try {
String str = submit.get();
System.out.println(str);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
threadPool.shutdown();
}
}
2、线程状态和相关方法
2.1 线程五大状态
-
线程刚new出来进入
新生状态
-
调用start()方法进入
就绪状态
-
就绪状态的线程经过cpu调度进入
运行状态
-
运行队列满或者调用sleep,wait,被阻塞在临界区外时是
阻塞状态
-
线程中断或结束进入
死亡状态
,死亡后不能再次启动
2.2 线程相关方法
2.2.1 优先级setPriority
- 优先级默认为
5
,可设置范围为1-10
,超出范围会抛出IllegalArgumentException
package com.bandit.ThreadMethod;
public class ThreadDemo2 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
Thread thread3 = new Thread(myThread);
Thread thread4 = new Thread(myThread);
Thread thread5 = new Thread(myThread);
thread1.setPriority(6);
thread2.setPriority(3);
thread3.setPriority(Thread.NORM_PRIORITY); //默认5
thread3.setPriority(Thread.MAX_PRIORITY); //10
thread4.setPriority(Thread.MIN_PRIORITY); //1
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"优先级为"+Thread.currentThread().getPriority());
}
}
实际上运行有很多种结果,这个优先级只是cpu调度该线程的概率
2.2.2 线程休眠sleep
线程执行过程中可以通过sleep进行休眠
注意:sleep不会释放锁
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
public class ThreadSleep {
public static void main(String[] args) {
ticketThead ticketThead = new ticketThead();
new Thread(ticketThead,"小明").start();
new Thread(ticketThead,"小王").start();
new Thread(ticketThead,"黄牛").start();
}
}
class ticketThead 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-- + "张票");
}
}
}
查看运行,会有拿到0票和负票的,解决方法:加锁
2.2.3 线程停止
不建议使用JDK提供的stop()和destory()方法
推荐设置一个标志位
作为终止变量,当flag=false
时终止线程运行
public class ThreadStop {
public static void main(String[] args) {
stopThread t1 = new stopThread();
new Thread(t1).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//停止
t1.stop();
}
}
class stopThread implements Runnable{
private boolean flag = true;
public void stop() {//向外提供一个stop方法设置flag
this.flag = false;
}
@Override
public void run() {
while (flag){
System.out.println("运行");
}
}
}
2.2.4 线程礼让yield
让当前正在执行的进程暂停,但不阻塞
,即由运行态->就绪态
让cpu重新调度,礼让不一定成功,全靠cpu调度
public class ThreadYield {
public static void main(String[] args) {
YieldThread t1 = new YieldThread();
new Thread(t1,"线程1").start();
new Thread(t1,"线程2").start();
}
}
class YieldThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}
}
礼让成功:
2.2.5 线程插队join
join可以想象成插队,让其他进程等待此进程执行完后再执行
public class ThreadJoin {
public static void main(String[] args) {
JoinThread joinThread = new JoinThread();
Thread thread = new Thread(joinThread);
thread.start();
for (int i = 0; i < 200; i++) {
if(i==100){//在100时插队
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("主线程"+i);
}
}
}
class JoinThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("插队进程"+i);
}
}
}
如果不要这段,会先跑完主进程再执行子进程
if(i==100){//在100时插队
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.3 线程状态观测
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("//");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);
//观察启动后
thread.start();
state = thread.getState();
System.out.println(state);//Run
while (state != Thread.State.TERMINATED) {//只要现成不终止,就一直输出状态
Thread.sleep(100);
state = thread.getState();//更新线程状态
System.out.println(state);
}
//死亡后的线程不能再启动了,启动会报异常
//thread.start();
}
}
2.4 守护线程
线程分为用户线程和守护线程
- 用户线程:虚拟机必须保证用户线程执行完毕才关闭
- main线程和自定义的一些其他Thread线程
- 守护线程:虚拟机不用等待守护线程执行完毕
- gc垃圾回收,监控内存,后台记录操作日志等线程
可以通过thread.setDaemon(true)
设置线程为守护线程,默认为false
public class ThreadDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
//默认false表示是用户线程,正常的线程都是用户线程...
thread.setDaemon(true);
//上帝守护线程启动
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("====goodbye!world====");
}
}
3、线程锁
多个线程访问一个临界资源时需要加锁,让访问的线程排列成队列互斥访问临界区
3.1 synchronized
当一个线程获得对象的排他锁,独占资源,其他线程必须等待该进程使用资源后释放锁才能访问
存在问题:
- 一个线程持有锁会导致其他需要此锁的线程挂起
- 多线程竞争时加锁解锁频繁会引起性能问题
- 如果一个优先级低的线程拿到了锁,让优先级高的等待会导致优先级倒置现象
3.1.1 方法锁
缺陷:如果将一个大方法直接申明为synchronized会影响效率
改造之前买票的案例
public class SynchronizedDemo {
public static void main(String[] args) {
ticketThead ticketThead = new ticketThead();
new Thread(ticketThead,"小明").start();
new Thread(ticketThead,"小王").start();
new Thread(ticketThead,"黄牛").start();
}
}
class ticketThead implements Runnable{
//票数
private int ticketNums = 10;
@Override
public synchronized void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
//捕获异常
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票");
}
}
}
此处临界区为run方法,所以在run方法上加锁
3.1.2 块锁
- synchronized ( Obj ) { }
- Obj 称为 同步监视器,就是上锁的对象
public class SynchronizedDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
synchronized (list){//给list上锁
list.add(Thread.currentThread().getName());
}
}).start();
}
System.out.println(list.size());
}
}
3.2 Lock
-
从JDK5.0开始,java提供了一个更强大的同步锁Lock
-
每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象
-
ReentrantLock
类实现了Lock,拥有与synchronized
相同的并发性和内存语义,可以显示加锁解锁
购票问题的Lock实现
public class LockDemo {
public static void main(String[] args) {
ticketThead ticketThead = new ticketThead();
new Thread(ticketThead,"小明").start();
new Thread(ticketThead,"小王").start();
new Thread(ticketThead,"黄牛").start();
}
}
class ticketThead implements Runnable{
//票数
private int ticketNums = 10;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();//加锁
//买票
if (ticketNums <= 0) {
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();//解锁
}
System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "张票");
}
}
}
3.3 synchronized 和 lock 对比
4、线程通信
4.1 引入
操作系统中生产者消费者问题:采用信号量法
对信号量有Wait
和Signal
操作,
同样的,在Java中也有类似的操作:
- wait():线程一直等待直到其他线程通知,与sleep不同,会释放锁
- wait(Long timeout):指定等待时间
- notify():唤醒一个等待状态的线程
- notfiyAll():唤醒同一个对象上所以调用wait()方法的线程,优先级高的线程先唤醒
注意:以上方法只能在同步方法或同步块中使用,否则会抛出异常
4.2 生产者消费者问题
4.2.1 缓冲区法
//缓冲区法
public class Demo01 {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Producer(synContainer).start();
new Consumer(synContainer).start();
}
}
//生产者
class Producer extends Thread {
//容缓冲区
SynContainer container;
public Producer(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++) {
System.out.println("消费了-->" + container.pop().id + "件产品");
}
}
}
//产品
class Product {
int id;//产品编号
public Product(int id) {
this.id = id;
}
}
//缓冲区
class SynContainer {
//需要一个容器大小
Product[] products = new Product[10];
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Product product) {
//如果容器满了,需要等待消费者消费
/*如果是if的话,假如消费者1消费了最后一个,这是index变成0此时释放锁被消费者2拿到而不是生产者拿到,这时消费者的wait是在if里所以它就直接去消费index-1下标越界,如果是while就会再去判断一下index得值是不是变成0了*/
while (count == products.length) {
//通知消费者消费,等待生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,需要丢入产品
products[count] = product;
count++;
//通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Product pop() {
//判断是否能消费
while (count <= 0) {
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Product product = products[count];
//吃完了 通知生产者生产
this.notifyAll();
return product;
}
}
4.2.2 标志位法
//产品-->节目
public class TV {
//演员表演,观众等待
//观众观看,演员等待
String voice; //表演的节目
boolean flag = true;
//表演
public synchronized void play(String voice) {
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了" + voice);
//通知观众观看
this.voice = voice;
this.notifyAll();
this.flag = !flag;
}
//观看
public synchronized void watch(){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观众观看了" + voice);
//通知演员表演
this.notifyAll();
this.flag = !flag;
}
}
public class Player extends Thread {
private TV tv = null;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
tv.play("表演了" + i + "号节目");
}
}
}
public class Watcher extends Thread {
private TV tv = null;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
tv.watch();
}
}
}
//测试生产者消费者问题:信号灯法,标志位解决
public class Main {
public static void main(String[] args) {
TV tv = new TV();
new Watcher(tv).start();
new Player(tv).start();
}
}
5、线程池
5.1 介绍
背景: 经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完毕放回池中,可以避免频繁的创建销毁,实现重复利用,类似生活中的工共交通工具
好处:
- 提高了响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理(…)
- corePoolSize: 核心池的大小
- maximumPoolSize: 最大线程数
- keepAliveTime: 线程没有任务时最多保持多长时间后会终止
5.2 使用线程池
先创建一个线程池:ExecutorService threadPool = Executors.newFixedThreadPool(1);
再提交执行:
- Future submit = threadPool.submit(t3); 括号里为Callable接口方法
- threadPool.invokeAll(callables); 括号里为Callable接口方法的集合
- threadPool.execute(new MyThread()); 括号里为Runnable接口方法
可以获取call()函数的返回值
:submit.get();
最后要关闭服务:threadPool.shutdown();
public class Demo02 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
//callable
Future<String> submit = threadPool.submit(new MyThread());
//runnable
threadPool.execute(new MyThread2());
//callable List任务集合
ArrayList<Callable<String>> callables = new ArrayList<>();
callables.add(new MyThread());
callables.add(new MyThread());
try {
threadPool.invokeAll(callables);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPool.shutdown();
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
String str = "实现Callable创建==>>" + Thread.currentThread().getName();
System.out.println(str);
return str;
}
}
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable创建==>>"+Thread.currentThread().getName());
}
}