Java多线程基础
目录:
- 创建线程方式
- 线程生命周期
- 线程的同步
- 线程的通信(经典例题:消费者/生产者问题)
对程序、进程、线程的理解:
程序(program):为完成特定任务、用某种语言编写的一组指令的集合。即指一 段静态的代码,静态对象。
进程(process):是程序的一次执行过程,或是正在运行的一个程序。
线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
Java线程创建的方式
方式一:继承Thread类
代码示例:
package advanced_tutorial.Thread; class MyThread extends Thread{//继承Thread类,作为线程的实现类 //覆写run()方法 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+ "运行:" + i); } } } public class Demo01{ public static void main(String[] args) { MyThread myThread = new MyThread(); //实例化对象 MyThread myThread2 = new MyThread(); //实例化对象 myThread.setName("线程A"); //设置线程名 myThread2.setName("线程B"); //设置线程名 myThread.start(); //调用start()方法开启线程:启动当前线程,调用当前线程的run()方法 myThread2.start(); //调用start()方法开启线程:启动当前线程,调用当前线程的run()方法 } }
程序运行结果:
从运行的结果可以看出,两个线程输出的内容是交错运行的,说明哪个对象抢到了CPU资源,哪个线程就可以运行。在线程启动时虽然调用的是start()方法,但实际上调用的却是run()定义的主体。
方式二:实现Runnable接口
代码示例:
class MyThread implements Runnable{ //覆写方法:run() @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0){ System.out.println(Thread.currentThread().getName() + "运行,i=" + i); } } } } public class SingleThread { public static void main(String[] args) { MyThread m1 = new MyThread(); //实例化对象 MyThread m2 = new MyThread(); //实例化对象 Thread t1 = new Thread(m1); //实例化Thread类 Thread t2 = new Thread(m2); //实例化Thread类 t1.setName("线程A"); //设置线程名 t2.setName("线程B"); //设置线程名 t1.start();//①启动线程 t2.start();//②启动线程 } }
Thread类和Runnable接口两者的联系
public class Thread extends Object implements Runnable
从 Thread 类的定义可以清楚的发现,Thread 类也是 Runnable 接口的子类,但在Thread类中并没有完全实现 Runnable 接口中的 run() 方法。
Private Runnable target; public Thread(Runnable target,String name){ init(null,target,name,0); } private void init(ThreadGroup g,Runnable target,String name,long stackSize){ ... this.target=target; } public void run(){ if(target!=null){ target.run(); } }
从定义中可以发现,在 Thread 类中的 run() 方法调用的是 Runnable 接口中的 run() 方法,也就是说此方法是由 Runnable 子类完成的,所以如果要通过继承 Thread 类实现多线程,则必须覆写 run()。
实际上 Thread 类和 Runnable 接口之间在使用上也是有区别的,如果一个类继承 Thread类,则不适合于多个线程共享资源,而实现了 Runnable 接口,就可以方便的实现资源的共享。
方式三:实现Callable接口。 --->JDK 5.0新增
1、call()可以有返回值。
2、call()可以抛出异常,被外面的操作捕获,获取异常的信息
3、Callable是支持泛型的
示例代码:
class Number implements Callable{ //1、创建一个实现Callable的实现类 @Override //2、实现call方法,将此线程需要执行的操作声明在call中 public Object call() throws Exception { int sum = 0; for (int i = 0; i <= 100; i++) { if (i % 2 ==0){ System.out.println(i); sum += i; } }return sum; } } public class afa { public static void main(String[] args) { Number newThread = new Number(); //3、创建Callable接口实现类的对象 FutureTask futureTask = new FutureTask(newThread); //4、将此Callable接口实现类的对象作为对象传递到FutureTask构造器中,创建FutureTask的对象 new Thread(futureTask).start(); //5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start() try { //6、获取Callable中call方法的返回值 Object sum = futureTask.get(); //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值 System.out.println("总和为:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
线程的生命周期(状态变化)
1、新建(当一个Thread类或其子类的对象被声明并创建时,新生的线程处于新建状态)
2、就绪(处于被新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已经具备了运行的条件,只是没分配到CPU资源)
3、运行(当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能)
4、阻塞(在某种特殊情况下,被认为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态)
5、死亡(线程已经完成了它的全部工作或线程被提前强制性地中止终止或出现异常导致结束)
常用的一些方法
start(): 启动当前线程;调用当前线程的run()
run(): 通常需要重写Thread类中此方法,将创建的线程要执行的操作声明在此方法中
currentThread(): 静态方法,返回执行当前代码的线程
getName(): 获取当前线程的名字
setName(): 设置当前线程的名字状态
yield(): 释放当前CPU的执行权
join(): 在线程A中调用线程B的join(),此时线程A就进入阻塞,直到线程B完全执行完过后,线程A才结束阻塞状态
sleep(long millitime): 让当前线程 “睡眠 “ 指定的 millitime 毫秒。在指定的millitiem毫秒,当前线程是阻塞状态
isAlive(): 判断当前线程是否存活
线程的优先级等级
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5
涉及的方法 getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
说明:
线程创建时继承父线程的优先级 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
线程的同步
一个多线程的程序如果是通过 Runnable 接口实现的,则意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。
解决线程安全的方法:
方式一:同步代码块
synchronized(同步监视器){ 同步的代码 }
class Mythread implements Runnable { private int ticket = 10; //设置票数 @Override public void run() { while (true) { synchronized(this){ //此时的this:就是Mythread对象 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; } else { break; } } } } } public class MultiThread { public static void main(String[] args) { MyThread mt = new Mythread(); //定义线程对象 Thread t1 = new Thread(mt); //定义Thread类对象 Thread t2 = new Thread(mt); //定义Thread类对象 Thread t3 = new Thread(mt); //定义Thread类对象 t1.setName("窗口1"); //设置窗口名 t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); //启动线程 t2.start(); t3.start(); } }
程序执行结果:
说明:1、操作共享数据的代码,即为需要被同步的代码 -->不能包含多了,不能包含少了
2、共享数据:多个线程共同操作的
3、同步监视器:俗称:锁,任何一个类的对象,都可以充当锁,要求多个线程必须要共用同一把锁
补充:在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器
方式二:同步方法
除了可以将需要的代码设置成同步代码块外,也可以使用 synchronized 关键字将一个方法声明为同步方法
synchronized 方法返回值 方法名称(参数列表)
class MyThread implements Runnable { private int ticket = 100; //设置票数 @Override public void run() { while (true){ show(); } } private synchronized void show(){ //声明同步方法 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; } } } public class afa { public static void main(String[] args) { MyThread mt = new MyThread(); //定义线程对象 Thread t1 = new Thread(mt); //定义Thread类对象 Thread t2 = new Thread(mt); //定义Thread类对象 Thread t3 = new Thread(mt); //定义Thread类对象 t1.setName("窗口1"); //设置窗口名 t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); //启动线程 t2.start(); t3.start(); } }
方式三:Lock锁 --->JDK5.0新增
class Window5 implements Runnable{ private int ticket = 10; private ReentrantLock lock = new ReentrantLock(true); //1、实例化ReentrantLOCKLock @Override public void run() { while (true){ try{ lock.lock(); //2、调用锁定lock() if (ticket > 0){ System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket); ticket--; }else{ break; } }finally { lock.unlock();//3、调用解锁方法:unlock() } } } } public class LockTest { public static void main(String[] args) { Window5 w = new Window5(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
此代码完成了与之前同步代码同样的功能。不同的是:Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的去实现(unLock())
银行存钱案例:https://www.cnblogs.com/tisnk/articles/13356595.html
线程通信
涉及到的三个方法:
wait(): 一旦执行此方法当前线程进入阻塞状态,释放同步监视器。
notify(): 一旦执行此方法,就会唤醒被wait()的一个线程,如果有多个线程 被wait(),就唤醒优先级高的那个
notifyAll(): 一旦执行此方法,就会唤醒所有被wait()的线程
代码示例:
class Number implements Runnable{ private int number = 1; @Override public void run() { while (true){ synchronized (this) { this.notify(); //唤醒被wait()阻塞的一个线程 if (number <= 100){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + number); number++; try { this.wait(); //使得调用如下wait()方法的线程进入阻塞状态 } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } } } } } public class Communication { public static void main(String[] args) { Number number = new Number(); Thread t1 = new Thread(number); Thread t2 = new Thread(number); t1.setName("线程1"); t2.setName("线程2"); t1.start(); t2.start(); } }
线程通信的应用:经典例题:生产者/消费者问题
/** * 生产者(Producttor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品, * 店员生产一次只能持有固有数量的商品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者 * 停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果 * 店中有产品了再通知消费者来取走产品。 */ class Clerk{ private int productCount = 0; //产品数量 public synchronized void produceProduct() { //生产产品 if (productCount < 20){ //如果产品数量小于20则开始生产产品 productCount++; System.out.println(Thread.currentThread().getName() + "开始生产第" + productCount + "个产品"); notifyAll(); //唤醒其他线程 }else{ try { //等待 wait(); //阻塞 } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void consumeProduct() { //消费产品 if (productCount > 0){ //如果产品数量大于0则开始消费产品 System.out.println(Thread.currentThread().getName() + "开始消费第" + productCount + "个产品"); productCount--; notifyAll(); //唤醒其他线程 }else{ try { //等待 wait(); //阻塞 } catch (InterruptedException e) { e.printStackTrace(); } } } } class Producer extends Thread{ //生产者 private final Clerk clerk; public Producer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(getName() + ":开始生产产品"); while (true){ try { //等待15毫秒开始生产产品 Thread.sleep(15); } catch (InterruptedException e) { e.printStackTrace(); } clerk.produceProduct(); //调用Clerk类中的produceProduct生产方法 } } } class Consumer extends Thread{ //消费者 private final Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println(getName() + "开始消费产品"); while (true) { //等待30毫秒开始生产产品 try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } clerk.consumeProduct(); //调用Clerk类中的consumeProduct生产方法 } } } public class ProductTest { public static void main(String[] args) { Clerk clerk = new Clerk(); //创建产品对象 Producer p1 = new Producer(clerk); //创建生产者对象 p1.setName("生产者"); Consumer c1 = new Consumer(clerk); //创建消费者1 c1.setName("消费者1"); Consumer c2 = new Consumer(clerk); //创建消费者2 c2.setName("消费者2"); p1.start(); c1.start(); c2.start(); } }