多线程的创建,状态,安全问题(同步代码块)

Java学习笔记之----------多线程

    • 进程
    • 线程
    • 多线程存在的意义
    • 创建多线程
    • 线程的几种状态
    • 创建多线程(推荐)
    • 多线程的安全问题(同步代码块)

进程

进程:应用程序在内存中分配的空间(正在运行中的程序)

线程

线程
进程中负责程序执行的执行单元,也称为执行路径
一个进程至少有一个线程负责该进程的执行,称主线程
如果一个进程启用了多个线程,就称该程序为多线程程序

多线程存在的意义

多线程有时并不能提高效率,所以多线程存在的意义不是提高效率,多线程其实是合理使用资源
解决多部分代码同时执行的需求,合理使用CPU资源
多线程的运行是根据CPU的切换完成的,如何切换是由CPU决定的,所以多线程运行具有随机性
JVM中的线程至少有两个
一个是负责自定义代码运行的
一个是负责垃圾回收的

class Demo{
	//定义垃圾回收方法
	public void finalize(){
		System.out.println("clear is ok");
	}
}
public class GcTest{
	public static void main(String args[]){
	new Demo();
	new Demo();
	new Demo();
	System.gc();//启动垃圾回收器
	System.out.println("TEST OVER!");	
	}
}

多线程的创建,状态,安全问题(同步代码块)_第1张图片
可以看到,垃圾回收器启动了之后不一定执行,执行与否不确定,由CPU说了算
:因为我的JDK版本较高,所以在编译时出现了警告,显示finalize方法已经过时
多线程的创建,状态,安全问题(同步代码块)_第2张图片
多线程的创建,状态,安全问题(同步代码块)_第3张图片
每个线程都有运行的代码内容,称为线程的任务。而之所以创建线程就是为了去运行指定的任务代码
线程的任务都封装在特定的区域中。例如:主线程运行的任务都定义在main方法中

创建多线程

单线程

//单线程示例
class People{
	private String name;
	//初始化
	People(String name){
		this.name = name;
	}
	public void show(){
		for(int x = 1;x<11;x++){
			System.out.println(name+"-----"+x);
		}
	}
}
public class ThreadDemo{
	public static void main(String args[]){
		People p1 = new People("LILY");
		People p2 = new People("LUCY");
		p1.show();
		p2.show();
	}
}

多线程的创建,状态,安全问题(同步代码块)_第4张图片

创建多线程
1.继承Thread类
2.覆盖run方法
3.创建子类对象就是创建线程对象
4.调用Thread类中的start方法就可以执行线程,并会调用run方法
多线程的创建,状态,安全问题(同步代码块)_第5张图片

//继承Thread类
class People extends Thread{
	private String name;
	//初始化
	People(String name){
		this.name = name;
	}
	public void show(){
		for(int x = 1;x<11;x++){
			System.out.println(name+"-----"+x);
		}
	}
	//覆盖run方法
	public void run(){
		show();
	}
}
public class ThreadDemo{
	public static void main(String args[]){
		People p1 = new People("LILY");
		People p2 = new People("LUCY");
		p1.start();//调用start方法,start会调用run方法,run方法再调用show方法
		p2.start();
	}
}

多线程的创建,状态,安全问题(同步代码块)_第6张图片
可以看到,代码两次运行后的结果有区别,说明两个线程的运行是由CPU确定的,运行具有不确定性。

获取当前线程对象名
使用Thread类中的getName()

//单线程示例
class People extends Thread{
	private String name;
	//初始化
	People(String name){
		this.name = name;
	}
	public void show(){
		for(int x = 1;x<11;x++){
			System.out.println(getName()+"-----"+name+"-----"+x);
		}
	}
}
public class ThreadDemo1{
	public static void main(String args[]){
		People p1 = new People("LILY");
		People p2 = new People("LUCY");
		p1.show();
		p2.show();
	}
}

多线程的创建,状态,安全问题(同步代码块)_第7张图片
Thread-0,Thread-1仅仅代表了当前对象的名字,不代表线程被开启。

获取当前线程路径,(开启线程)
使用Thread.currentThread()方法
:需要调用start方法启动线程

class People extends Thread{
	private String name;
	//初始化
	People(String name){
		this.name = name;
	}
	public void show(){
		for(int x = 1;x<11;x++){
		//获取自定义的两个线程当前路径
			System.out.println(Thread.currentThread().getName()+"-------"+name+"-----"+x);
		}
	}
	//覆盖run方法
	public void run(){
		show();
	}
}
public class ThreadDemo{
	public static void main(String args[]){
		People p1 = new People("LILY");
		People p2 = new People("LUCY");
		p1.start();//调用start方法,start调用run方法,run方法再调用show方法
		p2.start();
		//获取主线程当前路径
		for(int i = 1;i<11;i++){
			System.out.println(Thread.currentThread().getName()+"-----"+i);
		}

	}
}

多线程的创建,状态,安全问题(同步代码块)_第8张图片
当启动了p1.start()后是继续执行p2.start()还是执行run方法是不确定的,取决于CPU所分配的时间片,如果当前CPU在主线程上,则会继续启动p2。
可以看到,主线程和自定义的两个线程执行的顺序是不一致的,体现了CPU的随机性。

问题:调用start和调用run有什么区别
:调用run多线程是没有被开启的,此时只有主线程在执行。而调用start是启动run里面自定义的线程对象
线程路径和线程对象名的区别
多线程的创建,状态,安全问题(同步代码块)_第9张图片
线程结束的条件是所属线程里没有其他方法

线程的几种状态

多线程的创建,状态,安全问题(同步代码块)_第10张图片
:sleep需要指定睡眠时间,单位为毫秒

创建多线程(推荐)

首先,我们先写一个售票的小例子,假设总共有火车票100张,开启三个窗口去售卖这些车票

class TicketDemo  extends Thread{
	private int tickets = 100;
	public void run(){
		while(true){		
				if(tickets>0){
					System.out.println(Thread.currentThread().getName()+"-----"+tickets--);
				}
		}		
	}
}
public class SaleTicket{
	public static void main(String args[]){
	TicketDemo t1 = new TicketDemo();
	TicketDemo t2 = new TicketDemo();
	TicketDemo t3 = new TicketDemo();
	//开启三个线程
	t1.start();
	t2.start();
	t3.start();
	
		
	}
}

多线程的创建,状态,安全问题(同步代码块)_第11张图片
但是运行结果似乎有一点点的不对劲,我们总共100张票,但是三个线程都输出了相同的数,就是说,一张票在不同的窗口被售卖了三次,这正常吗?明显不正常!

分析代码可以得知,我们在堆中new了三个TicketDemo,每个TicketDemo中都有100张票,但是我们在本例子中只需要一个TicketDemo,那么,如何在实例化一个对象的前提下,开启三个或者多个线程呢

此处需要注意:如果只实例化一个对象,那么当前线程只有2个,即主线程和自定义开启的线程,但要注意的是,多次启用一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动,如果线程已启动,会抛出IllegalThreadStateException
多线程的创建,状态,安全问题(同步代码块)_第12张图片
是否可以使用Thread类对象来创建四个线程呢?

public class SaleTicket{
	public static void main(String args[]){
	Thread t1 = new Thread();
	Thread t2 = new Thread();
	Thread t3 = new Thread();
	//开启三个线程
	t1.start();
	t2.start();
	t3.start();		
	}
}

多线程的创建,状态,安全问题(同步代码块)_第13张图片
运行代码会看到没有输出结果,这是为什么呢?这里需要注意的是,我们使用Thread创建线程,那么start调用的run方法会是父类自身的run方法,而该方法是一个空方法,所以没有任何输出结果。

创建多线程方式二-----实现Runnable接口
1.定义一个类实现Runnable。
2.覆盖Runnable接口中的run方法。
3.通过Thread类创建线程对象,并将实现了Runnable接口的对象作为Thread类的构造函数的参数进行传递。
4.调用start方法,启动线程。

//实现Runnable接口
class TicketDemo implements Runnable{ //extends Thread{
	private int tickets = 100;
	public void run(){
		while(true){		
				if(tickets>0){
					System.out.println(Thread.currentThread().getName()+"-----"+tickets--);
				}
		}		
	}
}
public class SaleTicket{
	public static void main(String args[]){
	//TicketDemo t1 = new TicketDemo();
	//TicketDemo t2 = new TicketDemo();
	//TicketDemo t3 = new TicketDemo();
	TicketDemo t = new TicketDemo();
	Thread t1 = new Thread(t);
	Thread t2 = new Thread(t);
	Thread t3 = new Thread(t);
	//开启三个线程
	t1.start();
	t2.start();
	t3.start();		
	}
}

多线程的创建,状态,安全问题(同步代码块)_第14张图片
实现Runnable接口的好处
1.避免了继承Thread类的单继承的局限性
2.Runnable接口更符合面向对象,将线程单独进行对象的封装
3.Runnable接口降低了线程对象和线程任务的耦合性

多线程的安全问题(同步代码块)

CPU时间片的分配可能会导致程序运行时出错
测试代码

//实现Runnable接口
class TicketDemo implements Runnable{ //extends Thread{
	private int tickets = 100;
	public void run(){
		while(true){		
				if(tickets>0){
					//让线程在该处暂停
					try{Thread.sleep(10);}catch(InterruptedException e){}
					System.out.println(Thread.currentThread().getName()+"-----"+tickets--);
				}
		}		
	}
}

多线程的创建,状态,安全问题(同步代码块)_第15张图片

可以看到,程序执行到最后时,出错了。出错的原因是当某一个线程在执行了if判断之后,CPU将时间片分配给了另一个进程,当时间片又分配给当前进程时,当前进程无需做if判断而直接输出,导致了输出结果异常。
解决方式-----同步代码块

//实现Runnable接口
class TicketDemo implements Runnable{ //extends Thread{
	private int tickets = 100;
	//使用一个现有的类创建对象
	Object obj = new Object();
	public void run(){
		while(true){
			/*使用同步代码块,是一种内部机制,当有线程使用时,其他线程没有该对象的使用权
			synchronized(对象){
					需要被同步的代码
			}
			同步机制其实就是锁的机制*/
			synchronized(obj){	
					if(tickets>0){
						//让线程在该处暂停
						try{Thread.sleep(10);}catch(InterruptedException e){}
						System.out.println(Thread.currentThread().getName()+"-----"+tickets--);
					}
			}
		}		
	}
}
public class SaleTicket{
	public static void main(String args[]){
	//TicketDemo t1 = new TicketDemo();
	//TicketDemo t2 = new TicketDemo();
	//TicketDemo t3 = new TicketDemo();
	TicketDemo t = new TicketDemo();
	Thread t1 = new Thread(t);
	Thread t2 = new Thread(t);
	Thread t3 = new Thread(t);
	//开启三个线程
	t1.start();
	t2.start();
	t3.start();		
	}
}

多线程的创建,状态,安全问题(同步代码块)_第16张图片
同步在目前状态下保证了一次只有一个线程在执行,其他进程无法进入。
好处:解决了多线程的安全问题
弊端:降低了效率,但是在可接受的范围,一种以效率换安全的方式
注意:synchronized(对象)中需要用公有的锁,例子中如果换成synchronized(new Obiect()),那么每个进程使用的都是自己的锁,无法做到同步。
同步锁使用的前提是首先判断程序是不是多线程,单线程无需用到同步锁,且多个线程在同步中必须使用同一个锁。

文章为学习笔记,如有不足之处还请指正

你可能感兴趣的