多线程的基本概念与线程安全问题

版权声明:本文为博主原创文章,转载请标明出处。https://blog.csdn.net/kXYOnA63Ag9zqtXx0/article/details/82954503 https://blog.csdn.net/forever428/article/details/83240638

多线程:

基本概念

  • 程序:是一个可执行的文件.

  • 进程:是一个正在运行的程序.在内存中开辟了一块儿空间

  • 线程:负责程序的运行,可以看做程序运行的一条通道或者一个执行单元.所以我们通常将进程的工作理解成线程的工作.

  • 进程中可不可以没有线程?

    • 必须有线程,至少有一个,当有一个线程存在的时候,我们称为单线程,这个唯一的线程就是主线程(main线程)
  • 当有一个以上的线程存在的时候,我们称为多线程.

  • 多线程存在的意义:为了实现同一时间做多件事情.

  • 任务区:我们将线程完成工作的方法称为任务区

    • 每一个线程都有自己的任务区.
  • JVM是多线程吗?

    • 一定是多线程
    • 至少有两个
    • 主线程的任务区:main方法
    • 垃圾回收线程的任务区:finalize()方法
public class Demo7 {
	public static void main(String[] args) {//一个main线程
		new Test();
		
		/*
		 * 手动执行gc方法,运行垃圾回收器,触发垃圾回收机制.
		 * 工作原理:执行gc方法,触发垃圾回收机制,运行垃圾回收线程,调用finalize()方法
		 * 
		 * 多个线程是抢cpu的关系,cpu有随机性
		 */
		System.gc();//两个:主线程和垃圾回收线程
		
		System.out.println("main");
	}//线程是随着任务的开始而开始结束而结束,只要任务没有结束,线程就不会结束.当线程还在工作的时候,进程没有办法结束.
}

class Test{
	/*
	 * finalize()方法应该由系统调用,为了模拟多线程的使用环境,我们将它进行了重写
	 * 正常情况下,当Test类的对象被释放的时候,这个方法就会被调用
	 */
	protected void finalize() throws Throwable {
	    System.out.println("finalize");
	}
}

自己创建线程的原因

系统的线程无法完成我们自己的功能,我们就自己创建线程.系统将线程面向对象了,形成的类就是Thread.

  • Thread的任务区是run()方法

  • 注意:如果我们想让run方法作为任务区,不能手动去调用,必须通过调用start方法,让run自动执行.

  • 两种创建线程的方式:

    1. 通过Thread直接创建线程
    2. 重写的run方法,作为任务区
public class Demo8 {
//	public static void main(String[] args) {//为了方便研究,暂时忽略垃圾回收线程.认为这里有一个线程--main线程
//		//1.通过Thread直接创建线程
//		//创建两个线程
//		Thread thread1 = new Thread();
//		Thread thread2 = new Thread();
//		
//		//通过start方法让线程工作
//		thread1.start();
//		thread2.start();//有三个线程,两个子线程一个主线程
//		
//		System.out.println("main");
//	}
	
	public static void main(String[] args) {//为了方便研究,暂时忽略垃圾回收线程.认为这里有一个线程--main线程
		//1.通过Thread的子类创建线程
		//创建两个线程
		MyThread thread1 = new MyThread("bingbing");//thread-0
		MyThread thread2 = new MyThread("yingying");//thread-1
		
		//通过start方法让线程工作
		thread1.start();
		thread2.start();//有三个线程,两个子线程一个主线程
		
		for(int i=0;i<10;i++){
			System.out.println(Thread.currentThread().getName()+" main  i:"+i);
		}
		
		/*
		 * 当手动调用run时,run变成了普通方法,失去了任务区的功能.
		 * run内部对应的线程就是run方法被手动调用的位置对应的线程.
		 */
		//thread1.run();
	}
}

class MyThread extends Thread{
	String myname;
	public MyThread(String myname) {
		super();
		this.myname = myname;
	}
	@Override
	public void run() {
		/*
		 * 重写的run方法,作为任务区
		 * Thread.currentThread():获取的当前线程
		 * Thread.currentThread().getName():获取的是当前线程的名字,系统给的名字.
		 */
		for(int i=0;i<10;i++){
			System.out.println(Thread.currentThread().getName()+"  "+myname+"  i:"+i);
		}
	}
}

线程的两种创建方式:

  1. 创建线程的第一种方式:通过创建Thread类的子类—让run留在了线程了内部,造成任务与线程的绑定,操作不方便
  2. 创建线程的第二种方式:让线程与任务分离—将run从线程中独立出来.好处:操作更方便,那个线程想工作,我就把任务交给谁

实例:实现四个售票员售票

  • 分析:创建4个线程–模拟四个售票员
  • 任务:只需要一个
  • 数据:只需要一个

创建线程的第一种方式

public class Demo2 {
	public static void main(String[] args) {
		//创建线程对象
		Seller seller1 = new Seller();
		Seller seller2 = new Seller();
		Seller seller3 = new Seller();
		Seller seller4 = new Seller();
		
		//开启线程
		seller1.start();
		seller2.start();
		seller3.start();
		seller4.start();
	}
}

class Seller extends Thread{
	static int num = 40;//为了实现num的共享
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"  i:"+"  "+ (--num));
		}
	}
}

创建线程的第二种方式

public class Demo2 {
	public static void main(String[] args) {
		
		//创建任务对象
		Ticket ticket = new Ticket();
		//创建线程并关联同一个任务
		//如果我们创建了自己独立的任务类,线程会优先调用我们手动传入线程的任务类对象的run方法,不会再去调用Thread类的run方法
		Thread thread1 = new Thread(ticket);
		Thread thread2 = new Thread(ticket);
		Thread thread3 = new Thread(ticket);
		Thread thread4 = new Thread(ticket);

		thread1.start();
		thread2.start();
		thread3.start();
		thread4.start();

	}
}

//创建任务类
class Ticket implements Runnable {
	//因为Ticket对象被四个线程共享,所以num作为属性也被共享了
	int num = 40;
	public void run() {
		for(int i = 0; i < 10; i++) {
			System.out.println(Thread.currentThread().getName()+"  i:"+"  "+ (--num));
		}
	}
}

线程安全问题:

  • 分析:4个线程共用了一个数据,出现了-1,-2,-3等错误的数据

  • 具体分析:

  1. 共用了一个数据
  2. 共享语句有多条,一个线程使用cpu,没有使用完,cpu被抢走,当再次抢到cpu的时候,直接执行后面的语句,造成了错误的发生.
  • 解决:

    • 在代码中使用同步代码块儿(同步锁)
    • 解释:在某一段任务中,同一时间只允许一个线程执行任务,其他的线程即使抢到了cpu,也无法进入当前的任务区间,只有当当前的线程将任务执行完后,其他的线程才能有资格进入
  • 同步代码块儿的构成:

synchronized((对象)){

	  同步的代码
    }
  • 对作为锁的对象的要求:

    1. 必须是对象
    2. 必须保证被多个线程共享
  • 可以充当锁的:

    1. 一个普通的对象
    2. 当前对象的引用–this
    3. 类的字节码文件
  • 同步代码块儿的特点:

    1. 可以保证线程的安全
    2. 由于每次都要进行判断处理,所以降低了执行效率
  • 总结:什么时候使用同步代码块儿

    1. 多个线程共享一个数据
    2. 至少有两个线程
//第二种:线程与任务分离
public class Demo3 {
	public static void main(String[] args) {
		//创建任务对象
		Ticket1 ticket = new Ticket1();
		//创建线程对象并关联同一个任务
		//如果我们创建了自己独立的任务类,线程会优先调用我们手动传入线程的任务类对象的run方法,不会再去调用Thread默认的run方法
		Thread seller1 = new Thread(ticket);
		Thread seller2 = new Thread(ticket);
		Thread seller3 = new Thread(ticket);
		Thread seller4 = new Thread(ticket);
		
		//开启线程
		seller1.start();
		seller2.start();
		seller3.start();
		seller4.start();
	}
}

//创建任务类
class Ticket1 implements Runnable{
	//因为Ticket对象被四个线程共享,所以num作为属性也被共享了
 	int num = 20;
 	boolean flag = false;
 	//让object充当锁
 	//作为锁要满足两个条件:1.必须是对象   2.必须供所有的线程共享.
 	//可以作为锁的有:1.任意一个实例对象   2.this   3.字节码文件对象
 	Object object = new Object();
	public void run() {
		while (!flag) {
			synchronized (object) {//同步代码块儿--让线程之间互斥
				//制造一个延迟,相当于让当前执行run的线程休息一会儿(临时让出cpu)
				try {
					Thread.sleep(100);//100是时间,单位是毫秒
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
				if (num >0) {
					System.out.println(Thread.currentThread().getName()+"  "+ --num);
				}else {
					flag = true;
				}
			}
		}

	}
}

实例:两个人向同一个账户里面存钱

  • 一人存三次,每次存100

  • 注意:

  1. 当在一个类中同时存在多个synchronized修饰的代码块儿或函数时,要想安全,就必须让他们后面的对象一致。因为只有同一把锁才能安全。
  • 同步函数的锁:this
  1. 静态同步函数在进内存的时候不会创建对象,但是存在其所属类的字节码文件对象,属于class类型的对象,所以静态同步函数的锁是其所属类的字节码文件对象

理解synchronized关键字

  1. synchronized关键字的作用域有二种:

    1. 是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象
      的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
    2. 是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问
      这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
  2. 除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是:synchronized(this){/区块/},它的作用域是当前对象;

  3. synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){}
    在继承类中并不自动是synchronizedf(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法.

public class Demo4 {
	public static void main(String[] args) {
		//1.创建任务类对象
		CunQian cunQian = new CunQian();
		//2.创建线程并绑定任务
		Thread thread1 = new Thread(cunQian);
		Thread thread2 = new Thread(cunQian);
		//3.开启线程
		thread1.start();
		thread2.start();
	}
}

class Bank{
	int sum;//放的是当前账户的钱
	//使用同步代码块儿
//	public void addMoney(int money){
//		synchronized (this) {
//			sum+=money;
//			System.out.println(sum);
//	    }
//	}
	//使用同步函数
	//非静态的同步函数
	//相当于默认在synchronized后面跟着this充当锁
//	public synchronized void addMoney(int money){
//		sum+=money;
//		System.out.println(sum);
//	}
	
	//静态的同步函数
	//相当于默认在synchronized后面跟着当前的类的字节码文件充当锁----Bank.class
	public synchronized static void addMoney(int money){

	}
}

//创建任务类
class CunQian implements Runnable{
	Bank bank = new Bank();
	public void run() {
		for(int i=0;i<3;i++){
			bank.addMoney(100);
		}
	}
}

单例模式与线程

public class Demo5 {

}

//懒汉式
class SingleInstance1{
	private  static SingleInstance1 singleInstance = null;
	private SingleInstance1(){
		
	}
	//因为同步代码块儿的效率高于同步函数,所以尽量使用同步代码块儿
	public static SingleInstance1 getInstance() {
		if (singleInstance == null) {//目的:尽量减少线程安全代码的判断次数,提高效率
			
			synchronized (SingleInstance1.class) {
				if (singleInstance == null) {
					singleInstance = new SingleInstance1();
				}
			}
		}
		return singleInstance;
	}
}

//饿汉式
class SingleInstance{
	private final static SingleInstance singleInstance = new SingleInstance();
	private SingleInstance(){
		
	}
	public static SingleInstance getInstance() {
		return singleInstance;
	}
}

class Test implements Runnable{
	@Override
	public void run() {
		SingleInstance1 singleInstance1 = SingleInstance1.getInstance();
		
	}
}

Thread对象作为参数


public class Demo6 {
	public static void main(String[] args) {
		Thread thread1 = new Thread();
		//1.这里是可以的,这里将thread1当做了任务类对象,执行的时候调用的是thread1内部的run方法
		Thread thread2 = new Thread(thread1);
		thread2.start();
		
		//2.创建Thread类的匿名子类对象充当线程类
		new Thread(){
			public void run() {
				System.out.println("haha");
			};
		}.start();
	}
}


线程的停止

如何让他的任务结束

  1. 通过一个标识结束线程

  2. 通过调用stop方法结束线程----有固有的安全问题,已经过时,不建议再使用

  3. 调用interrupt()方法结束线程

    原理:线程可以调用wait()方法,让当前的线程处于钝化的状态(会立刻释放cpu,并且处于无法抢cpu的状态,但是当前的线程并没有死亡)
    注意点:wait方法必须在同步状态下使用.
    调用interrupt方法就是将处于wait状态的线程停止.

通过一个标识结束线程


public class Demo7 {
	public static void main(String[] args) {
		Test1 test1  = new Test1();
		Thread thread = new Thread(test1);
		thread.start();
		
		//让主线程睡一会儿
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		int i=0;
		while (true) {
			if (++i == 10) {
				test1.flag = false;//当主线程执行到某个阶段的时候,让flag值变成false,控制while循环的结束,从而控制子线程的结束
			
				break;//目的:为了让主线程结束
			}
		}
	}
}

class Test1 implements Runnable{
	boolean flag = true;
	public void run() {
		while (flag) {
			System.out.println(Thread.currentThread().getName()+"   "+"我们很happy");
		}
	}
}

调用interrupt()方法结束线程

public class Demo7 {
	public static void main(String[] args) {
		Test1 test1  = new Test1();
		Thread thread = new Thread(test1);
		thread.start();
		
		//让主线程睡一会儿
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		int i=0;
		while (true) {
			if (++i == 10) {
			    thread.interrupt();//当调用这个方法的时候,会触发wait方法的interruptedException异常,我们就可以在捕获异常的	
			    					//时候将flag值变成false,从而结束循环,结束任务,结束线程
				break;//目的:为了让主线程结束
			}
		}
	}
}

class Test1 implements Runnable{
	boolean flag = true;
	public synchronized void run() {
		while (flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				flag = false;
				System.out.println("InterruptedException");
			}//wait方法由锁对象来调用
			System.out.println(Thread.currentThread().getName()+"   "+"我们很happy");
		}
	}
}



猜你喜欢

转载自blog.csdn.net/forever428/article/details/83240638