多线程学习笔记(一)---- 基础知识

一、相关概念

1、进程

正在运行的程序

2、线程

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

  • 一个进程可以有多个线程,即为多线程;
  • 一个进程中至少要有一个线程;

3、多线程的意义

(1)好处
解决了多部分同时运行的问题;
(2)弊端
线程太多,回收效率降低;
(3)多线程的实质
多线程其实是由cpu在做着快速的切换应用程序完成的,这个切换是随机的。由于切换速度太快,让人以为是同时执行;
(4)注意

  • 在多线程程序中,只要有一条线程没完成,该程序都还没有完成;即使main线程完成了;
  • 各个线程独立运行,互不影响;即使某个线程出现异常,其他线程也不受影响;

4、线程的四种状态

多线程学习笔记(一)---- 基础知识_第1张图片

二、创建多线程的俩种方法

1、继承Thread类

(1)步骤

  • 定义一个类继承Thread类;
  • 并覆盖其run方法,在run方法中写关于你要执行的程序;
  • 创建Thread的子类对象创建线程;
  • 调用start方法开启线程,并调用run方法执行线程;

(2)举例

public class Doem extends Thread{
    String name;
    
    Doem(String name){
        this.name = name;
    }
    
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(name);
        }
    }
}


public class Test {
    public static void main(String[] args) {
        Doem a1 = new Doem("xiaoqiang");
        Doem a2 = new Doem("旺财");
        
        a1.start();
        a2.start();

    }
}

(3)Thread类介绍

  • String getName();//获取线程名称
  • public static Thread currentThread();//获取当前运行的线程

2、实现Runnable接口

(1)步骤

  • 定义类实现Runnable接口;
  • 覆盖接口中的run方法,将线程的任务代码封装到run方法中;
  • 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造参数进行传递;
  • 调用线程对象的start方法开启线程;

(2)举例

public class Doem implements Runnable{
    String name;
    
    Doem(String name){
        this.name = name;
    }
    
    public void run(){
        for(int i=0;i<10;i++){
            System.out.println(name+"         "+Thread.currentThread().getName());
        }
    }
}


public class Test {
    public static void main(String[] args) {
        Doem a1 = new Doem("xiaoqiang");
        Doem a2 = new Doem("旺财");
        
        Thread test1 = new Thread(a1);
        Thread test2 = new Thread(a2);
        
        test1.start();
        test2.start();
        
}
///////////结果///////////
旺财         Thread-1
xiaoqiang         Thread-0
xiaoqiang         Thread-0
xiaoqiang         Thread-0
xiaoqiang         Thread-0
xiaoqiang         Thread-0
旺财         Thread-1
xiaoqiang         Thread-0
xiaoqiang         Thread-0
xiaoqiang         Thread-0
xiaoqiang         Thread-0
xiaoqiang         Thread-0
旺财         Thread-1
旺财         Thread-1
旺财         Thread-1
旺财         Thread-1
旺财         Thread-1
旺财         Thread-1
旺财         Thread-1
旺财         Thread-1

(3)实现Runnable接口方式的好处

  • 将线程的任务(run方法)从线程的子类中分离出来,将其分装成对象;
  • 在Java中,一个类只能继承一个类,但可以实现多个接口。如果一个类已经继承一个类,那么就可以实现Runable接口来实现多线程的功能;

由于第2点好处,所以实现多线程功能常用实现Runable接口的方法。

3、俩种方式的比较

多线程学习笔记(一)---- 基础知识_第2张图片

三、多线程中俩个主要问题

1、前言

在学习多线程开始,也是我们大多数刚学多线程的例子,都会遇到俩个问题:“共享资源”、“代码安全隐患”。先大体上说明这俩个问题和其解决办法:
多线程学习笔记(一)---- 基础知识_第3张图片

2、示例

(1)售卖火车票
在四个售票窗口同时销售一个火车票
(2)错误代码示例

public class Ticket extends Thread{
	//火车票的总票数
	private int num = 100;
	
	//卖票
	public void sale(){
		while(true){
			if(num>0){
				System.out.println(Thread.currentThread().getName()+"---"+num--);
			}
		}
	} 
	
	//多线程的任务
	public void run(){
		sale();
	}
}

/////////////////////////////////////////////////////////

public class Doem {
	public static void main(String[] args) {
		//建立四个多线程
		Ticket doem1 = new Ticket();
		Ticket doem2 = new Ticket();
		Ticket doem3 = new Ticket();
		Ticket doem4 = new Ticket();
		
		//四个线程开始售票
		doem1.start();
		doem2.start();
		doem3.start();
		doem4.start();
	}
}

分析:这里就犯了“共享资源”和“代码安全隐患”问题。

  • private int num = 100,是共享的,像上述代码那样写,属于每个线程都有100张票,这自然是不可以的;
  • 第8、9行代码,没有同步。可能会造成出现售卖0、-1、-2张票的情况;

(3)解决“共享资源”问题的方法

  • 当继承Thread类实现多线程时
    在继承Thread类上,找出要共享的私有属性,在其加上“static”关键字;
public class Ticket extends Thread{
	//火车票的总票数
	private static int num = 100;
	
	//卖票
	public void sale(){
		while(true){
			if(num>0){
				System.out.println(Thread.currentThread().getName()+"---"+num--);
			}
		}
	} 
	
	//多线程的任务
	public void run(){
		sale();
	}
}
  • 实现Runnable接口实现多线程时
    在实现Runnable接口的类上,找出要共享的私有属性,在其加上“static”关键字;
/*建立任务对象*/
public class Ticket implements Runnable{
	//火车票的总票数
	private static int num = 100;
	
	//卖票
	public void sale(){
		while(true){
			if(num>0){
				System.out.println(Thread.currentThread().getName()+"---"+num--);
			}
		}
	} 
	
	//多线程的任务
	public void run(){
		sale();
	}
}

虽然这俩种方式都是加“static”关键字来解决“共享资源”问题。但是第二种实现Runnable接口这种方式比第一种更好,实现Runnable接口相当于建立一个“任务对象”,更符合面向对象的思想。

(4)解决“代码安全隐患”问题

  • 同步代码块介绍
    1、格式
synchronized (任意对象) {
    需要同步的代码块;	
}

synchronized的“任意对象”称之为锁。当一个线程进入同步代码块时,它会将“锁”"锁上",防止其他线程再次进入;当该线程离开同步代码块时,它就会将“锁”“打开”。这样,其他线程就有可能进入同步代码块了。
2、同步的优、缺点
同步的好处:解决了线程的安全问题;
同步的弊端:程序整体代码效率低;
3、注意
同一个任务时、在同步时,是多个线程使用同一个锁。

  • 同步代码块解决问题
public class Ticket implements Runnable{
	//火车票的总票数
	private static int num = 100;
	
	Object obj = new Object();
	
	//卖票
	public void run(){
		while(true){
			synchronized (obj) {
				if(num>0){
					try {
						Thread.sleep(20);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					
					System.out.println(Thread.currentThread().getName()+"---"+num--);
				}
			}
		}
	} 
}
//////////////////////////////////////////////////////////////////////
public class Doem {
	public static void main(String[] args) {
		//建立任务对象
		Ticket test = new Ticket();
		
		//创建四个线程,共同完成一个任务对象
		Thread doem1 = new Thread(test);
		Thread doem2 = new Thread(test);
		Thread doem3 = new Thread(test);
		Thread doem4 = new Thread(test);
		
		//开启四个线程
		doem1.start();
		doem2.start();
		doem3.start();
		doem4.start();
	}
}
  • 同步函数介绍
    1、格式
    在需要同步函数加“synchronized”关键词
    2、同步函数的锁
    是this,谁使用,锁就是当前对象

  • 使用同步函数解决问题
    1、需求介绍
    一个银行,俩个用户分别向该银行存钱
    2、代码实现

public class Bank {
	//该银行的总存钱数
	private int sum = 0;
	
	//向银行存钱
	public synchronized void add(int num){
		sum = sum + num;
		System.out.println("sum="+sum);
	}
}
public class Customer implements Runnable{
	private Bank bank = new Bank();
	
	//顾客的存钱方法
	public void run(){
		for(int x=0;x<3;x++){
			bank.add(100);
		}
	}
}
public class Doem {
	public static void main(String[] args) {
		//多线程任务:存钱
		Customer customer1 = new Customer();
		
		//
		Thread doem1 = new Thread(customer1);
		Thread doem2 = new Thread(customer1);
		
		//
		doem1.start();
		doem2.start();
	}
}
////////////////////////////////////////////
sum=100
sum=200
sum=300
sum=400
sum=500
sum=600
  • 关于锁的说明
    在同步函数上自然没必要说明,它指定了,我们也无需改变;但是,在同步代码块呢?
    在使用同步代码时,我们一定要看清楚,使用该“同步代码”的线程是哪几个,这几个线程所对应的唯一一个对象,又是谁?一定要记住“多个线程,对应一个相同的锁”。
    这里推荐在锁上写,实现Runnable接口的类的class对象。例如:Class Doem implements Runnable,我们在同步代码块用锁的时候,只需要写“Doem.class”。

你可能感兴趣的:(多线程学习笔记(一)---- 基础知识)