java main函数 多线程_Java多线程

多线程

应用程序的执行都是cpu在做着快速的切换完成的。这个切换是随机的

1、进程

直译:正在进行中的程序

一个程序就是一个进程,而一个程序中的多个任务则被称为线程,进程其实就是一个静态的概念

2、线程(控制单元/执行路径)

就是进程中一个负责程序执行的控制单元(执行路径)

一个线程中可以执行多个路径,称之为多线程

一个进程中至少有一个线程

开启多线程是为了同时运行多部分代码,每一个线程都有自己运行的内容。这个内容可以称为线程要执行的任务

3、多线程存在的利弊

多线程的好处:解决了多部分同时运行的问题

多线程的弊端:线程太多会使运行效率的降低

4、JVM中的多线程解析

JVM虚拟机的启动时本身就是多线程,至少有两个可以分析出来

执行main函数的线程

该线程的任务代码都定义在main函数中

负责垃圾回收的线程

该线程的任务代码都在垃圾回收器中

垃圾回收器实际上就是垃圾回收程序,可以通过系统System类中中的gc()方法唤醒调用

class Demo extends Object{

public void finalize(){

System.out.println("demo ok");

}

}

class ThreadDemo{

public static void main(){

new Demo();

new Demo();

System.gc();

new Demo();

System.out.println("hello zimo!");

}

}

// >: hello zimo!

// >: demo ok

// >: demo ok

5、创建线程

创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行,运行的指定代码就是这个执行路径的任务。

所以开启线程是为了运行指定代码,只有继承Thread类,并复写run方法,将运行的代码定义在run方法中即可。

jvm创建的主线程的任务都定义在了主函数中,而自定义的线程任务运行在哪?

​ Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述。这个任务就是通过Thread类中的run方法来体现。也就是说,run方法就是封装自定义线程运行任务的函数。

​ run方法就是定义在线程要运行的任务代码。

1、创建线程方式一:继承Thread类

定义一个类继承Thread类

覆盖Thread类中的run方法

直接创建Thread的子类对象创建线程

调用start方法开启线程并调用线程的任务run方法执行

多线程实现两个对象同时运行实例:

class Demo extends Thread{

private String name;

Demo(String name){

// super(name); // 给线程起个名

this.name = name;

}

public run(){

show();

}

public void show(){

for(int i = 0; i < 20; i++){

System.out.println(name + "....." + i + getName());

}

}

}

class ThreadDemo{

public static void main(){

Demo d1 = new Demo("zimo");

Demo d2 = new Demo("mozi");

// d1.run();

// d2.run();

d1.start(); // 开启线程,调用run方法

d2.start();

System.out.println("hello zimo!");

}

}

可以通过Thread的getName()获取线程的名称 Thread - 编号(从0开始)

获取当前运行线程名称 Thread.currentThread().getName() 获取线程名称

主线程的名称:main

2、创建线程方式二:实现Runnable接口

定义类实现Runnable接口

覆盖接口中的run()方法,将线程的任务代码封装到run()方法中

通过Thread类创建线程对象,并将Runnable接口的子类对象作为构造函数的参数进行传递

因为线程的任务都封装在Runnable接口子类对象的run()方法中,所以要在线程对象创建时就必须明确要运行的任务

调用线程对象的start()方法开启线程

如果该类已经继承了一个父类,想扩展功能为多线程,可以通过接口的形式完成

它的出现仅仅是将线程的任务进行了对象的封装

class Demo extends FuDemo implements Runnable{

private String name;

Demo(String name){

// super(name); // 给线程起个名

this.name = name;

}

// 覆盖接口中的run方法

public void run(){

show();

}

public void show(){

for(int i = 0; i < 20; i++){

System.out.println(name + "....." + i + getName());

}

}

}

class ThreadDemo{

public static void main(){

Demo d = new Demo();

Thread t1 = new Thread(d);

Thread t2 = new Thread(d);

t1.start(); // 开启线程,调用run方法

t2.start();

System.out.println("hello zimo!");

}

}

实现Runnable接口的好处:

将线程的任务从线程的子类中分离出来,进行了单独的封装

按照面向对象的思想将任务封装成了对象

避免Java单继承的局限性,所以第二种常用

6、线程的四种状态

CPU的执行资格:可以被CPU处理到,在处理的队列中排队

CPU的执行权:正在被CPU进行处理

63d0196732e6

线程的四种状态.png

sleep方法需要指定睡眠时间,单位是毫秒。

一个特殊的状态:就绪。具备了执行资格,但是还没有获取资源

多线程示例:卖票

class Ticket extends Thread{

private static int num = 100; // 如果不用静态的 他就会每个线程有独立的100张票

public void run(){

while(num > 0){

System.out.println(Thread.currentThread().getName() + "..sale.."+num--);

}

}

}

class Ticket1 implements Runnable{

private int num = 100;

public void run(){

while(num > 0){

System.out.println(Thread.currentThread().getName() + "..sale.."+num--);

}

}

}

class TicketDemo{

public static void main(String[] args){

Ticket t1 = new Ticket();

Ticket t2 = new Ticket();

Ticket t3 = new Ticket();

Ticket t4 = new Ticket();

t1.start();

// t1.start(); // 多次启动会抛出异常

t2.start();

t3.start();

t4.start();

Ticket1 t = new Ticket1(); // 创建一个线程任务对象

Ticket1 tt1 = new Ticket1(t);

Ticket1 tt2 = new Ticket1(t);

Ticket1 tt3 = new Ticket1(t);

Ticket1 tt4 = new Ticket1(t);

tt1.start();

tt2.start();

tt3.start();

tt4.start();

}

}

63d0196732e6

多线程卖票内存分析图.png

7、线程安全问题

导致产生线程安全的原因:

多个线程在操作共享的数据

操作共享数据的线程代码有多条

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算

解决方法:

Java中使用同步代码块:synchronized(对象){需要被同步的代码块}

同步函数:pubilc synchronized void add(int num){}

同步锁使用前提:同步中必须有多个线程,并且使用同一个锁。

同步的好处:解决了安全问题>;

同步的弊端:相对降低了效利,因为同步外的线程都会判断同步锁

class Ticket implements Runnable{

private int num = 100;

Object obj = new Object();

public void run(){

// Object obj = new Object(); //假设所在方法里,每个线程都有自己单独的锁,那么还是存在问题

while(num > 0){

synchronized(obj){

// synchronized(new Object()){ // err 相当于一个线程有一个独立的对象

System.out.println(Thread.currentThread().getName() + "..sale.."+num--);

}

}

}

}

class TicketDemo{

public static void main(String[] args){

Ticket t = new Ticket(); // 创建一个线程任务对象

Ticket tt1 = new Ticket(t);

Ticket tt2 = new Ticket(t);

Ticket tt3 = new Ticket(t);

Ticket tt4 = new Ticket(t);

tt1.start();

tt2.start();

tt3.start();

tt4.start();

}

}

8、线程同步函数示例

// 需求:两个储户,每个都到同一家银行,每次100,共三次

class Bank{

private int sumMoney;

// private Object obj = new Object();

public synchronized void add(int money){ // 同步函数

// synchronized(obj){

sum += sum;

try {Thread.sleep(10);}catch(InterruptedException e){}

System.out.println("sum = " + sum);

// }

}

}

class Cus implements Runnable{

private Bank b = new Bank();

for(int i = 0; i < 3; i++){

b.add(100);

}

}

class CusBankDemo{

public static void main(String[] args){

Cus c = new Cus();

Thread t1 = new Thread(c);

Thread t2 = new Thread(c);

t1.start()

}

}

验证同步函数的锁

同步函数锁使用的是this对象

class Ticket implements Runnable{

private int num = 100;

// Object obj = new Object();

public void run(){

if(flag){

while(true){

// synchronized(obj){ // 如果是obj,那么线程代码块和线程函数的锁不是同意把,还是存在安全问题

synchronized(this){

System.out.println("this:" + this);

if(num > 0){

System.out.println(Thread.currentThread().getName() + "..sale.."+num--);

}

}

}

}else

while(true)

show();

}

public synchronized void show(){

// public static synchronized void show(){ // 静态代码块没有this对象,只有一个getClass获取的当前class字节码所属的对象

// 同步代码块可以通过传synchronized(this.getClass()){}来实现同步

// 获取字节码文件对象还可以通过类型.class: Ticket.class

System.out.println(Thread.currentThread().getName() + "..sale.."+num--);

}

}

class TicketDemo{

public static void main(String[] args){

Ticket t = new Ticket(); // 创建一个线程任务对象

System.out.println("t:" + t);

Ticket tt1 = new Ticket(t);

Ticket tt2 = new Ticket(t);

tt1.start();

tt2.start();

}

}

同步函数和同步代码块的区别是:

同步函数的锁是固定的this

同步代码块的锁是任意的对象

静态的同步函数使用的锁是,该函数所属字节码文件,该同步锁对象不是this

可以通过getClass()方法获取,也可以用当前类名.class表示

建议使用同步代码块

9、多线程下的单例模式

饿汉式(单例模式)

class Single{

private static final Single s = new Single(); // 1.固定不变,一开始就被创建

private Single(){}

public static Single getInstance(){

return s; // 2.返回地址,不存在线程安全问题

}

}

懒汉式(延迟加载单例模式)

class Single{

private static final Single s = null; // 共享数据

private Single(){}

public static Single getInstance(){

// 方法一:虽然解决了问题,但是每次进来都要判断锁,效率低

// public static synchronized Single getInstance(){

// 方法二:同步代码块,这样写还是和同步方法没区别,一进来就要判断锁

synchronized(Single.class){ // 这边不能使用getClass()方法,是非静态的

if(s == null){

// 可能存在线程切换同时进入这里

s = new Single(); // 产生多个对象,不能保证唯一性

}

}

return s;

}

// 改良,通过双重判断解决懒汉式的 线程安全问题 和 效率问题

public static Single getInstance(){

if(s == null){

synchronized(Single.class){

if(s == null){

s = new Single();

}

}

}

return s;

}

}

10、死锁示例

死锁:常见的情景之一:同步的嵌套

class Ticket implements Runnable{

private int num = 100;

private boolean flag = true;

Object obj = new Object();

public void run(){

if(flag){

while(true){

synchronized(obj){ // obj -->> this

show();

}

}

}else

while(true)

show();

}

public synchronized void show(){ // this -->> obj

synchronized(obj){

System.out.println(Thread.currentThread().getName() + "..sale.."+num--);

}

}

}

class DeadLockDemo{

public static void main(String[] args){

Ticket t = new Ticket(); // 创建一个线程任务对象

Ticket t1 = new Ticket(t);

Ticket t2 = new Ticket(t);

t.flag = false;

t1.start();

t2.start();

}

}

手动实现死锁示例:遵循原则--嵌套

class Test implements Runnable{

private boolean flag;

Test(boolean flag){

this.flag = flag;

}

public void run{

if(flag){

synchronized(MyLock.locka){

system.out.println("if-locka");

synchronized(MyLock.lockb){

system.out.println("if-lockb");

}

}

}else{

synchronized(MyLock.lockb){

system.out.println("else-locka");

synchronized(MyLock.locka){

system.out.println("else-lockb");

}

}

}

}

}

class MyLock{

public static final Object locka = new Object();

public static final Object lockb = new Object();

}

class DeadLockTest{

public static void main(String[] args){

Test a = new Test(true);

Test b = new Test(false);

Thread t1 = new Thread(a);

Thread t2 = new Thread(b);

t1.start();

t2.start();

System.out.println("hello zimo!");

}

}

参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页

打赏作者

力气不语

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值