JAVA从菜鸟到进阶--面向对象基础(六)——多线程基础一


进程:正在进行的程序(直译),只是分配该程序的内存空间。
线程:是进程中一个负责程序执行的执行路径,一个程序有多个执行路径(多线程),一个进程中至少要有一个线程。

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

三 多线程的好处和弊端
好处:解决了多部分代码同时运行的问题
弊端:线程的增多导致效率降低(Cpu进行快速的切换运行不同的线程,线程越多,切换的频率降低导致效率降低)

四.
JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。
①:执行main函数的线程 该线程的任务代码都定义在main函数中。
②:负责垃圾回收的线程

五:如何创建一个线程?
第一种方法:
①定义一个类继承Thread类
②重写run方法
③新建这个类的对象调用start()方法线程开启
第二种方法:
①定义一个类实现Runnable接口
②重写run方法
③创建实现Runnable接口的对象
④在主函数中创建一个Thread类的对象:把实现Runnable接口的对象传递给Thread类的构造函数中(线程的任务封装在Runable接口的子类的run方法中,要在线程对象创建时就必须明确要进行任务。)
⑤调用start()方法,开启进程。
主要思想:把任务进行封装成Runnable的子类对象。

六.为什么创建线程?
创建线程目的是为了开启一条执行路径去运行指定的代码和其他代码实现同时运行,而运行的指定代码就是这个执行路径的任务。
(JVM创建的主函数的线程都定义在主函数的中,自定义的线程任务在于被重写或者实现的run方法中)

七调用run()和start()方法的区别
注:在继承Thread类的时候直接调用run方法线程并没有开始,只是调用了一般方法。而在调用了start()方法后,虚拟机自动调用run()方法并开启进程。

八,线程的状态
Cpu的执行资格:可以被cpu处理,在处理队列中排队。
Cpu的执行权:正在被Cpu处理的线程
JAVA从菜鸟到进阶--面向对象基础(六)——多线程基础一
九.Thread类的基本源码
class Thread
{
private Runnable r;
Thread()
{
}
Thread(Runnable r)
{
this,r=r;
}
public void run()
{
if(r!=null)
{r.run();}
}
public void start()
{
run();
}
Runnable出现仅仅是将线程的任务进行对象的封装。
Thread类也实现Runna接口。
实现Runnable接口的好处:
1.将线程的任务从线程的子类分离出来,进行单独封装,按照面向对象思想将任务封装成对象
2.避免了java单继承的局限性

九.线程安全问题产生的原因?
①多个线程在操作共享的数据
②操作共享的数据代码有多条
当一个线程在执行操作共享数据的多条代码中,其他线程参与了运算,就会导致线程安全问题的产生。
Example1:

package Thread;
class Ticket implements Runnable
{   
	private int tickets=10;	
	public void run()
	{
		 while(true)
		 {   
			 if(tickets>0)
			 System.out.println(Thread.currentThread().getName()+"..tickets余票还有。。。"+tickets--);
		 }
	}
}

public class Thread3 {
 
	public static void main(String[]args)
	{
		Ticket Ti=new Ticket();
		Thread t1=new Thread(Ti);
		Thread t2=new Thread(Ti);
		Thread t3=new Thread(Ti);
		t1.start();
		t2.start();
		t3.start();
	}
}
Thread-1..tickets余票还有。。。10
Thread-1..tickets余票还有。。。7
Thread-0..tickets余票还有。。。8
Thread-2..tickets余票还有。。。9
Thread-2..tickets余票还有。。。4
Thread-2..tickets余票还有。。。3
Thread-2..tickets余票还有。。。2
Thread-2..tickets余票还有。。。1
Thread-0..tickets余票还有。。。5
Thread-1..tickets余票还有。。。6


可以看出,有三个线程正在进行访问共享的数据,有多条代码会进行延迟,其他的线程也会进行。
解决思路:就是将多条操作共享数据的代码封装起来,当有线程在执行这些代码时,其他线程是不可以参与运算的。必须把当前代码执行结束后,其他线程才可以执行(用synchronized解决)
(1)同步代码块
同步代码块的格式:
synchronized(锁对象)
{ 需要被同步的代码 }
可以把锁对象理解成“火车上的卫生间”

package Thread;
class Ticket implements Runnable
{   Object obj=new Object();
	private int tickets=10;	
	public void run()
	{     
		 while(true)
		 {   
			 synchronized(obj)
			 {
			 if(tickets>0)
				 try {
					 Thread.sleep(100);
					 System.out.println(Thread.currentThread().getName()+"..tickets余票还有。。。"+tickets--);
			      	 }
			     catch(Exception e)
			   {}
		     }
		 }
	}
}

public class Thread3 {
 
	public static void main(String[]args)
	{
		Ticket Ti=new Ticket();
		Thread t1=new Thread(Ti);
		Thread t2=new Thread(Ti);
		Thread t3=new Thread(Ti);
		t1.start();
		t2.start();
		t3.start();
	}
}

Thread-0..tickets余票还有。。。10
Thread-0..tickets余票还有。。。9
Thread-2..tickets余票还有。。。8
Thread-2..tickets余票还有。。。7
Thread-2..tickets余票还有。。。6
Thread-1..tickets余票还有。。。5
Thread-2..tickets余票还有。。。4
Thread-2..tickets余票还有。。。3
Thread-0..tickets余票还有。。。2
Thread-0..tickets余票还有。。。1

注意 锁对象必须是类的成员变量,若锁对象在方法区中每一个线程调用会新建一个锁对象,若每个线程的锁不同,就不能进行同步了。

(二)同步方法

package Thread;
class Ticket implements Runnable
{   Object obj=new Object();
	private int tickets=100;	
	private int flag=1;
	public void run()
	{     if(flag==1)
	    {
		 while(true)
		 {   
			 synchronized(obj)
			 {
				 if(tickets>0)
					 try {
						 Thread.sleep(500);
						 System.out.println("同步代码块"+Thread.currentThread().getName()+"..tickets余票还有。。。"+tickets--);
				      	 }
				     catch(Exception e) {}
			     
			     }
				 if (tickets==0) break;
		     
		 }
      	}
	     if(flag==0)
	     {
	    	 method();
	     }
	}
	public synchronized void method()
	{
		while(true)
		 {   
			 
				 if(tickets>0)
				 { try {
						 Thread.sleep(500);
						 System.out.println("同步方法"+Thread.currentThread().getName()+"..tickets余票还有。。。"+tickets--);
				      	 }
				     catch(Exception e) {}
				 }
			     
				 if (tickets==0) break;
		     
		 }
	}
	public void  SetFlag(int flag)
	{
		this.flag=flag;
	}
}


public class Thread3 {
 
	public static void main(String[]args)
	{
		Ticket Ti=new Ticket();
		Thread t1=new Thread(Ti);
		Thread t2=new Thread(Ti);
		Thread t3=new Thread(Ti);
		t1.start();
	
		t2.start();
		Ti.SetFlag(0);
		t3.start();
		
	}
}

注意:在此程序中同步代码块的锁对象是obj,所有的线程都拥有共同的锁对象。而同步方法的锁对象是this调用对象。当不同对象调用同步方法时也产生不同的同步锁,不能进行同步。建议使用同步代码块

十.同步的好处;解决了线程安全问题
同步的弊端;相对降低了效率,因为同步的进程都会判断同步锁
同步的前提;①同步中必须有多线程使用同一个锁
②同步锁必须在函数外声明