java多线程教程
Posted 施莱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java多线程教程相关的知识,希望对你有一定的参考价值。
多线程
文章目录
1.程序、进程、多线程
1.1、程序
说起进程,就不得不说下程序,程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
1.2、进程
进程则是执行程序的一次执行过程,他是一个动态的概念,是系统资源分配的单位。
1.3、线程
通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位
注意:很多 多线程是模拟出来的,真正的多线程是指多个cpu,即多核,如服务器,如果是模拟出来的多线程,即在一个cpu的情况下,在同一时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
每一个java应用程序都有一个主线程。
2、Thread类
2.1.1、构造方法
// 分配一个新的线程对象。
public Thread()
// 分配一个指定名字的新的线程对象。
public Thread(String name)
// 分配一个带有指定目标新的线程对象。
public Thread(Runnable target)
// 分配一个带有指定目标新的线程对象并指定名字。
public Thread(Runnable target,String name)
2.1.2、常用方法
// 设置线程名称
void setName(String name)
// 获取当前线程名称。
public String getName()
// 导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void start()// 此线程要执行的任务在此处定义代码。
public void run()
// 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static void sleep(long millis)
// 返回对当前正在执行的线程对象的引用。
public static Thread currentThread()
3、多线程的创建方式
3.1、方式一:继承Thread类
1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
2.重写Thread类的run()方法
3.创建Thread子类的对象
4.通过此对象调用start()方法
run start 区别
start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)
总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)
代码实现:
package Thread1;
public class ThreadTest1 extends Thread
@Override
public void run()
for (int i=0;i<=2;i++)
System.out.println("当前线程名:"+ThreadTest1.currentThread().getName());
public static void main(String[] args)
ThreadTest1 threadTest1 = new ThreadTest1();
ThreadTest1 threadTest2 = new ThreadTest1();
ThreadTest1 threadTest3 = new ThreadTest1();
threadTest1.setName("线程1");
threadTest2.setName("线程2");
threadTest3.setName("线程3");
threadTest1.start();
threadTest2.start();
threadTest3.start();
运行结果:
当前线程名:线程1
当前线程名:线程3
当前线程名:线程2
当前线程名:线程3
当前线程名:线程1
当前线程名:线程1
当前线程名:线程3
当前线程名:线程2
当前线程名:线程2
进程已结束,退出代码 0
3.2、方式二:实现Runable接口
1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()
代码实现:
package Rnnable1;
import sun.security.mscapi.CPublicKey;
public class RunnableTest1 implements Runnable
int waterAmount;//用int变量模拟水量
public void setWaterAmount(int w)
waterAmount=w;
@Override
public void run()
while (true)
String thread = Thread.currentThread().getName();
System.out.println(thread);
if (thread.equals("dog"))
System.out.println("家狗喝水");
waterAmount=waterAmount-2;
else if (thread.equals("cat"))
System.out.println("家猫喝水");
waterAmount=waterAmount-2;
System.out.println("剩:"+waterAmount);
if (waterAmount<=0)
return;
public static void main(String[] args)
RunnableTest1 runnableTest1 = new RunnableTest1();
runnableTest1.setWaterAmount(10);
Thread thread = new Thread(runnableTest1);
Thread thread2 = new Thread(runnableTest1);
thread.setName("dog");
thread2.setName("cat");
thread.start();
thread2.start();
运行结果:
cat
家猫喝水
剩:8
cat
家猫喝水
剩:6
cat
家猫喝水
剩:4
cat
家猫喝水
dog
剩:2
cat
家猫喝水
剩:0
家狗喝水
剩:-2
3.3、比较方式一和方式二
开发中,优先选择实现Runnable接口的方式
原因:
1:实现的方式没有类的单继承性的局限性
继承Thread不灵活,因为Java只允许单继承(继承了Thread之后如果再想继承别的类就不可能了),实现Runnable接口避免单继承的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runnable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中
4、多线程静态代理模式
- 代理对象和代理要继承同一接口
- 代理对象要代理真实角色
好处:
- 代理对象可以做真实对象做不了的事情
- 真实对象专注于做自己的事情
代码实现:
package Rnnable1;
public class test02
public static void main(String[] args)
You you = new You();
Marry daiLi = new daiLi(you);
daiLi.marry();
// test01 test01 = new test01();
//Thread thread2 = new Thread(test01);
//thread2.start();
interface Marry
void marry();
class You implements Marry
@Override
public void marry()
System.out.println("结婚了");
class daiLi implements Marry
private Marry target;
public daiLi(Marry target)
this.target=target;
public void before()
System.out.println("准备");
@Override
public void marry()
before();
this.target.marry();
after();
public void after()
System.out.println("结束");
5、线程的状态和生命周期
51、线程的状态图
Java中的线程的生命周期大体可分为5种状态。
-
新建(NEW):新创建了一个线程对象。
-
可运行(RUNNABLE,也叫就绪):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。
-
运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。
-
阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
-
死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
5.2、线程方法
setPriority(int newPriority) | 更改线程的优先级 |
---|---|
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止,其他线程才能 执行 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程(最好别用这个方式) |
boolean isAlive() | 测试线程是否处于活动状态 |
1、停止线程
不推荐使用JDK提供的stop() destroy() 方法 [已废弃]
推荐线程自己停止下来,建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
代码实现:
package MutilThreading;
public class test02 implements Runnable
private boolean flag=true;
@Override
public void run()
int i=0;
while (flag)
System.out.println("run。。。thread"+i++);
public void flag()
this.flag=false;
public static void main(String[] args)
test02 test02 = new test02();
Thread thread = new Thread(test02);
thread.start();
for (int i = 0; i < 100; i++)
System.out.println("main"+i);
if (i==50)
test02.flag();
System.out.println("该线程停止");
2、线程休眠
- sleep 指定当前线程阻塞的毫秒数
- sleep 存在异常
- sleep 时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
代码实现:
package Rnnable1;
//模拟网络延时 放大问题的发生性
public class test01 implements Runnable
int i=10;
@Override
public void run()
while (true)
try
Thread.sleep(200);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(Thread.currentThread().getName()+"拿了第"+i--+"票");
if (i<0)
break;
public static void main(String[] args)
test01 test01 = new test01();
Thread thread = new Thread(test01);
Thread thread2 = new Thread(test01);
Thread thread3 = new Thread(test01);
thread.start();
thread2.start();
thread3.start();
3、线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu 重新调度,礼让不一定成功,看cpu 心情
3、线程强制执行
package MutilThreading;
public class testJoin implements Runnable
@Override
public void run()
for (int i=0;i<=1000;i++)
System.out.println(Thread.currentThread().getName()+"-->"+i);
public static void main(String[] args) throws InterruptedException
testJoin testJoin = new testJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i=1;i<500;i++)
if (i==150)
thread.join();//插队,让上面那个线程执行完毕,再执行主线程
System.out.println("主线程"+"-->"+i);
4、线程的优先级
java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示,范围从1-10
Thread.MIN_PRIORITY =1;
Thread.MAX_PRIORITY=10;
Thread.NORM_PRIORITY=5;
使用一下方式改变或获取优先级
getPriority() setPriority(int xxx)
优先级低只是意味着获得调度的概率低,并不是优先级低的就不会被调用,这都是看cpu 的调度
package MutilThreading;
public class testPriority implements Runnable
@Override
public void run()
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
public static void main(String[] args)
testPriority testPriority = new testPriority();
Thread thread = new Thread(testPriority);
Thread thread2 = new Thread(testPriority);
Thread thread3 = new Thread(testPriority);
Thread thread4 = new Thread(testPriority);
Thread thread5 = new Thread(testPriority);
thread.start();
thread2.setPriority(8);
thread2.start();
thread3.setPriority(Thread.MAX_PRIORITY);
thread3.start();
thread4.setPriority(Thread.MIN_PRIORITY);
thread4.start();
thread5.setPriority(2);
thread5.start();
//运行结果
Thread-2-->10
Thread-3-->1
Thread-0-->5
Thread-1-->8
Thread-4-->2
5.3、守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕 如后台记录操作日志、监控内存、垃圾回收等待。。
调用方法 setDaemon();
6、线程同步
6.1、线程同步:多个线程操作同一个资源
线程同步就是并发
6.2、并发与并行
并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
并发:同一个对象被多个线程同时操作,一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事
6.3、线程的安全问题:
什么是线程安全问题呢?
线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。
上述例子中:创建三个窗口卖票,总票数为100张票
1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
4.在java中,我们通过同步机制,来解决线程的安全
6.4、同步代码块和同步方法
1.为什么要使用synchronized
在并发编程中存在线程安全问题,主要原因有:
1.存在共享数据
2.多线程共同操作共享数据。
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。
2.实现原理
synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性
3.synchronized的三种应用方式
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
- 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
- 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
- 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
4.synchronized的作用
Synchronized是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码
6.4.1、同步方法
public synchronized void method(int args)
synchronized 方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
缺陷:若将一个大的方法申明为synchronized 将会影响效率
代码实现:
package MutilThreading;
public class buyTickets implements Runnable
public static void main(String[] args)
buyTickets buyTickets = new buyTickets();
Thread thread = new Thread(buyTickets,"黄牛党");
Thread thread2 = new Thread(buyTickets,"老人");
Thread thread3 = new Thread(buyTickets,"年轻人");
thread.start();
thread2.start();
thread3.start();
private int ticket=10;
boolean flag=true;
@Override
public void run()
while (flag)
try
buy();
catch (InterruptedException e)
e.printStackTrace();
//synchronized 同步方法 锁的是 this
public synchronized void buy() throws InterruptedException
if (ticket==Java-多线程实验