Java多线程

目录

 

一、如何理解多线程

1.多线程概念

2.并行和并发

二、多线程的实现方式

1.继承Thread

2.实现Runnable

三、实现Runnable的原理

四、两种实现多线程方式的区别

五、匿名内部类实现线程的两种方式

1.继承Thread类

2.实现Runnable接口

六、多线程方法

1.获取名字

2.设置名字

3.获取当前线程的对象Thread.currentThread()

4.休眠线程 Thread.sleep()

5.守护线程 setDaemon()

6.加入线程

7.礼让线程 yield让出cpu

8.设置线程的优先级 setPriority()

七、同步代码块

八、线程安全问题


一、如何理解多线程

1.多线程概念

我们都知道一个进程中可以包含多条线程,那么多线程在应用程序中是如何体现的?比如某管家可以同时清理垃圾和查杀病毒,某app可以同时浏览商品和监听用户,JVM也是多线程的,它至少启动了gc和主线程:

public class Demo1 {


	public static void main(String[] args) {
		for(int i = 0; i < 1000000; i++) {
			new Demo();
		}
		
		for(int i = 0; i < 10000; i++) {
			System.out.println("主线程在执行");
		}
	}

}

class Demo {

	@Override
	public void finalize() {
		System.out.println("清理垃圾。。。");
	}
	
}

由上可以看出两条线程是穿插执行的 。

2.并行和并发

  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU),比如古代三妻四妾,一个男人同时娶多个女人。

  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。 比如现代渣男,同时谈多个女朋友,利用时间片轮转的方法,使每个妹子都觉得自己是独占渣男的。

二、多线程的实现方式

1.继承Thread

  • 定义类继承Thread

  • 重写run方法

  • 把新线程要做的事写在run方法中

  • 创建线程对象

  • 开启新线程, 内部会自动执行run方法

public class Demo2 {
	public static void main(String[] args) {
		MyThread mt = new                  
                MyThread();	//4,创建自定义类的对象
		mt.start();	//5,开启线程
	
		for(int i = 0; i < 3000; i++) {
			System.out.println("bb");
		}
	}

}
class MyThread extends Thread{		//1,定义类继承Thread
    public void run(){	//2,重写run方法
	for(int i = 0; i < 3000; i++) {	//3,将要执行的代码,写在run方法中
	    System.out.println("aa-----------------------");
	}
    }
}

2.实现Runnable

  • 定义类实现Runnable接口

  • 实现run方法

  • 把新线程要做的事写在run方法中

  • 创建自定义的Runnable的子类对象

  • 创建Thread对象, 传入Runnable

  • 调用start()开启新线程, 内部会自动调用Runnable的run()方法

public class Demo3 {
	
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable(); //4,创建自定义类对象
		Thread t = new Thread(mr);	//5,将其当作参数传递给Thread的构造函数
		t.start();	//6,开启线程
		
		for(int i = 0; i < 1000; i++) {
			System.out.println("bb");
		}
	}

}

class MyRunnable implements Runnable {		//1,自定义类实现Runnable接口
	@Override
	public void run() {		//2,重写run方法
		for(int i = 0; i < 1000; i++) {	   //3,将要执行的代码,写在run方法中
			System.out.println("aaaaaaaaaaaa");
		}
	}	
}

三、实现Runnable的原理

  • 看Thread类的构造函数,传递了Runnable接口的引用

    public Thread(Runnable target) { //target就相当于上例中的mr
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
  • 通过init()方法找到传递的target给成员变量的target赋值

    private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
        init(g, target, name, stackSize, null);
    }

 

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    
        ...

    this.target = target;  //发现将此局部变量赋值给了成员变量

        ...
}

mr一层一层传递到成员变量:

    /* What will be run. */
    private Runnable target;

 

  • 查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

四、两种实现多线程方式的区别

  • 继承Thread : 由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法

  • 实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法

  • 优缺点:
 继承Thread类实现Runnable接口
优点

可以直接使用Thread类中的方法,代码简单

即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
缺点如果已经有了父类,就不能用这种方法不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

五、匿名内部类实现线程的两种方式

1.继承Thread类

	new Thread() {      //1.继承Thread类
		public void run() {  //2.重写run方法
			for(int i = 0; i < 1000; i++) {   //3.将要执行的代码写在run方法中
				System.out.println("匿名内部类 继承Thread类实现多线程");
			}
		}
	}.start();  //4.开启线程

2.实现Runnable接口

    new Thread(new Runnable(){	   //1.将runnable的子类对象传递给Thread的构造方法
	    public void run() {    //2.重写run方法
	        for(int i = 0; i < 3000; i++) {	//3.将要执行的代码,写在run方法中
		        System.out.println("匿名内部类 实现runnable接口");
	        }
            }
    }).start(); //4.开启线程

六、多线程方法

1.获取名字

通过getName()方法获取线程对象的名字

2.设置名字

通过构造函数可以传入String类型的名字

new Thread("yyy") { //设置名字
	public void run() {
		for(int i = 0; i < 1000; i++) {
			System.out.println(this.getName() + "....bb");
		}
	}
}.start(); 

通过setName(String)方法可以设置线程对象的名字

	Thread t1 = new Thread() {
		public void run() {
			System.out.println(this.getName() + "....aaaaaaaaaaaaa");
		}
	};
        t1.setName("t1");
        t1.start();

3.获取当前线程的对象Thread.currentThread()

new Thread(new Runnable() {
	public void run() {
		for(int i = 0; i < 1000; i++) {
			System.out.println(Thread.currentThread().getName() + "...bb");
		}
	}
}).start();
Thread.currentThread().setName("我是主线程");					//获取主函数线程的引用,并改名字
System.out.println(Thread.currentThread().getName());		//获取主函数线程的引用,并获取名字

4.休眠线程 Thread.sleep()

	new Thread() {
		public void run() {
			for(int i = 0; i < 10; i++) {
				System.out.println(getName() + "...bb");
				try {
					Thread.sleep(1000);  //休眠1000毫秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}.start();

5.守护线程 setDaemon()

设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出

Thread t2 = new Thread() {
	public void run() {
		for(int i = 0; i < 5; i++) {
			System.out.println(getName() + "...bb");
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
};

t1.setDaemon(true);						//将t1设置为守护线程

t1.start();
t2.start();

6.加入线程

  • join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
  • join(int), 可以等待指定的毫秒之后继续

7.礼让线程 yield让出cpu

8.设置线程的优先级 setPriority()

七、同步代码块

使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块 ,多个同步代码块如果使用相同的锁对象, 那么他们就是同步的

class Printer {
    public static void print1() {
	synchronized(Printer.class){	//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
		System.out.print("H");
		System.out.print("e");
		System.out.print("l");
		System.out.print("l");
		System.out.print("o");
		System.out.print("\r\n");
	}
    }
    /*
     * 非静态同步函数的锁是:this
     * 静态的同步函数的锁是:字节码对象
     */
    public static synchronized void print2() {	
	    System.out.print("W");
	    System.out.print("o");
	    System.out.print("r");
	    System.out.print("l");
            System.out.print("d");
	    System.out.print("\r\n");
    }
}

八、线程安全问题

多线程并发操作同一数据时, 就有可能出现线程安全问题,使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作。

多线程的经典案例(卖票问题)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值