java多线程介绍
本文目录
简单的说:多线程就是同一时间段内同时做多件事情。效率大大提高。当然把如果时间段看成很小的时间单位时就不存在多线程了。
下面重点介绍java怎么使用多线程。
开启线程方法一继承Thread类
直接看代码。注意main方法就是一个线程。再开一个就是两个线程了。在一个线程里开启一个线程。称子线程。
class Demo extends Thread{
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println("我在听歌。。。。");
}
}
}
public class Main {
public static void main(String[] args) {
//demo继承了Thread
Demo demo = new Demo();
//运行子线程。start()是Thread的方法
demo.start();
for (int i = 0; i <5 ; i++) {
System.out.println("我在学习java多线程");
}
//结果
//我在学习java多线程
//我在学习java多线程
//我在学习java多线程
//我在学习java多线程
//我在学习java多线程
//我在听歌。。。。
//我在听歌。。。。
//我在听歌。。。。
//我在听歌。。。。
//我在听歌。。。。
}
}
开启线程方式二实现Runnable接口
推荐使用方法二。因为java是单继承,多实现。实现同一个功能能不用继承最好不要用。
class Demo implements Runnable{
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println("我在听歌。。。。");
}
}
}
public class Main {
public static void main(String[] args) {
//demo继承了Thread
Demo demo = new Demo();
//创建一个子线程.代理模式
Thread thread = new Thread(demo);
//运行子线程
thread.start();
for (int i = 0; i <5 ; i++) {
System.out.println("我在学习java多线程");
}
//结果
//我在学习java多线程
//我在学习java多线程
//我在学习java多线程
//我在学习java多线程
//我在学习java多线程
//我在听歌。。。。
//我在听歌。。。。
//我在听歌。。。。
//我在听歌。。。。
//我在听歌。。。。
}
}
开启线程方法三实现Callable<?>接口
此方法初学者建议跳过。而且阿里巴巴也建议不用Executors创建线程池了。
//String代表要返回的类型
class Demo implements Callable<String> {
//前面两种方法创建用run()无法抛异常返回值只能是void
@Override
public String call() throws Exception {
System.out.println("我在听歌。。。");
return "我回来了";
}
}
public class Main {
public static void main(String[] args) {
Demo demo = new Demo();
//创建一个线程池 参数是可以接纳多少个线程
ExecutorService service = Executors.newFixedThreadPool(2);
//加入线程1
Future<String> submit1 = service.submit(demo);
//加入线程2
Future<String> submit2 = service.submit(demo);
try {
String value1 = submit1.get();
//获取线程1返回的值
System.out.println("我是线程1:"+value1);
String value2 = submit2.get();
//获取线程2返回的值
System.out.println("我是线程2:"+value2);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//关闭线程
service.shutdown();
//结果
//我在听歌。。。
//我在听歌。。。
//我是线程1:我回来了
//我是线程2:我回来了
}
}
用Lambda写法介绍
Lambda针对一个接口仅有一个方法时可以简化匿名内部类的写法
interface Demo{
String say(int a);
}
public class Main {
public static void main(String[] args) {
Demo demo1 = new Demo(){
@Override
public String say(int a) {
System.out.println("我是正常匿名类实例化->a="+a);
return "demo1正常匿名类返回值";
}
};
System.out.println(demo1.say(1));
//结果
//我是正常匿名类实例化->a=1
//demo1正常匿名类返回值
Demo demo2 = (int a)->{
System.out.println("我是Lambda简化的匿名内部类->a="+a);
return "demo2Lambda简化的匿名返回值";
};
System.out.println(demo2.say(2));
//结果
//我是Lambda简化的匿名内部类->a=2
//demo2Lambda简化的匿名返回值
//当参数只有一个时省略参数类型如下
Demo demo3 = a->{
System.out.println("我是Lambda简化的匿名内部类->a="+a);
return "demo3Lambda简化的匿名返回值";
};
System.out.println(demo3.say(3));
//结果
//我是Lambda简化的匿名内部类->a=3
//demo3Lambda简化的匿名返回值
//当方法的就一行是可以省略花括号如(如果是return return 关键字也要去掉)
Demo demo4 = a-> "demo4Lambda简化的匿名返回值a="+a;
System.out.println(demo4.say(4));
//结果
//demo4Lambda简化的匿名返回值a=4
}
}
用Lambda简化开启线程方式二
public class Main {
public static void main(String[] args) {
//普通匿名内部类写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是普通写法的匿名内部类");
}
}).start();
//简化一
new Thread(()->{
System.out.println("我是Lambda简化的匿名内部类");
}).start();
//简化二
new Thread(()-> System.out.println("我是Lambda最简化的匿名内部类")).start();
//结果
//我是普通写法的匿名内部类
//我是Lambda简化的匿名内部类
//我是Lambda最简化的匿名内部类
}
}
Thread.yield()礼让线程
class Demo implements Runnable{
@Override
public void run() {
//Thread.currentThread().getName()获取当前线程的名字
System.out.println(Thread.currentThread().getName()+"=Start========");
//礼让线程。礼让后公平竞争。有可礼让后还是调用自己。记住是公平竞争
Thread.yield();
System.out.println(Thread.currentThread().getName()+"=end========");
}
}
public class Main {
public static void main(String[] args) {
Demo demo = new Demo();
Thread threadOne = new Thread(demo);
threadOne.setName("线程一");//设置线程的名字。要在启动前
threadOne.start();//启动
Thread threadTow = new Thread(demo);
threadTow.setName("线程二");
threadTow.start();
//多运行几次有不同的结果
//结果1
//线程二=Start========
//线程一=Start========
//线程二=end========
//线程一=end========
//结果2-->这个结果就是礼让后还是调用自身。(公平竞争)
//线程一=Start========
//线程一=end========
//线程二=Start========
//线程二=end========
}
}
Thread.sleep();抱着资源睡觉
public class Main {
public static void main(String[] args) {
new Thread(()->{
try {
//单位是毫秒这里是1秒
//抱着资源睡觉。没有释放资源。只是单纯的睡了一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("一秒后输出我");
}).start();
}
}
thread.join();线程插队
thread.join();出现在哪个线程就往哪个线程插入
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("===========我是子线程"+i);
}
});
thread.start();
for (int i = 0; i <10 ; i++) {
if(i==5){
try {
//合并线程。将子线程插入主线程中。先走完子线程再走主线程
//相当于把子线程的代码复制到这。
//可能插队形容起来更加贴切
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("我是主线程"+i);
}
//结果->主线程0到4都要是大家公平竞争。谁快输出谁。主线程4后子线程就插队了。
//我是主线程0
//我是主线程1
//我是主线程2
//===========我是子线程0
//===========我是子线程1
//我是主线程3
//我是主线程4
//===========我是子线程2
//===========我是子线程3
//===========我是子线程4
//我是主线程5
//我是主线程6
//我是主线程7
//我是主线程8
//我是主线程9
}
}
线程的优先级Priority
优先级只是代表调用的概率。并不保证执行的顺序(可以设置1到10)默认是5
class Demo implements Runnable{
@Override
public void run() {
//获取当前线程的名字
String name = Thread.currentThread().getName();
//获取当前线程的优先级
int priority = Thread.currentThread().getPriority();
//输出
System.out.println(name+"->优先级是:"+priority);
}
}
public class Main {
public static void main(String[] args) {
Demo demo = new Demo();
//构造方法也可以设置线程的名字
Thread t1 = new Thread(demo,"t1");
Thread t2 = new Thread(demo,"t2");
Thread t3 = new Thread(demo,"t3");
Thread t4 = new Thread(demo,"t4");
Thread t5 = new Thread(demo,"t5");
//设置优先级-》Thread.MAX_PRIORITY是个常量。也可以填1到10;
t1.setPriority(Thread.MIN_PRIORITY);//Thread.MAX_PRIORITY==1
t2.setPriority(Thread.NORM_PRIORITY);//Thread.MAX_PRIORITY==5
t3.setPriority(Thread.MAX_PRIORITY);//Thread.MAX_PRIORITY==10
t4.setPriority(3);//Thread.MAX_PRIORITY==10
t5.setPriority(6);//Thread.MAX_PRIORITY==10
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
//结果 可以看到优先级高的不一定最先运行
//t5->优先级是:6
//t4->优先级是:3
//t2->优先级是:5
//t3->优先级是:10
//t1->优先级是:1
}
}
线程的状态Thread.State
NEW->代表刚刚分配地址
RUNNABLE->启动状态
BLOCKED->一个线程因为等待临界区的锁被阻塞产生的状态
WAITING->一个线程进入了锁,但是需要等待其他线程执行某些操作。时间不确定
TIMED_WAITING->一个线程进入了锁,需要等待其他线程执行某些操作。时间确定
TERMINATED->结束状态
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("---执行了--");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//获取线程的状态t前面线程的实例化
Thread.State state = t.getState();
System.out.println(state);//NEW->分配空间
t.start();
System.out.println(t.getState());//RUNNABLE->启动
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getState());//TIMED_WAITING->时间堵塞
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(t.getState());//TERMINATED->停止了
}
线程不安全例子
模拟去12306抢票
class Demo implements Runnable {
//总票数是十张
private Integer count = 10;
@Override
public void run() {
//一人要抢十张票
for (int i = 0; i < 10; i++) {
//如果没票返回false
boolean b = test01();
if (!b) {
//没票就退出
break;
}
}
}
private boolean test01() {
try {
//模拟网络延时。
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这里要抢一张票。如果没有了则return false
if ((count-1) < 0) {
System.out.println("票没了");
return false;
}
//上面没返回证明有票
System.out.println(Thread.currentThread().getName() + "抢到票,票号:" + count);
//票数减一
count--;
//抢到票
return true;
}
}
public class Main {
public static void main(String[] args) {
Demo demo = new Demo();
//A过来抢票
new Thread(demo,"A").start();
//B也要抢
new Thread(demo,"B").start();
//结果
//A抢到票,票号:10
//B抢到票,票号:10
//A抢到票,票号:8
//B抢到票,票号:8
//A抢到票,票号:6
//B抢到票,票号:6
//B抢到票,票号:4
//A抢到票,票号:4
//A抢到票,票号:2
//B抢到票,票号:2
//票没了
//票没了
}
}
可以看到票号被重复抢到了。重复的原因是因为两个线程相互抢夺资源。
如A线程抢到票了还没来的及去修改剩余票数。B线程就来这时B线程将获取和A线程一样的票数号。导致安全。
synchronized解决线程安全
synchronized同步锁
注意->锁的对象地址不能改变。会变的对象锁也没用。可以看到test02就是错误例子。
可以修饰方法可以 如private synchronized boolean test01();还可以做同步块代码test01就是
class Demo implements Runnable {
//总票数是十张
private Integer count = 10;
@Override
public void run() {
//一人要抢十张票
for (int i = 0; i < 10; i++) {
//如果没票返回false
boolean b = test01();
if (!b) {
//没票就退出
break;
}
}
}
private boolean test01() {
//没票直接返回,不必到下面排队等锁释放。大大提高效率
if((count-1)<0){
return false;
}
//注意参数要锁对->可以看test02就锁错了对象
//参数可以是对象,可以是Class
synchronized (this){
try {
//模拟网络延时。
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这里要抢一张票。如果没有了则return false
if ((count-1) < 0) {
System.out.println("票没了");
return false;
}
//上面没返回证明有票
System.out.println(Thread.currentThread().getName() + "抢到票,票号:" + count);
//票数减一
count--;
//抢到票
return true;
}
}
private boolean test02() {
//这里锁的是count。可count对象在变。锁不住
synchronized (count){
try {
//模拟网络延时。
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这里要抢一张票。如果没有了则return false
if ((count-1) < 0) {
System.out.println("票没了");
return false;
}
//上面没返回证明有票
System.out.println(Thread.currentThread().getName() + "抢到票,票号:" + count);
//票数减一
count--;
//抢到票
return true;
}
}
}
public class Main {
public static void main(String[] args) {
Demo demo = new Demo();
//A过来抢票
new Thread(demo,"A").start();
//B也要抢
new Thread(demo,"B").start();
//结果
//A抢到票,票号:10
//B抢到票,票号:9
//A抢到票,票号:8
//A抢到票,票号:7
//A抢到票,票号:6
//B抢到票,票号:5
//B抢到票,票号:4
//B抢到票,票号:3
//A抢到票,票号:2
//A抢到票,票号:1
//票没了
//票没了
}
}
Wait和Notify和thread.setDaemon(true);
Wait是让线程等待 Notify唤醒等待的线程
模拟面包铺。生产包子和顾客来买包子。
thread.setDaemon(true);//开启守护线程守护线程就是主线程结束后其自动结束。用于收尾工作。包子的例子中都没顾客线程都没看。生产的线程也没必要存在
//缓存区
class SynContainer{
private Queue<String> queue;//用来存放生产的包子
//构造器实例化队列
public SynContainer(Queue<String> queue) {
this.queue = queue;
}
public synchronized void pusp(String name){
int size = queue.size();
//这里设定了只能生产10个包子。达十个要等顾客消费再生产
if (size>=10){
System.out.println("等待消费");
try {
//到达十个了。停止生产
//wait();让其线程等待。等待被唤醒
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//生产了就往队列里压
queue.offer(name);
System.out.println("生产了"+name);
//生产了证明有包子卖了
//唤醒等待的线程。有可能客户正在等待中
this.notifyAll();
}
public synchronized String pop(){
//顾客来买包子。发现没有只能等待
if(queue.size()==0){
System.out.println("等待生产");
try {
//让其线程等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//买一个包子就出队一个
String value = queue.poll();
//买了一个包子。就不可能还有十个
//通知生产者生产
this.notifyAll();
//返回购买的包子
return value;
}
}
//生产者
class Productor implements Runnable{
private SynContainer synContainer;
public Productor(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
//最多去生产20个。生产十个就得停下等顾客购买
for (int i = 1; i <= 20; i++) {
synContainer.pusp("包子"+i+"号");
}
}
}
//消费者
class Consumer implements Runnable{
private SynContainer synContainer;
public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
//顾客要买一百个包子
for (int i = 0; i <15 ; i++) {
String name = synContainer.pop();
System.out.println("获得"+name);
}
}
}
public class WaitAndNotifyDemo01 {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer(new LinkedList<>());
Thread thread = new Thread(new Productor(synContainer));
thread.setDaemon(true);//开启守护线程
thread.start();
new Thread(new Consumer(synContainer)).start();
//结果
//等待生产
//生产了包子1号
//生产了包子2号
//生产了包子3号
//生产了包子4号
//生产了包子5号
//生产了包子6号
//生产了包子7号
//生产了包子8号
//生产了包子9号
//生产了包子10号
//等待消费
//生产了包子11号
//等待消费
//获得包子1号
//获得包子2号
//生产了包子12号
//等待消费
//获得包子3号
//获得包子4号
//获得包子5号
//生产了包子13号
//生产了包子14号
//生产了包子15号
//等待消费
//获得包子6号
//生产了包子16号
//等待消费
//获得包子7号
//获得包子8号
//获得包子9号
//获得包子10号
//获得包子11号
//生产了包子17号
//生产了包子18号
//生产了包子19号
//生产了包子20号
//获得包子12号
//获得包子13号
//获得包子14号
//获得包子15号
}
}
代理模式
代理模式就是不动原来的代码。为其增加功能。在不动源代码的前提下。想执行其方法前打印日志就需要用到该模式。spring的Aop就是典型的例子
interface Proxy{
void happyPaly();
}
class You implements Proxy{
@Override
public void happyPaly() {
System.out.println("是我在玩遥控车");
}
}
class WeddingCompany implements Proxy{
private Proxy proxy;
WeddingCompany(Proxy proxy){
this.proxy=proxy;
}
@Override
public void happyPaly() {
beforHappyPaly();
proxy.happyPaly();
afterHappyPaly();
}
private void beforHappyPaly(){
System.out.println("代理帮遥控车充电了。。。。。");
}
private void afterHappyPaly(){
System.out.println("代理帮说遥控车没电了。。。。");
}
}
public class ProxyDemo {
public static void main(String[] args) {
WeddingCompany weddingCompany = new WeddingCompany(new You());
weddingCompany.happyPaly();
//结果
//代理帮遥控车充电了。。。。。
//是我在玩遥控车
//代理帮说遥控车没电了。。。。
//这里可以看出new Thread(对象).start();就是代理对象
}
}
注意代理和修饰写法好像但是两种设计模式不一样
指令重排-单例模式例子–volatile
指令重排就是jvm为了优化代码。而不影响代码本身的前提下对代码进行排列。可是在多线程就会出事。
volatile->保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
volatile->禁止进行指令重排序。(实现有序性)
volatile-> 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。
class SingletonDubleChecked{
//考虑到new SingletonDubleChecked();时可能发生指令重排
private static volatile SingletonDubleChecked singletonDubleChecked;
private SingletonDubleChecked(){}
public static SingletonDubleChecked getSingleObj(){
if(singletonDubleChecked==null){
synchronized (SingletonDubleChecked.class){
if(singletonDubleChecked==null){
singletonDubleChecked = new SingletonDubleChecked();
//如果不加volatile修饰
//singletonDubleChecked = new SingletonDubleChecked();
//第一步->new 为其分配权限
//第二步->SingletonDubleChecked();初始化对象
//第三不->对象赋值
//正常来说是1->2->3
//指令重排的话可能是1->3->2
//132的话在多线程。可能会获取到null值。因为还没赋值就往下了
System.out.println("初始化");
}
}
}
return singletonDubleChecked;
}
}
public class DoubleCheckedSingleton {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
SingletonDubleChecked.getSingleObj();
}).start();
}
}
}
ThreadLocal
ThreadLocal为每个线程都开辟一个独立的空间。使得多线程数据可以相互不干扰
官方建议用 private static 来修饰ThreadLocal
get/set/initialValue
public class Main {
//private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 200);给初始化值
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(100);
new Thread(()->{
threadLocal.set(50);
System.out.println("子线程:"+threadLocal.get());
}).start();
try {
//等待一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程"+threadLocal.get());
//结果
//子线程:50
//主线程100
//可以看到主线程和子线程数据不干扰
}
}
CAS
Compare and Swap,即比较再交换。
jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快。而且CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了。
import java.util.concurrent.atomic.AtomicInteger;
public class CAS {
//Atomic原子性
private static AtomicInteger count = new AtomicInteger();
public static void main(String[] args) {
count.set(5);
for (int i = 0; i <10 ; i++) {
new Thread(()->{
for (int j = 0; j < 10; j++) {
//减一
int c = count.decrementAndGet();
if(c<1){
// System.out.println("抢完了");
return ;
}
System.out.println(Thread.currentThread().getName()+"抢到了"+c);
}
}).start();
}
//结果
//Thread-2抢到了3
//Thread-3抢到了1
//Thread-1抢到了2
//Thread-0抢到了4
}
}
TimerTask
设一个时间任务
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
class MyTask extends TimerTask {
private Timer timer;
MyTask(Timer timer){
this.timer=timer;
}
@Override
public void run() {
System.out.println("时间到我自然启动了。。。。。");
Date date = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateStr = dateFormat.format(date);
System.out.println("启动时间"+dateStr);
//执行后关闭
timer.cancel();
}
}
public class TimerDemo01 {
public static void main(String[] args) {
//Timer(true)//开启守护线程
Timer timer = new Timer();
try {
Date parse = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2025-08-08 22:12:00");
timer.schedule(new MyTask(timer),parse);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
注意
public class Main {
public static void main(String[] args) {
int i=1;
int arr[] = new int[5];
new Thread(()->{
//i=2;是错的
int a = i;
a = 2;//这样就可以
System.out.println(a);
//可以看出只要不修改i的地址就可以
//i--;也是错的。修改了原来的地址
//集合数组可以直接赋值操作因为没改变其地址
arr[0] = 1;//这个是对的
}).start();
}
}