Java 多线程知识点总结

一、Java线程

1、Java线程实现方式:Java中实现多线程主要有两种方式,通过extends Thread类的方式来实现;另一种通过implements Runnable接口来实现。

2、线程的生命周期:Java中,一个线程的生命周期有4种状态,初始化、可执行、阻塞、死亡。

  • 初始化状态:通过new语句创建一个线程对象。
  • 可执行状态:调用start()方法,线程分配到了CPU时间,或者等待分配CPU时间。
  • 阻塞状态:通过调用sleep()方法或wait()方法,线程进入挂起状态,线程不会分配到CPU时间。
  • 死亡状态:run()方法中的逻辑正常运行结束进入死亡状态;调用stop()方法或destroy()方法时也会非正常地终止当前线程。

 二、创建Java线程

Java中创建一个子线程用到Thread类和Runnabe接口。Thread是线程类,创建一个Thread对象就是创建一个新线程,线程执行的代码程序是在实现Runnable接口对象的run()方法中编写的,即线程执行对象。

1、继承Thread线程类

Thread类也实现了Runnable接口,所以Thread类也可以作为线程执行对象,需要继承Thread类,覆盖run()方法。通过start()方法来启动该线程,它会触发run()方法。

创建继承thread类

public class SimpleThread extends Thread{
	int index;//线程编号
	//通过构造函数指定该线程编号
	public SimpleThread(int index) {
	   this.index=index;
	   System.out.println("创建线程"+index);
	}
	//定义线程的运行代码
	public void run()
	{
		for(int i=0;i<=3;i++)
		{
			System.out.println("线程"+index+" : "+i);
		}
		System.out.println("线程"+index+" : "+index);
	}

	
}

测试该线程类

public static void main(String[] args) {
			
		for(int j=0;j<3;j++)
		{
			Thread t=new SimpleThread(j+1);
			t.start();
		}
		
	}

 2、实现Runnable接口

Java语言中一个类不能继承多个类,如果一个类已经继承另一个类,就无法再继承thread类,这时可以实现Runnable的方式实现多线程。

public class ThreadPriority implements Runnable {
	int numble;//线程编号
	public ThreadPriority(int numble) {
		   this.numble=numble;
		   System.out.println("创建线程"+numble);
		}
	@Override
	public void run() {	
		// TODO Auto-generated method stub
		for(int i=0;i<=3;i++)
		{
			System.out.println("线程"+numble+" : "+i);
		}
		System.out.println("线程"+numble+" : "+numble);
	}

}

调用线程

	public static void main(String[] args) {
			
		for(int j=0;j<3;j++)
		{
			Thread t=new Thread(new ThreadPriority(j+1));
			t.start();
		}
		
	}

 如果线程体使用的地方不多,可以不用单独定义一个类,使用匿名内部类或Lambda表达式直接实现Runnable接口,不需要定义一个线程类文件,使得代码变得简洁。

(1)使用匿名内部类

public static void main(String[] args) {
			Thread t1=new Thread(new Runnable() {//此处使用Thread(Runnable target)构造方法
				//编写执行线程代码
				public void run() {	
					// TODO Auto-generated method stub
					for(int i=0;i<=3;i++)
					{
						System.out.println(i);
					}
					System.out.println("执行完成"+Thread.currentThread().getName());
				}
			});
			
	      t1.start();
	}

 

(2)使用Lambda表达式

public static void main(String[] args) {
			Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
				//编写执行线程代码
					// TODO Auto-generated method stub
					for(int i=0;i<=3;i++)
					{
						System.out.println(i);
					}
					System.out.println("执行完成"+Thread.currentThread().getName());	
			});
			
	      t1.start();
	}

 3、设置线程优先级

Java语言把线程分成了10个不同的优先级别,用1-10表示,数字越小级别越高。Thread类的setPriority(int newPriority)方法可以设置线程优先级,通过getPriority()方法获得线程优先级。

public static void main(String[] args) {
			

			Thread t1=new Thread(new ThreadPriority(1));//线程1
			t1.setPriority(3);//设置线程优先级别为3
			t1.start();
			Thread t2=new Thread(new ThreadPriority(2));//线程2
			t2.setPriority(2);//设置线程优先级别为2
			t2.start();
	
	}

 4、线程让步

线程类Thread中的静态方法yield(),可以使当前线程给其他线程让步。

public static void main(String[] args) {
			

			Thread t1=new Thread(new ThreadPriority(1));//线程1
			t1.setPriority(3);//设置线程优先级别为3
			t1.start();
			Thread t2=new Thread(new ThreadPriority(2));//线程2
			t2.setPriority(2);//设置线程优先级别为2
			t2.start();
			Thread.yield();//当前线程让步		
	
	}

 5、线程休眠

Thread类的sleep()静态方法,可以让当前线程阻塞指定时间。

注意:调用sleep()方法时,必须要包含在try...catch代码中,否则会有语法错误。

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

三、多线程同步

       多线程对临界资源的访问有时会导致数据的不一致性。即启动多个线程时,它们可能并发执行某个方法或某块代码,从而可能会发生不同线程同时修改同块存储空间内容的情况。

       Java提供了互斥机制,在任意时刻只能由一个线程访问,即使该线程出现阻塞,该对象的被锁定状态也不会被解除,其他线程仍不能访问该对象。可以通过synchronized关键字实现同步,一种是使用synchronized关键字修饰方法,对方法进行同步;

以下两种写法一致:

 

另一种是使用synchronized关键字放在对象前面限制一段代码的执行。

  • 对于实例方法,要给调用该方法的对象加锁。
  • 对于静态方法,要给这个类加锁。

应用举例:

编写售票系统,ticketCount为当前票数,getTicketCount()方法获得当前票数,sellTicket()方法销票。

不加同步机制的运行结果如下所示:

1、使用synchronized修饰方法实现线程同步

对getTicketCount()方法和sellTicket()方法使用synchronized修饰

public class TicketDB {
	private int ticketCount=5;//机票数量
	
	//获得当前机票数量
	public synchronized int getTicketCount() {
		return ticketCount;
	}
	
	//销售机票
	public synchronized void sellTicket() {
		try {
			Thread.sleep(1000);//阻塞当前线程,模拟用户付款
		}
		catch(InterruptedException e) {
			
		}
		System.out.printf("第%d号票,已售出\n",ticketCount);
		ticketCount--;
	}
}

测试:

	public static void main(String[] args) {
		TicketDB db=new TicketDB();
		//模拟1号售票网点
			Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
				//编写执行线程代码
					while(true) {
						int currTicketCount=db.getTicketCount();//获取当前票数
						//查询是否有票
						if(currTicketCount>0) {
							db.sellTicket();
						}else {
							break;
						}
					}
			});
			
	      t1.start();
	      
	    //模拟2号售票网点
			Thread t2=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
				//编写执行线程代码
					while(true) {
						int currTicketCount=db.getTicketCount();//获取当前票数
						//查询是否有票
						if(currTicketCount>0) {
							db.sellTicket();
						}else {
							break;
						}
					}
			});
			
	      t2.start();
	}

运行结果:

 2、使用synchronized语句

getTicketCount()方法和sellTicket()方法为普通方法,不用synchronized修饰

public static void main(String[] args) {
		TicketDB db=new TicketDB();
		//模拟1号售票网点
			Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
				//编写执行线程代码
					while(true) {
						synchronized (db) {
							int currTicketCount=db.getTicketCount();//获取当前票数
							//查询是否有票
							if(currTicketCount>0) {
								db.sellTicket();
							}else {
								break;
							}						
						}				
					}
			});
			
	      t1.start();
	      
	    //模拟2号售票网点
			Thread t2=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
				//编写执行线程代码
					while(true) {
						synchronized (db) {
						int currTicketCount=db.getTicketCount();//获取当前票数
						//查询是否有票
						if(currTicketCount>0) {
							db.sellTicket();
						}else {
							break;
						}
				       }
					}
			});
			
	      t2.start();
	}

 四、线程通信

1、wait和notify方法

(1)wait和notify需要放置在synchronized的作用域中。wait属于object类,一旦一个线程执行wait方法后,该线程就会释放synchronized所关联的锁(对象锁),进入阻塞状态,所以该线程一般无法再次主动回到可执行状态,一定要通过其他线程的notify方法去唤醒它。

notifyAll会让所有因wait方法进入阻塞状态的线程退出阻塞状态,这些线程会竞争对象锁,如果其中一个线程获得了对象锁,则会继续执行。在它释放锁后,其他已被唤醒的线程继续竞争。

(2)一旦一个线程执行了notify方法,则会通知那些可能因调用wait方法而等待对象锁的其他线程。如果有多个线程等待,则任意挑选一个线程,通知该线程得到对象锁从而继续执行下去。

 应用举例:使用wait和notify方法实现生产者和消费者模型

public class Stack {
	//堆栈指针初始值0
	private int pointer=0;
	//定义堆栈字符空间
	private char [] data=new char[5];
	
	//压栈
	public synchronized void push(char c) {
		//堆栈已满,不能压栈
		while(pointer==data.length) {
			try {
				this.wait();//等待,直到有数据出栈
			}catch(InterruptedException e) {
				
			}
		}
		//通知其他线程把数据出栈
		this.notify();
		//数据压栈
		data[pointer]=c;
		pointer++;//指针向上移动
	}
	
	//出栈
	public synchronized char pop() {
		//堆栈无数据,不能出栈
		while(pointer==0) {
			try {
				this.wait();//等待其他线程把数据压栈
			}catch(InterruptedException e) {
				
			}
		}
		//通知其他线程压栈
		this.notify();
		pointer--;//指针向下移动
		return data[pointer];
	}
}

测试:

public static void main(String[] args) {
		Stack stack =new Stack();
		//生成者线程
			Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
				//编写执行线程代码
				char c;
				for(int i=0;i<10;i++) {
					c=(char)(Math.random()*26+'A');//随机产生10个字符
					stack.push(c);//字符压栈
					System.out.println("生成: "+c);
					try {
						Thread.sleep(1000);
					}catch(InterruptedException e) {
						
					}
				}
			});
			
	    
	      
	    //消费者线程
			Thread t2=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
				//编写执行线程代码
					char c;
					for(int i=0;i<10;i++) {
						c=stack.pop();//从堆栈取字符
						System.out.println("消费: "+c);
						try {
							Thread.sleep(1000);
						}catch(InterruptedException e) {
							
						}
					}
			});
		  t1.start();	
	      t2.start();
	}

发布了141 篇原创文章 · 获赞 194 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/kenjianqi1647/article/details/104746854