多线程
线程(基础)
概念:
-
什么是程序:
为完成某种任务,用某种语言编写的一组指令的集合(就是我们写的代码)
-
什么是进程:
运行中的程序(就会占用内容空间)
-
什么是线程:
线程由进程创建的,是进程的实体,一个进程可以有多个线程(例如迅雷里,多个下载任务就是多个线程)
-
单线程:
同一个时刻,只允许运行一个线程
-
多线程:
同一个时刻,可以执行多个线程,比如qq可以打开多个聊天窗口,迅雷可以同时下载多个
-
并发:
单核cpu实现的多任务就是并发;同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉(例如,我们人开车的时候打电脑,其实这个时候就是并发,并不是我们可以一心二用,而是我们大脑在车和电话里来回切换)
-
并行:
多核Cpu可以实现并行;同一时刻,多个任务同时执行(例:开车打电话的时候,车里有两个人,一个人开车,一个人打电话)

注:
并发和并行可以同时存在,当你开的进程太多,核(并行执行)不够用,就会有核来回切换并发执行
创建线程

继承Thread:
注:
-
1.当一个类继承了Thread,那这个类就可以当作一个线程使用
-
2.我们会重写一个run方法,run里写我们的业务代码
-
3.这个run方法其实是Thread类继承了Runnable里的run
-
4.当运行一个程序(启动进程),就会先运行main主线程(Test测试也是主线程),主线程可能先比子线程消亡那个的快,但是子线程还是会执行完,且子线程里还可以启动其他子线程
常用方法:
- Thread.sleep()休眠
- start()启动线程
- Thread.currentThread().getName() 获取当前线程名
public class Thread1 {
public static void main(String[] args) throws InterruptedException {
// 创建对象cat,当成线程来使用
Cat cat = new Cat();
// 启动线程
cat.start();
// 当main线程启动了一个子线程Thread-0(cat对象)后,主线程不会阻塞,会继续执行
// 这时主线程和子线程会交替执行
for (int i = 0; i <10 ; i++) {
System.out.println("我是主线程"+Thread.currentThread().getName());
Thread.sleep(1000);
}
}
}
/**
* 1.当一个类继承了Thread,那这个类就可以当作一个线程使用
* 2.我们会重写一个run方法,run里写我们的业务代码
* 3.这个run方法其实是Thread类继承了Runnable里的run
*/
class Cat extends Thread{
@Override
public void run(){
int time=0;
while (true){
time++;
// 获取线程名
System.out.println("喵喵,喔"+time+Thread.currentThread().getName());
try {
// 让线程休眠1s
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (time==80){
break;
}
}
}
// 测试一样是当main用的(获取的当前线程名为mian)
@Test
public void test() throws InterruptedException {
Cat cat = new Cat();
cat.start();
for (int i = 0; i <60 ; i++) {
System.out.println("test测试"+Thread.currentThread().getName());
Thread.sleep(1000);
}
}
图解线程运行:

疑问:启动线程为什么是调用start而不是直接调用run方法
run只是一个普通的方法,调用run,就没有真正执行子线程,才会往下执行
cat.start();> start0();
真正实现多线程的是start0方法,而不是run(普通方法)
private native void start0();》start0是一个本地方法,由jvm调用,底层是c/c++实现
且我们在启动了start0后只是让线程处于可运行状态,具体run()的调用,由cpu决定
实现Runnable接口
Runnable和Thread的区别:
为什么有了继承Thread,还要有这个接口
首先,Thread和Runnable本质上没有什么区别,都是调用start==》start0()
其次,java继承是单继承,当一个类已经继承过一个父类后,那这个类就不能实现线程了,而且实现Runnable接口方便多个线程共享一个资源(一个run),而Thread是实例化3个资源对象,所以建议使用Runnable
使用接口要使用Thread构造器的原因
使用了代理模式,
//线程代理类
class Threadproxy extends Thread{
private static Runnable targe=null;
public static void main(String[] args) {
if (targe!=null){
targe.run();
}
}
Threadproxy(Runnable targe){//这就是那个调用Thread构造器的原因
targe=this.targe;
}
public void start(){
start0();//这个是实现线程的核心方法
}
public void start0(){
run();
}
}
实现Runnable接口实现线程代码:
-
由于Runnable就是个什么都没有的接口,没有启动线程的方法start()
-
此时我们发现Thread有个构造器的参数是放入接口Runnable的,我们可以用Thread的start方法
public class Thread2 {
public static void main(String[] args) throws InterruptedException {
Dog dog = new Dog();
// 由于Runnable就是个什么都没有的接口,没有启动线程的方法start()
要将dog对象放到Thread里
Thread thread = new Thread(dog);
// public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);}
// 此时我们发现Thread有个构造器的参数是放入接口Runnable的,我们可以用Thread的start方法
thread.start();
for (int i = 0; i <20 ; i++) {
System.out.println("主线程"+i+Thread.currentThread().getName());
Thread.sleep(1000);
}
}
}
class Dog implements Runnable{
int time=0;
@Override
public void run() {
while (true){
time++;
System.out.println("小狗汪汪汪!"+time+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (time==80){
break;
}
}
}
}
线程常用方法:
- setName //设置线程名称,使之与参数name相同
- getName//返回该线程的名称
- start//使该线程开始执行;Java虚拟机底层调用该线程的start0方法
- run//调用线程对象run方法;
- setPriority //更改线程的优先级
- getPriority//获取线程的优先级
- sleep/在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
- interrupt //中断线程(并没有真正结束线程,所以一般用来中断正在休眠的线程)
- yield:线程的礼让,让出cpu,,让其他线程执行,但礼让的时间不确认,所以有可能礼让不成功
- join:线程的插入;就像你让别人插队在你前面,你必须让它先执行完,你才能执行(在主线程中执行子线程.join,就是让子线程先执行完在执行主线程)
- wait:导致当前线程等待,直到调用notify()和notifyAll()
public static void main(String[] args) throws InterruptedException {
One one = new One();
one.setName("刘得很");
one.setPriority(1);
one.start();
for (int i = 0; i <10 ; i++) {
Thread.sleep(1000);
System.out.println("主");
}
System.out.println("优先级:"+one.getPriority());
// 实现中断
one.interrupt();
}
}
class One extends Thread{
@Override
public void run(){
while (true){
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"吃包子"+" "+(i+1)+"个");
}
try {
System.out.println("休眠中~~");
Thread.sleep(20000);
} catch (InterruptedException e) {
// 当该线程执行了一个Interrupted的时候就会进入到这里
// 捕获到一个中断异常不是中止
System.out.println(Thread.currentThread().getName()+"被Interrupted了");
}
}
}
插入和礼让:
public static void main(String[] args) throws InterruptedException {
M m = new M();
m.start();
for (int i = 0; i <20; i++) {
Thread.sleep(1000);
System.out.println("小弟(主线程)吃"+(i+1)+"个包子"+Thread.currentThread().getName());
if (i==5){
System.out.println("小弟主线程礼让,大哥子线程开始吃");
m.join();//线程插队
// m.yield();//线程礼让,不一定成功
System.out.println("大哥子线程吃完,主线程小弟继续吃");
}
}
}
}
class M extends Thread{
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("大哥(子线程)吃"+(i+1)+"个包子"+" "+Thread.currentThread().getName());
}
}
}
用户线程和守护线程
概念:
-
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
-
守护线程:一般为工作线程服务,当所有的用户线程结束,守护线程自动结束
-
常见守护线程:垃圾回收机制
设置守护线程:setDaemon(bool);
代码:
public static void main(String[] args) throws InterruptedException {
green green = new green();
// 当主线程的内容(其他用户线程)先结束,子线程(设置成守护线程)就会自己结束
green.setDaemon(true);
green.start();
for (int i = 0; i <5 ; i++) {
System.out.println("宝强在辛苦工作");
Thread.sleep(1000);
}
}
}
class green extends Thread{
@Override
public void run() {
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("马蓉和宋hhh");
}
}
}
线程7种生命周期

public static void main(String[] args) throws InterruptedException {
One one = new One();
新建时
System.out.println("新建时线程名:"+one.getName()+"状态:"+one.getState());
one.start();
while (Thread.State.TERMINATED!=one.getState()){
Thread.sleep(1000);
// 只要状态不等于结束时的状态就一直打印
启动时
System.out.println("启动时线程名:"+one.getName()+"状态:"+one.getState());
}
结束时
System.out.println("结束时线程名:"+one.getName()+"状态:"+one.getState());
}
}
class One extends Thread{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("你好");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
同步机制
多线程实现售票
问题代码:
public static void main(String[] args) {
T1 t1 = new T1();
T1 t2 = new T1();
T1 t3 = new T1();
// 这里会出现超卖的情况
t1.start();
t2.start();
t3.start();
}
}
class T1 extends Thread{
private static int ticket=100;
@Override
public void run(){
while (true){
if (ticket<=0){
System.out.println("售票结束。。。");
break;
}
System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张票:"+"剩余票数:"+(--ticket));
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上面的代码会出现重票和超卖的问题
超卖:(可能超卖也可以不超卖,不安全),由于你给了休眠时间,当一个线程休眠后,另外一个线程执行刚好为0,第一个线程恢复又-1,所以就会出现超卖现象
线程终止
通知退出:在一个线程里设置一个控制变量,另一个线程控制这个控制变量
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
// 设置主线程的休眠时间然后控制子线程变量
Thread.sleep(10000);
t.setT(false);
}
}
class T extends Thread{
// 设置控制变量
private Boolean T=true;
private int count = 0;
@Override
public void run() {
while (T){
System.out.println(Thread.currentThread().getName()+" "+(count++));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setT(Boolean t) {
T = t;
}
解决卖票问题
同步机制
什么叫同步?
当有一个共享数据被多个线程访问到(例如上面卖票),这个时候就需要同步机制,保证数据在任何的同一时刻只能被一个线程所访问到
同步用到的方法
-
同步代码块
synchronized (对象){
同步代码
}
2.同步方法
public synchronized void m(){
同步代码
}
理解:
就像上厕所一样为了你滴隐私(为了数据安全),会有一个门(同步锁),只有当一个人出来后,另一个人才能关上门(拿到锁)进去
同步方法解决卖票问题
同步方法这里是不能使用继承Thread的方式的,因为同步方法默认就是this当前对象,而实例化3个继承了Thread的对象就会有3个不同的对象,他们拿的锁的对象不同
public static void main(String[] args) {
T1 t1 = new T1();
Thread T1 = new Thread(t1);
Thread T2 = new Thread(t1);
Thread T3 = new Thread(t1);
// 出现超卖的情况
T1.start();
T2.start();
T3.start();
}
}
class T1 implements Runnable{
private static int ticket=100;
private Boolean T=true; //通知退出变量
public synchronized void sell(){//同步方法,在同一个时刻只有一个线程来执行这个方法
if (ticket<=0){
System.out.println("售票结束。。。");
T=false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张票:"+"剩余票数:"+(--ticket));
}
@Override
public void run(){
while (T) {
sell();
}
}
互斥锁
- 每个对象都对应着一个可称为”互斥锁“的标记,这个标记保证任何时刻都只有一个线程访问该对象
- 同步的局限:变成单线程,影响效率
- 同步方法(静态)的锁为类本身,同步方法(非静态)的锁可以是this也可以是其他对象(要求是同一个对象)
- 只有拿到互斥锁,同步代码才能算是同步
核心点(*):
保证同步的关键就在保证锁是同一个对象,咱几个线程等的是同一个对象(就是必须保证是咱几个人等的是同一个厕所)
使用继承Thread的线程用同步代码块的方式解决售票问题
public static void main(String[] args) {
T t = new T();
T t2 = new T();
T t3= new T();
t.start();
t2.start();
t3.start();
}
}
class T extends Thread{
public static int ticket=100;
private static Object o=new Object();//共用一个对象o
private Boolean T=true; //通知退出变量
@Override
public void run(){
while (T) {
synchronized (o){
if (ticket<=0){
System.out.println("售票结束。。。");
T=false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口"+Thread.currentThread().getName()+"卖出一张票:"+"剩余票数:"+(--ticket));
}
}
}
}
同步方法是静态或者非静态:
class t implements Runnable{
public static void m(){
synchronized (t.class){
System.out.println("当是静态方法时,锁必须是当前类");
}
}
public void n(){
synchronized (this){
System.out.println("当不是静态方法,锁可以是当前对象或者其他对象");
}
}
@Override
public void run() {
m();
}
死锁
例:
比如两个掏枪的人,第一个人对另外一个人说,你放下枪,我就放下枪,
结果第二个人对第一个人也这么说,你放下枪,我就放下枪,两个人就陷入死循环
死锁代码及解决:
public static void main(String[] args) {
T t1 = new T(true);//true,使代码先执行o1,o1后面要用o2,而o2在下面那个线程那里
T t2 = new T(false);//false,使代码先执行o2,o2后面要用o1,而o1在上面那个线程那里
t1.start();
t2.start();
}
}
class T extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
private Boolean T;
T(Boolean T){
this.T=T;
}
@Override
public void run() {
if (T){
synchronized (o1){
System.out.println("A"+Thread.currentThread().getName()+"进入1");
synchronized (o2){
System.out.println("B"+Thread.currentThread().getName()+"进入2");
}
}
}else{
synchronized (o2){
try {
// 解决:设置休眠,就可以先让上面的代码拿到o2锁
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B"+Thread.currentThread().getName()+"进入3");
synchronized (o1){
System.out.println("A"+Thread.currentThread().getName()+"进入4");
}
}
}
}
}
释放锁

浙公网安备 33010602011771号