本系列博客汇总在这里:Java系列_汇总


一、简介

1、计算机中的进程是什么?

进程:计算机中特定功能的程序在数据集上的一次运行。
Java系列(51)——线程_java

2、计算机中的线程又是什么?

线程:线程是进程的一个单元。
Java系列(51)——线程_ide_02
Java系列(51)——线程_ide_03
Java系列(51)——线程_线程_04

3、什么是多线程?

多线程:一个进程中有多个线程在同时运行,如迅雷下载,迅雷软件的一次运行就是一个进程,那么在迅雷中可以同时下载多个电影,这就是多线程(每一个下载都是一个线程)。

4、Java虚拟机是多线程的吗?

jvm 是多线程的,在我们运行 jvm 的时候后台会运行垃圾回收的线程,来清理没有被引用的对象。

二、线程的实现

Java系列(51)——线程_线程_05
Java系列(51)——线程_多线程_06

1、线程实现的第一种方式

  1. 将类声明为 Thread 的子类,该子类应重写 Thread 类的 run 方法,接下来可以分配并启动该子类的实例。线程启动的时候使用线程的 start 方法而不是 run!

  2. 示例

    public class MyThread extends Thread
    {
    
    	/**
    	 * 必须重写线程体
    	 */
    	@Override
    	public void run()
    	{
    		for (int i = 0; i < 5; i++)
    		{
    			System.out.println(this.getName() + "线程在执行" + i);
    		}
    
    	}
    }
    
    public class TestThread
    {
    	public static void main(String[] args)
    	{
    		// 创建一个线程实例对象
    		MyThread t = new MyThread();
    		// 创建第二个线程的实例对象
    		MyThread t1 = new MyThread();
    		/**
    		 * 
    		 * run不能启动线程,这只是普通方法的调用 t.run(); t1.run();
    		 * 
    		 * void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法,并不是启动线程。
    		 */
    		// 设置名称,也可以在创建线程的时候指定
    		t.setName("线程 一");
    		t1.setName("线程 二");
    		 
    		// 启动线程
    		t.start();
    		t1.start();
    	}
    }
    

    Java系列(51)——线程_ide_07

2、线程实现的第二种方式

  1. 声明实现 Runnable 接口的类,该类然后实现 run 方法,然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
    Java系列(51)——线程_线程_08

  2. 示例

    public class MyThread2 implements Runnable
    {
    	public void run()
    	{
    		for (int i = 0; i < 5; i++)
    		{
    			//输出线程的名字
    			System.out.println(Thread.currentThread().getName() + "线程在执行" + i);
    		}
    	}
    }
    
    public class TestThread2
    {
    
    	public static void main(String[] args)
    	{		
    		// 创建一个线程实例对象
    		MyThread2 mt1 = new MyThread2();		
    		Thread t1 = new Thread(mt1,"线程一");
    		
    		// 创建第二个线程的实例对象
    		MyThread2 mt2 = new MyThread2();		
    		Thread t2 = new Thread(mt2,"线程二");
    		
    		//启动线程
    		t1.start();
    		t2.start();
    	}
    }
    

    Java系列(51)——线程_ide_09

三、线程的执行原理和生命周期

1、执行原理

  • 线程的并发执行通过多个线程不断的切换CPU的资源,这个速度非常快,我们感知不到,我们能感知到的就是三个线程在并发的执行。
    Java系列(51)——线程_多线程_10

2、生命周期

  1. 新建: 线程被实例化 new 出来。
  2. 准备就绪:线程具有执行的资格,即线程调用了start(),没有执行的权利。
  3. 运行:具备执行的资格和具备执行的权利。
  4. 阻塞:没有执行的资格和执行权利。
  5. 销毁: 线程的对象变成垃圾,释放资源。
    Java系列(51)——线程_i++_11
四、并发安全性问题
  1. 互联网的项目中存在着大量的并发的案例,如卖火车票,电商网站。

  2. 范例:火车站有 100 张票,4 个窗口同时买票。
    分析:4 个窗口是 4 个线程同时在运行,100 票是 4 个线程的共享资源。
    Java系列(51)——线程_java_12
    针对线程的安全性问题,我们需要使用同步(就是要加锁,共享资源只能一个人同时访问)锁。
    Java系列(51)——线程_多线程_13

  3. 语法

    synchronized(锁对象)
    {
    	//操作共享资源的代码
    }
    
  4. 同步代码加在什么地方?需要满足那些条件?
    (1)代码被多个线程访问。
    (2)代码中有共享的数据。
    (3)共享数据被多条语句操作。

  5. Synchronized 同步代码块的锁对象可以是任意类对象(线程的实现方式是使用继承于 Thread),这个对象必须是线程类共享(静态的)。

  6. 示例

    public class SaleTickets extends Thread
    {
    
    	private String name;
    
    	/**
    	  * 因为票数是共享的,而且 SaleTickets 类需要创建多个对象,所以需要static修饰
    	 */
    	static int ticketsCount = 100;
    
    	static Object obj = new Object();
    
    	public SaleTickets(String name)
    	{
    		super(name);
    	}
    
    	@Override
    	public void run()
    	{
    		// 三个线程走到while 同时看到 ticketsCount = 1
    		// synchronized 就是同步锁,这里面的代码只能同时有一个线程进入
    		while (ticketsCount > 0)
    		{
    			/**
    			 * synchronized加入的位置 1.有多线程访问的代码 2.有共享资源的代码 3.共享资源被多个线程修改
    			 */
    			synchronized (obj)
    			{
    				try
    				{
    					Thread.sleep(100);
    				} catch (InterruptedException e)
    				{
    					e.printStackTrace();
    				}
    				if (ticketsCount != 0)
    				{
    					System.out.println(Thread.currentThread().getName() + "正在卖第" + ticketsCount-- + "张票");
    				}
    			}
    		}
    		System.out.println("票已售完");
    	}
    }
    
    public class Test
    {
    
    	public static void main(String[] args)
    	{
    		/**
    		 * 创建四个窗口
    		 */
    		SaleTickets st1 = new SaleTickets("窗口1");
    		SaleTickets st2 = new SaleTickets("窗口2");
    		SaleTickets st3 = new SaleTickets("窗口3");
    		SaleTickets st4 = new SaleTickets("窗口4");
    
    		st1.start();
    		st2.start();
    		st3.start();
    		st4.start();
    	}
    }
    

    Java系列(51)——线程_多线程_14
    Java系列(51)——线程_多线程_15

五、synchronized 锁对象剖析

把买票的方法抽取出来

  1. 示例

    public class TicketsRun implements Runnable
    {
    
    	/**
    	 * 创建共享资源
    	 */
    	private int ticketCount = 100;
    
    	private A a = new A();
    	
    	@Override
    	public void run()
    	{
    		while (ticketCount > 0)
    		{
    			saleTicket();
    		}
    	}
    
    	/**
    	  * 把买票的方法抽取出来
    	 */
    	 public void saleTicket()
    	 { 
    		 synchronized (a) 
    		 { 
    			 if(ticketCount != 0)
    			 {
    				 System.out.println(Thread.currentThread().getName()+"正在卖第"+ticketCount--+"张票"); 
    			 } 
    		 } 
    	 }
    }
    
    class A
    {}
    
  2. Synchronized 是可以加在方法上,如果是静态方法 Synchronized 的所对象就是类的类对象,如果不是静态的方法,Synchronized 如果加在对象方法上,那么它的锁是 this。**

  3. 示例

    public class TicketsRun implements Runnable
    {
    	/**
    	 * 创建共享资源
    	 */
    	private int ticketCount = 100;
    
    	@Override
    	public void run()
    	{
    		while (ticketCount > 0)
    		{
    			try
    			{
    				Thread.sleep(100);
    			} catch (InterruptedException e)
    			{
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			if (ticketCount % 2 == 0)
    			{
    
    				synchronized (this)
    				{
    					if (ticketCount != 0)
    					{
    						System.out.println(Thread.currentThread().getName() + "正在卖第" + ticketCount-- + "张票");
    					}
    				}
    			}
    			else
    			{
    				saleTicket();
    			}
    		}
    	}
    
    	/**
    	  * 如果在方法上来使用 synchronized 就不用使用共享资源锁默认是当前类的对象 this
    	 */
    	public synchronized void saleTicket()
    	{
    		if (ticketCount != 0)
    		{
    			System.out.println(Thread.currentThread().getName() + "正在卖第" + ticketCount-- + "张票");
    		}
    	}
    }
    
  4. 如果在方法上来使用 synchronized 就不用使用共享资源锁,默认是当前类的对象,也就是 this。

  5. 示例

    public class TicketsRun implements Runnable
    {
    
    	/**
    	 * 创建共享资源
    	 */
    	private static int ticketCount = 100;
    	
    	@Override
    	public void run()
    	{
    		while (ticketCount > 0)
    		{
    			try
    			{
    				Thread.sleep(100);
    			} catch (InterruptedException e)
    			{				
    				e.printStackTrace();
    			}
    			if (ticketCount % 2 == 0)
    			{
    
    				synchronized (TicketsRun.class)
    				{
    					if (ticketCount != 0)
    					{
    						System.out.println(Thread.currentThread().getName() + "正在卖第" + ticketCount-- + "张票");
    					}
    				}
    			}
    			else
    			{
    				saleTicket();
    			}
    
    		}
    	}
    
    	/**
    	  * 如果在方法上来使用synchronized就不用使用共享资源锁 默认是当前类的对象
    	 */
    	public static synchronized void saleTicket()
    	{
    		if (ticketCount != 0)
    		{
    			System.out.println(Thread.currentThread().getName() + "正在卖第" + ticketCount-- + "张票");
    		}
    	}
    }
    
    public class Test
    {
    
    	public static void main(String[] args)
    	{
    		TicketsRun tr = new TicketsRun();
    
    		Thread t1 = new Thread(tr, "窗口1");
    		Thread t2 = new Thread(tr, "窗口2");
    		Thread t3 = new Thread(tr, "窗口3");
    		Thread t4 = new Thread(tr, "窗口4");
    
    		t1.start();
    		t2.start();
    		t3.start();
    		t4.start();
    	}
    }
    
六、线程的休眠
  1. 线程的休眠是很必要的,在做服务端的时候为了减少服务器的压力我们需要休眠;如果休眠是在同步代码块中执行,休眠不会让出锁对象
  2. 例如程序当中的死循环,我们可以让线程休眠一段时间后再执行,以提高性能。
  3. 示例
    public class SaleTicket implements Runnable
    {
    
    	/**
    	 * 100张票是共享的数据
    	 */
    	private int tickets = 100;
    
    	// private Object obj = new Object();
    
    	@Override
    	public void run()
    	{
    		while (true)
    		{
    			// 同步代码块
    			synchronized (this)
    			{
    				if (tickets > 0)
    				{
    					try
    					{
    						Thread.sleep(2000);
    						//不会出现两排线的情况,也就是说休眠时抱着锁休眠的
    						System.out.println("--------------------");
    					} catch (InterruptedException e)
    					{
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "正在卖第" + tickets-- + "张票");
    				} else
    				{
    					System.out.println("票已经售完");
    					break;
    				}
    			}
    		}
    	}
    }
    
    public class Test
    {
    
    	public static void main(String[] args)
    	{
    		SaleTicket st = new SaleTicket();
    		
    		Thread t = new Thread(st);
    		Thread t1 = new Thread(st);
    		Thread t2 = new Thread(st);
    		Thread t3 = new Thread(st);
    		Thread t4 = new Thread(st);
    		Thread t5 = new Thread(st);
    
    		t.start();
    		t1.start();
    		t2.start();
    		t3.start();
    		t4.start();
    		t5.start();
    	}
    }
    
    Java系列(51)——线程_多线程_16
七、线程间的通信

生产者与消费者

Java系列(51)——线程_i++_17

  1. 生产者生成水果,如果水果没有被买走那么就不生产处于等待状态,如果水果被消费者买走就的时候消费者会通知生产者告诉他我们已经把水果买走了请生产,消费者同理,如果水果已经生产出来那么就买走,买走之后再通知生产者水果已经没了请生产。

  2. 注意
    (1)线程间的通信共享数据一定要有同步代码块 synchronized。
    (2)一定要有 wait 和 notify,而且二者一定是成对出现。
    (3)生产者和消费者的线程实现一定是在 while(true)里面。

  3. 示例源码

    //篮子
    public class Basket
    {
    	private boolean isEmpty = true;
    
    	public boolean isEmpty()
    	{
    		return isEmpty;
    	}
    
    	public void setEmpty(boolean isEmpty)
    	{
    		this.isEmpty = isEmpty;
    	}
    }
    
    //生产者
    public class ProductThread extends Thread
    {
    	private Basket basket;
    
    	public ProductThread(Basket basket)
    	{
    		super();
    		this.basket = basket;
    	}
    
    	@Override
    	public void run()
    	{
    		while (true)
    		{
    			// 多个线程对同一个资源操作必须同步
    			synchronized (basket)
    			{
    				// 如果篮子不是空的
    				if (!basket.isEmpty())
    				{
    					// 当前线程等待
    					try
    					{
    						//为了维护性能,通常需要休眠一段时间 
    						Thread.sleep(1000);
    						//当前生成水果的线程被挂起,形成阻塞状态
    						basket.wait();
    					} catch (InterruptedException e)
    					{
    						e.printStackTrace();
    					}
    				} 
    				else
    				{
    					System.out.println("生成苹果");
    					// 把篮子装满,有水果了
    					basket.setEmpty(false);
    					// 唤醒消费者线程
    					basket.notify();
    				}
    			}
    		}
    	}
    }
    
    //消费者
    public class BuyThread extends Thread
    {
    	private Basket basket;
    
    	public BuyThread(Basket basket)
    	{
    		super();
    		this.basket = basket;
    	}
    
    	@Override
    	public void run()
    	{
    		while (true)
    		{
    			synchronized (basket)
    			{
    				// 如果篮子是空的
    				if (basket.isEmpty())
    				{
    					// 等待生产者生成
    					try
    					{
    						//为了维护性能,通常需要休眠一段时间 
    						Thread.sleep(1000);		
    						//当前消费水果的线程被挂起,形成阻塞状态				
    						basket.wait();
    					} catch (InterruptedException e)
    					{
    						e.printStackTrace();
    					}
    				} 
    				else
    				{
    					// 买水果
    					System.out.println("把水果买走");
    					//没有水果了
    					basket.setEmpty(true);
    					// 唤醒生产者
    					basket.notify();
    				}
    			}
    		}
    	}
    }
    
    public class Test
    {
    	public static void main(String[] args)
    	{
    		Basket b = new Basket();
    
    		ProductThread pt = new ProductThread(b);
    		BuyThread bt = new BuyThread(b);
    
    		pt.start();
    		bt.start();
    	}
    }
    

    Java系列(51)——线程_java_18

八、线程的优先级
  1. 我们可以通过 setPriority() 方法来设置线程的优先级,但是优先级并不是绝对的,只是先对来说比其他的线程得到 CPU 的资源机会多一些。
    Java系列(51)——线程_多线程_19
    Java系列(51)——线程_ide_20
  2. 示例
    public class MyThread extends Thread
    {
    
    	/**
    	 * 必须重写线程体
    	 */
    	@Override
    	public void run()
    	{
    		for (int i = 0; i < 10; i++)
    		{
    			System.out.println(Thread.currentThread().getName() + "线程在执行" + i);
    		}
    	}
    }
    
    public class TestThread
    {
    
    	public static void main(String[] args)
    	{
    		MyThread t0 = new MyThread();
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    
    		// 打印线程的优先级,默认为5
    		/*
    		 * System.out.println(t0.getPriority()); 
    		 * System.out.println(t1.getPriority());
    		 * System.out.println(t2.getPriority());
    		 */
    		
    		// 设置优先级,Thread.MAX_PRIORITY = 10
    		t0.setPriority(Thread.MAX_PRIORITY);
    		
    		t0.start();
    		t1.start();
    		t2.start();
    	}
    }
    
    Java系列(51)——线程_多线程_21
九、加入线程(join)和等待线程(yield)
  1. join 线程会抢先拿到 CPU 来执行线程,然后其他的线程再来执行。
    Java系列(51)——线程_java_22

    public class MyThread extends Thread
    {
    	/**
    	 * 必须重写线程体
    	 */
    	@Override
    	public void run()
    	{
    		for (int i = 0; i < 10; i++)
    		{
    			System.out.println(Thread.currentThread().getName() + "线程在执行" + i);
    		}
    	}
    }
    
    public class TestThread
    {
    
    	public static void main(String[] args)
    	{
    		MyThread t0 = new MyThread();
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    
    		t0.start();
    		try
    		{
    			t0.join();
    		} catch (InterruptedException e)
    		{
    			e.printStackTrace();
    		}
    		t1.start();
    		t2.start();
    	}
    }
    

    Java系列(51)——线程_ide_23

  2. 当前的线程从运行阶段回到就绪阶段,目的是把 CPU 的资源让给其他的线程。
    Java系列(51)——线程_多线程_24

    public class MyThread extends Thread
    {
    	/**
    	 * 必须重写线程体
    	 */
    	@Override
    	public void run()
    	{
    		for (int i = 0; i < 100; i++)
    		{
    			System.out.println(Thread.currentThread().getName() + "线程在执行" + i);
    			
    			//当前进程回到就绪状态,把 cpu 的资源让出,但是仍然具有抢占 CPU 资源的资格
    			Thread.yield();
    		}
    	}
    }
    
    public class TestThread
    {
    	public static void main(String[] args)
    	{
    		MyThread t0 = new MyThread();
    		MyThread t1 = new MyThread();
    		MyThread t2 = new MyThread();
    
    		t0.start();
    		t1.start();
    		t2.start();
    	}
    }
    

结果图(相对来说比较均匀):
Java系列(51)——线程_多线程_25

十、守护线程
  1. 守护线程会随着主线程的结束而结束。
    Java系列(51)——线程_java_26
    public class DaemonThread extends Thread
    {
    	@Override
    	public void run()
    	{
    
    		for (int i = 0; i < 50; i++)
    		{
    			System.out.println(this.getName() + "正在运行" + i);
    		}
    	}
    }
    
    public class DaemonTest
    {
    
    	public static void main(String[] args)
    	{
    		DaemonThread pt0 = new DaemonThread();
    		DaemonThread pt1 = new DaemonThread();
    		
    		//设置为守护线程
    		pt0.setDaemon(true);
    		pt1.setDaemon(true);
    		
    		pt0.start();
    		pt1.start();
    
    		for (int i = 0; i < 5; i++)
    		{
    			System.out.println(Thread.currentThread().getName() + "主线程正在运行" + i);
    		}
    	}
    }
    

Java系列(51)——线程_ide_27

十一、线程死锁

Java系列(51)——线程_ide_28

  • 在做线程开发的时候要避免出现死锁。

    public class Lock
    {
    	//两把锁	
    	static Object lock1 = new Object();
    	static Object lock2 = new Object();
    }
    
    public class DieLockDemo implements Runnable
    {
    
    	private boolean flag;
    
    	public DieLockDemo(boolean flag)
    	{
    		super();
    		this.flag = flag;
    	}
    
    	@Override
    	public void run()
    	{
    		if (flag)
    		{
    			synchronized (Lock.lock1)
    			{
    				System.out.println("线程一执行拿到第一把锁");
    				
    				synchronized (Lock.lock2)
    				{
    					System.out.println("线程一执行拿到第二把锁");
    				}
    			}
    		} 
    		else
    		{
    			synchronized (Lock.lock2)
    			{
    				System.out.println("线程二执行拿到第二把锁");
    				
    				synchronized (Lock.lock1)
    				{
    					System.out.println("线程二执行拿到第一把锁");
    				}
    			}
    		}
    	}
    }
    
    public class DieLockTest
    {
    
    	public static void main(String[] args)
    	{
    		DieLockDemo dl1 = new DieLockDemo(true);
    		DieLockDemo dl2 = new DieLockDemo(false);
    
    		Thread t1 = new Thread(dl1);
    		Thread t2 = new Thread(dl2);
    
    		t1.start();
    		t2.start();
    	}
    }
    

    Java系列(51)——线程_多线程_29

如有错误,欢迎指正!