进程和线程

进程:并发情况下占有计算机资源的程序,是一个动态的概念,是对计算机系统而言的资源使用者。

线程:获得资源的进程会利用多个线程完成工作。线程是进程的颗粒级执行单元,属于轻量级的进程。

Thread、Runnable、Callable

Thread

开启多线程的核心类,内部提供很多有关线程的方法,并实现了Runnable接口。

Thread开启线程基本使用

public class DemoThread {

	/*
	 * 进程:一个正在运行的程序,可以包含多条线程,但至少要有一条线程再执行
	 * 
	 * java程序运行时默认开启一条主线程
	 * 
	 * 线程:就是程序的执行路径,多线程就是多条线程同时执行(假象),Cpu再多条线程之间交替执行
	 * 
	 * 开启线程的方式:
	 * 
	 * 1.继承Thread类,实现run()方法,再run()方法中放想要执行的代码
	 */
	public static void main(String[] args) throws Exception {

		// 4.创建线程对象

		MyThread myThread = new MyThread("线程3");

		// 设置线程名

		myThread.setName("线程1");
		
		System.out.println(myThread.getPriority());

		// 5.使用start()方法开启线程

		myThread.start();

		for (int i = 0; i < 200; i++) {

			System.out.println("bb");

		}

	}
}

//1.创建继承Thread类的子类,实现run()方法	

class MyThread extends Thread {

	public MyThread() {
		super();
		// TODO 自动生成的构造函数存根
	}

	public MyThread(String name) {
		super(name);
		// TODO 自动生成的构造函数存根
	}

	@Override

	// 2.重写run()方法

	public void run() {

		// 3.run()方法中放需要执行的任务

		for (int i = 0; i < 200; i++) {

			// 获取线程名称

			System.out.println(getName() + "aa");

		}
	}
}

Runnable

为弥补Thread的单继承缺陷,Runnable接口同样可以开启多线程,不同的是其可以完成类的“多继承”。

Runnable开启线程基本使用

public class DemoRunnable {

	public static void main(String[] args) {
		
		//4.创建runnable对象,并放入Thread类中。
		
		MyRunnable mr=new MyRunnable();
		
		Thread thread = new Thread(mr);
		
		//设置线程名
		
		thread.setName("线程2");

		//5.使用start方法
		
		thread.start();
		
		for (int i = 0; i < 2009; i++) {

			System.out.println("bbbbbb");

		}
	}

}

//1.找一个类实现Runnable接口
class MyRunnable  implements Runnable {

	@Override
	
    //2.重写run方法
	
	public void run() {
		
        //3.将需要执行的任务放在这
		
		for (int i = 0; i < 2009; i++) {
			
			//获取线程名Thread.currentThread().getName()
			
			System.out.println(Thread.currentThread().getName()+"aa");

		}

	}

}

Callable

Thread、Runnable多实现的多线程都无法带出返回值,而往往实际应用中需要将数据在多个线程中传递,为此Callable出现并解决该问题。

Callable开启线程基本使用

public class DemoCallable {
	
	/**
	 * Callable 既能开启线程,又能得到线程返回值结果
	 * 
	 * 1.实现Callable接口,重写call方法
	 * 
	 * 2.创建FutureTask对象
	 * 
	 * 3.创建Thread对象,并将FutureTask对象传入构造方法中
	 * 
	 * 4.开启start方法
	 * 
	 * @throws Exception
	 * @throws InterruptedException
	 * 
	 */
	
	public static void main(String[] args) throws InterruptedException, Exception {

		// 创建FutureTask对象

		FutureTask<Integer> ft1 = new FutureTask<Integer>(new myCallable());

		FutureTask<Integer> ft2 = new FutureTask<Integer>(new myCallable());

		// 创建Thread对象,放入FutureTask对象,开启start方法

		Thread thread1 = new Thread(ft1, "线程a");

		thread1.start();

		Thread thread2 = new Thread(ft2, "线程b");

		thread2.start();

		// 调用get方法获取call方法的返回值

		System.out.println(ft1.get());

		System.out.println(ft2.get());
	}

}

class myCallable implements Callable<Integer> {

	// 重写call方法

	public Integer call() throws Exception {

		int sum = 0;

		for (int i = 0; i < 70; i++) {

			System.out.println(Thread.currentThread().getName() + ",当前使用的值" + i);

			sum += i;

		}

		return sum;
	}

}

线程状态

java 核心线程 java核心线程是什么意思_java 核心线程

线程分类

线程可以分为用户线程和后台线程。

用户线程正相反,可以理解为前台线程,虚拟机必须等待其运行完毕才可以结束进程。

后台线程可以按其表面意义进行理解,作为后台的线程运行,其最本质的特征就是虚拟机无需等待其运行完毕即可结束进程。

public class DemoDaemon {

	public static void main(String[] args) {

		Thread thread1 = new Thread() {

			public void run() {

				for (int i = 0; i < 1000; i++) {

					System.out.println("我是后台线程");

				}

			}
		};

		// 将线程设置为后台线程

		thread1.setDaemon(true);

		Thread thread2 = new Thread() {

			public void run() {

				for (int i = 0; i < 5; i++) {

					System.out.println("我是前台线程");

				}

			}
		};
		
		thread2.start();
		
		thread1.start();
	}
}

Thread对应的线程方法

线程优先级

线程可以有优先级,优先级从1-10,越高越容易被执行,但是最主要还是看cpu心情。

优先级常量

Value

最小优先级

MIN_PRIORITY(1)

默认优先级

NORM_PRIORITY(5)

最大优先级

MAX_PRIORITY(10)

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        Thread threadMin = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("最小优先级" + i);
            }
        });

        Thread threadMax = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("最大优先级" + i);
            }
        });

        //注意:要先设置优先级,在启动线程才会生效!
        threadMin.setPriority(Thread.MIN_PRIORITY);
        threadMax.setPriority(Thread.MAX_PRIORITY);

        threadMin.start();
        threadMax.start();

    }
}

线程状态

在Thread中定义了一个enum类型用来存放线程状态,可以使用getState方法获取当前线程的状态。

状态

Value

新建

NEW

运行

RUNNABLE

阻塞

BLOCKED

等待

WAITING

超时等待

TIMED_WAITING

终止

TERMINATED

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

       Thread thread=new Thread(()->{
           for (int i = 0; i < 10; i++) {
               System.out.print("T");
           }
       });

        System.out.println(thread.getState());

        thread.start();

        System.out.println(thread.getState());

        Thread.sleep(1000);
        System.out.println("\n"+thread.getState());



    }
}

线程休眠

sleep(long millis)方法,可以让当前线程休眠millis毫秒,其特殊在如果该线程有锁,则会抱着锁休眠,即不释放资源!

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

       new Thread(()->{
           for (int i = 0; i < 10; i++) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.print("T");
           }
       }).start();

    }
}

线程停止

stop()和 interrupt()

stop()强行终止当前线程,存在线程安全问题,会破坏线程的原子性。

interrupt()不会真的中止线程,只是给线程加上了一个标识,即isInterrupted从false变成了true,我们需要通过isInterrupted标识进行手动条件判断终止线程,而利用标志位终止线程也是最好的线程停止方式。

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                if (i==5)Thread.currentThread().stop();
                System.out.println("stop *");
            }
        }).start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                if (i==5)Thread.currentThread().interrupt();
                if (Thread.currentThread().isInterrupted())break;
                System.out.println("interrupt *");
            }
        }).start();

    }
}

线程让步

yield()可以让当前线程停止重新进入就绪状态抢占时间片,虽然让步很礼貌,但是运气好的时候会一直抢到时间执行,因此让步可能会失败。

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        new Thread(()->{
            for (int i = 0; i < 10; i++) {

                if (i==5)Thread.yield();
                System.out.println("*");
            }
        }).start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                System.out.println("/");
            }
        }).start();

    }
}

线程插入

join()方法可以进行线程插队,该方法插入后会独占时间片知道执行完。

public class DemoTest extends JFrame {
	private Thread threadA;
	private Thread threadB;
	JProgressBar jProgressBar1=new JProgressBar();
	JProgressBar jProgressBar2=new JProgressBar();
	int i=0;
	public DemoTest(){
		super();
		getContentPane().add(jProgressBar1, BorderLayout.NORTH);
		getContentPane().add(jProgressBar2, BorderLayout.SOUTH);
		jProgressBar1.setStringPainted(true);
		jProgressBar2.setStringPainted(true);
		threadA=new Thread(new Runnable() {
			int i=0;
			@Override
			public void run() {
				while (true){
					jProgressBar1.setValue(++i);
					try {
						Thread.sleep(100);
						threadB.join();
					}catch (Exception e){
						e.printStackTrace();
					}
				}
			}
		});
		threadA.start();
		threadB=new Thread(new Runnable() {
			int i=0;
			@Override
			public void run() {
				while (true){
					jProgressBar2.setValue(++i);
					try {
						Thread.sleep(100);
					}catch (Exception e){
						e.printStackTrace();
					}
					if (i==100){
						break;
					}
				}
			}
		});
		threadB.start();
	}
    public static void init(JFrame jFrame,int width,int height){
		jFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
		jFrame.setSize(width, height);
		jFrame.setVisible(true);
	}
	public static void main(String[] args) {
		init(new DemoTest(),200,100);
	}
}

线程通信

wait()和notify()实现线程间的通信,要利用锁对象控制与该锁有关的线程,可以利用标志位进行线程执行,wait会使当前线程释放锁并进入等待状态,notify会通知其他线程获取锁并进行执行。

public class DemoPrinter {
	/**
	 * 多线程通信:
	 * 
	 * 需求:
	 *    要求有两条线程,具有通信效果
	 *    每个线程只执行一次
	 * 
	 * 效果:
	 *    传智播客
	 *    黑马程序员
	 *    传智播客
	 *    黑马程序员
	 *    
	 * wait和notify方法必须用于同步代码中
	 * 
	 * 并且必须用锁对象来调用
	 * 
	 * 小细节:
	 * 
	 *    sleep等待不释放锁
	 *    
	 *    wait等待释放锁
	 */

	public static void main(String[] args) {

		Printer pr = new Printer();

		new Thread() {

			public void run() {

				while (true) {

					try {
						
						pr.print1();
						
					} catch (Exception e) {
						
						e.printStackTrace();
					}

				}
			}

		}.start();

		new Thread() {

			public void run() {

				while (true) {

					try {
						
						pr.print2();
						
					} catch (Exception e) {
						
						e.printStackTrace();
					}

				}
			}

		}.start();

	}
}

class Printer {
	
	int flag=1;

	public void print1() throws Exception {

		synchronized (Printer.class) {
			
			if(flag!=1) {
				
				//flag不是1,不是线程1执行时间,线程1等待
				
				Printer.class.wait();
			}
			
			System.out.print("传");

			System.out.print("智");

			System.out.print("播");

			System.out.print("客");

			System.out.println();
			
			flag=2;
			
			Printer.class.notify();
			
		}

	}

	public void print2() throws Exception {

		synchronized (Printer.class) {
			
			if(flag!=2) {
				
				//flag不是2,不是线程2执行时间,线程2等待
				
				Printer.class.wait();
				
			}

			System.out.print("黑");

			System.out.print("马");

			System.out.print("程");

			System.out.print("序");

			System.out.print("员");

			System.out.println();
			
			flag=1;
			
			Printer.class.notify();

		}

	}
}

不安全情况

当多个线程同时操作同一数据时,由于时间片存在影响线程代码的运行进度,就可能导致数据出现不一致的情况,破坏数据的原子性。

public class DemoThreadTest2 {
	
	/**多个线程处理同一个共享资源的情况
	 * 
	 * 需求:模拟火车售票过程
	 *     两个售票窗口共享10张票
	 * 
	 * A:接口式实现
	 * 
	 * B:继承类实现
	 * 
	 */
	
	public static void main(String[] args) {
		
		
		Thread thread1 = new MyTicketsThread("窗口1"); 
		
		Thread thread2 = new MyTicketsThread("窗口2"); 
		
		thread1.start();
		
		thread2.start();
		
	}
	

}

class MyTicketsThread extends Thread{
	
	
	
	public MyTicketsThread() {
		
		super();
		
	}

	public MyTicketsThread(String name) {
		
		super(name);
		
	}

	 int tickets=20;

	@Override
	
	public void run() {
		
		while (true) {
			
			if (tickets<=0) {
				
				break;
				
			}
			
			System.out.println(getName()+"卖出了"+(tickets--)+"号票");
			
		}
		
		
	}
	
}

锁机制

为了解决多线程的不安全情况,引入synchronized关键字和锁机制,实际上就是一个显示锁(Lock),一个隐式锁(synchronized)。用这俩种方法保证多线程的数据操作同步性。

synchronized隐式锁

synchronized可以直接用于方法中,锁为该类对象,也可以独立成块synchronized(一般用类.class){ }

public class DemoThreadTest3 {

	/**
	 * 多个线程处理同一个共享资源的情况
	 * 
	 * 需求:模拟火车售票过程 两个售票窗口共享10张票
	 * 
	 * A:接口式实现
	 * 
	 * B:继承类实现
	 * 
	 * 同步代码方法 在方法返回值之前加 synchronized
	 * 
	 * 锁对象:非静态方法就是this类对象
	 * 
	 *       静态方法就是class字节码
	 */

	public static void main(String[] args) {

		MyTickets1 myTickets1 = new MyTickets1();

		Thread thread1 = new Thread(myTickets1, "窗口1");

		Thread thread2 = new Thread(myTickets1, "窗口2");

		Thread thread3 = new Thread(myTickets1, "窗口3");

		thread1.start();

		thread2.start();

		thread3.start();

	}

}

class MyTickets1 implements Runnable {

	int tickets = 100;

	public void run() {

		while (true) {

			extracted();
			
			
			  try {
			  
			  Thread.sleep(100);
			  
			  } catch (InterruptedException e) {
			  
			  e.printStackTrace(); }
			 

		}

	}

	public synchronized void extracted() {

		if (tickets > 0) {

			System.out.println(Thread.currentThread().getName() + "卖出了" + (tickets--) + "号票");

		}

	}

}

Lock显示锁

Lock接口实现显示锁,最常用的是其实现类ReentrantLock(可重入锁),主要依靠lock()和unlock()进行数据同步。

public class DemoThreadTest4 {

	public static void main(String[] args) {
		
		Tickets ti=new Tickets();
		
		new Thread(ti,"窗口1").start();
		
		new Thread(ti,"窗口2").start();
		
		new Thread(ti,"窗口3").start();
	
	}
}

class Tickets implements Runnable {

	int tickets = 100;
	
	Lock lock=new ReentrantLock();

	public void run() {

		while (true) {
			
			lock.lock();//上锁

			if (tickets <= 0) {
				
				lock.unlock();

				break;

			}

			try {

				Thread.sleep(50);

			} catch (InterruptedException e) {

				e.printStackTrace();
			}

			System.out.println(Thread.currentThread().getName() + "卖出了" + (tickets--) + "号票");
			
			lock.unlock();//释放锁

		}

	}

}

死锁

虽然锁的出现解决了数据操作同步问题,保证多线程安全性,但同时也引发一个很严重的现象——死锁。该现象会导致进程卡死,如果运行在服务器中将会是极其严重的错误。

死锁的出现条件(四条皆必要,缺一不可)

  1. 互斥条件:多条线程互相占有对方想要的资源。
  2. 占有和等待条件:每条线程占有对方想要的资源,同时等待自己想要的资源。
  3. 不剥夺条件:线程所占有的资源不可以被强制剥夺,只可以等待其主动释放。
  4. 循环等待条件:每条线程都占有一个他人想要的资源,并等待自己想要的资源,由此形成一条死链。

避免死锁的核心思想就是破坏其出现的条件。

线程池

手动开启线程需要新建很多Thread类启动,因此不仅非常浪费空间,更重要的是无法对这些线程进行统一管理。为此引入了线程池的概念。

线程池最主要的工作就是管理线程相关事宜。

以Callable接口进行举例

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //新建固定容量的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //提交(运行)两个线程,由于该线程实现Callable接口,因此具有Future类型的返回值,其泛型根据Callable接口而定
        Future submit1 = executorService.submit(new Cosumer());
        Future<Integer> submit2 = executorService.submit(new Producer());
        //利用get方法获取返回值
        Object res1 = submit1.get();
        Integer res2 = submit2.get();
        //输出
        System.out.println(res1);
        System.out.println(res2);
        //停止所有线程
        executorService.shutdownNow();

    }
}

class Producer implements Callable<Integer> {
    private int i = 0;

    @Override
    public Integer call() throws Exception {
        for (; i < 10; i++) {
            System.out.println(i);
        }
        return i;
    }
}

class Cosumer implements Callable {
    private int i = 10;

    @Override
    public Object call() throws Exception {
        for (; i < 20; i++) {
            System.out.println(i);
        }
        return i;
    }
}

总结

一个程序就是一个进程,该进程默认有一个主线程,开启多线程后会由CPU进行时间调度完成各个线程。

开启线程有三种方法

  1. 继承Thread类重写run方法
  2. 实现Runnable接口重写run方法
  3. 实现Callable接口重写call方法

Thread类是线程的核心类,其封装有很多控制线程的方法。

当线程太多时可以利用线程池进行管理。

最重要的事情是保证数据同步,在进行数据操作的位置或方法加Lock显示锁或synchronized关键字。

Author 小葳宝贝最爱吃饭