多线程
内容
- 什么是线程
- 如何创建线程
- 线程的调度
- 线程的一个设计模式:生产消费者模型
- 线程池
- 线程集合对象(侧重点)
一、什么是线程
进程:运行中的程序才可以称为进程,一个程序一个进程。宏观并行,微观串行。
线程:
1.任何一个程序都至少拥有一个线程,即主线程。但是java程序默认有两个线程,除了主线程之外,还有一个线程,即用于垃圾回收的守护线程。
2.线程是一种轻量级进程,在CPU当中的最基本单元是线程。
3.一个进程可以包含若干的线程
4.各进程之间不共享内存,同进程的各线程之间共享内存
5.每个线程都有独立的栈空间
6.堆当中的地址,可以被共享
注意:
线程是不可控的,只能调度,无法精确控制。研究线程,研究的是如何在多线程场景中,保证数据读写的安全,以及调度线程的运行状态
二、线程的创建和使用
1.通过Thread类来进行创建,需要继承Thread类
package com.mine.demo01;
public class Main1 {
public static void main(String[] args) {
MyThread1 mt1 = new MyThread1();
mt1.start();//调用的不是run方法,而是start
}
}
class MyThread1 extends Thread{
@Override
public void run() {
System.out.println("线程运行了");
}
}
2.通过实现Runnable接口来创建
package com.mine.demo01;
public class Main2 {
public static void main(String[] args) {
Thread t = new Thread(new MyThread2());
t.start();
}
}
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("线程被运行了");
}
}
每个线程的逻辑,都是在run方法当中运行的。通过调用start方法来执行线程。start方法只能被调用一次。
3.线程名的创建和获取
Thread t = new Thread(new MyThread2(),"mythreadName2");
t.setName("myNewThreadName2");
t.start();
线程的线程名,需要在启动之前就确定好。
每个线程都有一个默认的名字:Thread-N,N是线程建立的顺序,是一个不重复的正整数
4.获取线程名的方式
在每一个线程的线程栈当中,调用Thread.currentThread().getName()获取线程名
package com.mine.demo01;
public class Main3 {
public static void main(String[] args) {
System.out.println("主线程:"+Thread.currentThread().getName());
Thread t = new Thread(new MyThread3());
t.start();
}
}
class MyThread3 implements Runnable {
@Override
public void run() {
System.out.println("子线程:" + Thread.currentThread().getName());
}
}
结果:
主线程:main
子线程:Thread-0
三、线程调度
线程之间传参,共享数据
synchronized:同一时刻,只有一个线程可以调用该方法。
package com.mine.demo01;
public class Main4 {
public static void main(String[] args) {
Test t = new Test();
for (int i = 0 ; i < 1000 ; i ++){
new Thread(new Thread1(t)).start();
}
}
}
//线程数据的共享和传参
class Test{
private int i = 0;
//为了保证数据安全,需要用到同步锁
public synchronized int getI() {
return ++i;
}
}
class Thread1 implements Runnable{
private Test t;
public Thread1(Test t){
this.t = t;
}
@Override
public void run() {
System.out.println(t.getI());
}
}
-
一个正常线程的生命周期
1.新建线程(1生命形态)
2.启动线程
3.就绪状态(2就绪形态)4.等待系统分配资源
5.运行状态(3运行形态)
6.死亡状态(4死亡形态)
1.等待
等待的构成:
1.新建
2.就绪
3.运行
4.持锁(先到就绪状态---->运行)
5.等待
6.释放锁
7.时间到或者被唤醒
8.持锁(先到就绪状态---->运行)
9.执行等待后续
10.死亡
注意:等待必须在同步环境当中,如果不在同步环境中,会报如下异常
java.lang.RuntimeException: java.lang.IllegalMonitorStateException
at com.qf.demo01.Test.getI(Main4.java:18)
at com.qf.demo01.Thread1.run(Main4.java:30)
at java.lang.Thread.run(Thread.java:745)
package com.mine.demo01;
public class Main4 {
public static void main(String[] args) {
Test t = new Test();
for (int i = 0; i < 3; i++) {
new Thread(new Thread1(t)).start();
}
}
}
class Test {
private int i = 0;
public synchronized int getI() {
try {
System.out.println("等待开始前:" + Thread.currentThread().getName());
this.wait(3000);
System.out.println("等待开始后:" + Thread.currentThread().getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
return ++i;
}
}
class Thread1 implements Runnable {
private Test t;
public Thread1(Test t) {
this.t = t;
}
@Override
public void run() {
System.out.println(t.getI());
}
}
运行结果:
等待开始前:Thread-0
等待开始前:Thread-2
等待开始前:Thread-1
等待开始后:Thread-2
1
等待开始后:Thread-1
2
等待开始后:Thread-0
3
注意:只要是线程进入阻塞,那么他一定回先回到就绪状态
2.休眠
package com.mine.demo05;
public class Main {
private synchronized static void test(){
try {
System.out.println("休眠前:"+Thread.currentThread().getName());
Thread.sleep(3000);
System.out.println("休眠后:"+Thread.currentThread().getName());
}catch (InterruptedException e){
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
for (int i = 0;i < 2 ;i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
test();
}
});
t.start();
}
}
}
结果:
休眠前:Thread-0
休眠后:Thread-0
休眠前:Thread-1
休眠后:Thread-1
注意:休眠不释放锁,休眠不可被唤醒,必须等到休眠时间结束
3.优先级(了解)
Thread t = new MyThread();
t.setPriority(5);
t.start();
4.让步(了解)
Thread.yield();//作用仅仅是让当前正在运行的线程回到就绪状态
5.线程的join(生命周期和sleep是一样的)
package com.mine.demo06;
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"......");
try {
Thread.sleep(3000);//3s
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"++++++");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
try {
t1.start();
t1.join();
t2.start();
t2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
结果:
Thread-0......
Thread-1++++++
6.守护线程
用户线程:主帅,用户线程只要在运行,守护线程可以一直运行下去,除非守护线程自行死亡
守护线程:卫兵,只要没有用户线程还在继续执行,那么无论其是否还存活,都将立即死亡
四、同步锁
注意:只要是改数据就,就一定要考虑数据同步问题
- 对象锁
//同步方法
public synchronized void getI() {
System.out.println(Thread.currentThread().getName()+"持锁");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void test(){
System.out.println(Thread.currentThread().getName()+" "+i);
synchronized (this){//同步代码块
System.out.println(Thread.currentThread().getName()+"持锁");
try {
Thread.sleep(3000);
i++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(i);
}
基于对象的锁,必须是多线程共享的同一个对象
//基于对象的锁
private A a = new A();
public void test(){
// A a = new A();
System.out.println(Thread.currentThread().getName()+" "+i);
synchronized(a){
System.out.println(Thread.currentThread().getName()+"持锁");
try {
Thread.sleep(3000);
i++;
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(i);
}
- 类锁
package com.mine.demo02;
public class Main2 {
public static void main(String[] args) {
for(int i = 0 ; i < 3 ; i ++){
new Thread(new Runnable() {
@Override
public void run() {
C.fun1();
}
}).start();
}
}
}
class C{
public synchronized static void fun1(){
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void fun2(){
synchronized(C.class){
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
- 死锁
package com.mine.demo02;
public class Main1 {
public static void main(String[] args) {
Test t = new Test();
new Thread(new Thread1(t)).start();
new Thread(new Thread2(t)).start();
}
}
class Test{
private A a = new A();
private B b = new B();
public void test1(){
synchronized(b){
System.out.println(Thread.currentThread().getName()+"持b锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized(a){
System.out.println(Thread.currentThread().getName()+"持a锁");
}
}
}
public void test2(){
synchronized(a){
System.out.println(Thread.currentThread().getName()+"持a锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized(b){
System.out.println(Thread.currentThread().getName()+"持b锁");
}
}
}
}
class Thread1 implements Runnable{
private Test t;
public Thread1(Test t){
this.t = t;
}
@Override
public void run() {
t.test1();
}
}
class Thread2 implements Runnable{
private Test t;
public Thread2(Test t){
this.t = t;
}
@Override
public void run() {
t.test2();
}
}
class A{}
class B{}
注意:
1.静态锁只和静态锁一起用,对象锁只和对象锁一起用,不要混用。
2.持锁顺序保持一致
五、volatile关键字
是一个轻量级的同步锁,因为互斥是同步中最消耗性能的地方,而在读-读/读-写(多读1写),那么这
种情况就只需要考虑可见性。
同步锁的功能包括:互斥,可见
package com.mine.demo02;
public class Person {
private volatile Integer id;
private volatile String name;
public Integer getId() {
return id;
}
public synchronized void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public synchronized void setName(String name) {
this.name = name;
}
}
六、生产消费者模型
package com.mine.demo03;
import java.util.Date;
//生成者
public class Producer implements Runnable {
private final Queue queue;
public Producer(final Queue queue) {
this.queue = queue;
}
@Override
public void run() {
for(;;){
try {
Thread.sleep(1000);
this.queue.add(Thread.currentThread().getName() + " " + new
Date().getTime());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
package com.mine.demo03;
import java.util.Date;
//消费者
public class Consumer implements Runnable{
private final Queue queue;
public Consumer(final Queue queue) {
this.queue = queue;
}
@Override
public void run() {
for(;;){
try {
Thread.sleep(1000);
System.out.println(this.queue.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package com.qf.demo03;
import java.util.LinkedList;
//队列
public class Queue{
private static final int MAX_VALUE = 5;
private LinkedList queue = new LinkedList();
public synchronized void add(Object obj){
while(queue.size()==MAX_VALUE){//用while不要用if
try {
//满了等待
System.out.println(Thread.currentThread().getName() + " 满了,等
待!!");
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//没有满,添加
this.queue.addFirst(obj);
//每次添加唤醒全部线程
this.notifyAll();
}
public synchronized Object get(){
while (this.queue.size() == 0){
//空了等待
try {
System.out.println(Thread.currentThread().getName() + " 空了,等
待!!");
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//不为空
Object obj = this.queue.getLast();
this.queue.removeLast();
//每次获取唤醒全部线程
this.notifyAll();
return obj;
}
}
package com.mine.demo03;
import com.oracle.jrockit.jfr.Producer;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Queue queue = new Queue();
new Thread(new Producer(queue)).start();
new Thread(new Producer(queue)).start();
new Thread(new Consumer(queue)).start();
}
}