** 概念
进程:简单说就是一个正在运行的程序。进程负责分配程序的内存空间等资源。
线程:一个进程的某个执行流程。一个进度可以有多个线程。进程中的多个线程共享进程的内存。
多线程就是一个进程中有多个线程同时进行。
对于电脑,多个程序同时运行,其实是CPU的分时机制在起作用,不过现在都是多核的电脑了。
多线程意味这可以在一个进程里同时执行多个任务。而且可以提高资源的利用率。
# 如何使线程
方式一、继承自Thread类
>>
继承自Thread类
>>
重写run()方法
>>
创建继承自Thread类的实例
>>
调用start()方法开启线程
publicclass Tm05 extends Thread{ /** * 一个程序就是一个进程,进程负责分配内存 * 一个进程有多个线程[多线程]可以执行不同的任务,线程负责CPU资源的抢夺 * 多线程提高了资源的利用率 * 对于单核的电脑对于微观来说是单进程的 * 任何一个java程序,jvm在运行的时候创建一个主线程,来执行main中的代码 * 任何一个java程序,至少有两个线程[1.main | 2.垃圾回收器[GC]] */ @Override public // for(int System.out.println("run()+"+(i+1)); } } public System.out.println("多线程使用"); System.out.println("#继承Thread类 System.out.println("注意不要调用run()方法"); Tm05 t=new Tm05(); t.start();; for(int System.out.println("main()+"+(i+1)); } } } |
方式二、实现Runnable接口[tips:推荐使用第二种,因为java是单继承多实现的]
>>实现Runnable接口
>>重写run()方法
>>创建Runnable接口子类的实例
>>创建Thread类实例,并将Runnable接口子类的实例作为实参传入到构造方法
>>由Thread类的实例调用start()方法开启线程[start()方法在Thread类中]
package hs; /** * * */ publicclass Tm10 static Object // int @Override public while(true){ synchronized (lockState) { if(num>0){ num--; System.out.println(Thread.currentThread().getName()+"卖出1张,还有"+num+"张"); }else{ System.out.println("已经买完了......"); break; } } } } public Tm10 t=new Tm10(); Thread t1=new Thread(t, Thread t2=new Thread(t, Thread t3=new Thread(t, t1.start(); t2.start(); t3.start(); } } |
** 线程的状态和常见的方法
就绪状态:状态的线程位于可运行线程池中,等待获取cpu的执行权
临时阻塞状态:线程因为某种原因放弃CPU使用权,暂时停止运行
运行状态:就绪状态的线程获取了CPU执行权,执行程序代码
常见的线程方法
publicclass Tm12 implements Runnable{ @Override public int while(true){ i++; System.out.println(Thread.currentThread().getName()+" i="+i); if(i>=10){ break; } } } /** * 常用的线程方法 * @throws InterruptedException */ public Tm12 t=new Tm12(); Thread th=new Thread(t, // # setName() th.setName("set thread name"); // # setPriority() th.setPriority(10); // # start() th.start(); // # sleep() Thread.sleep(1000); // # setDaemon() th.setDaemon(true); // # // # currentThread() Thread thMain=Thread.currentThread(); for(int System.out.println(Thread.currentThread().getName()+" i="+i); } } } |
** 同步代码块和同步函数
同步代码块[同步代码块比较容易控制锁对象,所以推荐使用同步代码块]
@Override public while (true) { synchronized (lockState) { if (num > 0) { num--; System.out.println(Thread.currentThread().getName()+"成功卖出1张票,还有:" } else { System.out.println("已经买完了票......"); break; } } } } |
同步函数
// 静态同步函数 --> 锁对象是唯一的 // **推荐使用同步代码块** public while(true){ if(money-1000>0){ money=money-1000; System.out.println(Thread.currentThread().getName()+"取了1000,还有"+money); }else{ System.out.println("对不起余额不足......"); break; } } } |
** 锁对象
Java中的每个对象都有一个内置锁,只有当对象具有同步方法代码时,内置锁才会起作用,当进入一个同步的非静态方法时,就会自动获得与类的当前实例(this)相关的锁,该类的代码就是正在执行的代码。获得一个对象的锁也成为获取锁、锁定对象也可以称之为监视器来指我们正在获取的锁对象。
因为一个对象只有一个锁,所有如果一个线程获得了这个锁,其他线程就不能获得了,直到这个线程释放(或者返回)锁。也就是说在锁释放之前,任何其他线程都不能进入同步代码(不可以进入该对象的任何同步方法)。释放锁指的是持有该锁的线程退出同步方法,此时,其他线程可以进入该对象上的同步方法。
1:只能同步方法(代码块),不能同步变量或者类
2:每个对象只有一个锁
3:不必同步类中的所有方法,类可以同时具有同步方法和非同步方法
4:如果两个线程要执行一个类中的一个同步方法,并且他们使用的是了类的同一个实例(对象)来调用方法,那么一次只有一个线程能够执行该方法,另一个线程需要等待,直到第一个线程完成方法调用,总结就是:一个线程获得了对象的锁,其他线程不可以进入该对象的同步方法。
5:如果类同时具有同步方法和非同步方法,那么多个线程仍然可以访问该类的非同步方法。
同步会影响性能(甚至死锁),优先考虑同步代码块。
6:如果线程进入sleep()睡眠状态,该线程会继续持有锁,不会释放。
** 生产者和消费者
[百度百科]
生产者消费者问题(英语:Producer-consumerproblem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了两个共享固定大小缓冲区的线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
wait:告诉当前线程放弃执行权,并放弃监视器(锁)并进入阻塞状态,直到其他线程持有获得执行权,并持有了相同的监视器(锁)并调用notify为止。
notify:唤醒持有同一个监视器(锁)中调用wait的第一个线程,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。注意:被唤醒的线程是进入了可运行状态。等待cpu执行权。
notifyAll:唤醒持有同一监视器中调用wait的所有的线程。
如何解决生产者和消费者的问题?
可以通过设置一个标记,表示数据的(存储空间的状态)例如,当消费者读取了(消费了一次)一次数据之后可以将标记改为false,当生产者生产了一个数据,将标记改为true。
,也就是只有标记为true的时候,消费者才能取走数据,标记为false时候生产者才生产数据。
package hs; import java.util.ArrayList; import java.util.List; /** * */ class Product{ String name; double public Product(String this.name=name; this.price=price; } } /** * */ class Producer static List<Product> public Producer(List<Product> p) { this.p=p; } @Override public while(true){ synchronized (p) { if(p.size()<10){ Product if(i%2==0){ pro=new Product("西瓜",2.99); p.add(pro); i++; }else{ pro=new Product("桃子", 4.88); p.add(pro); i++; } System.out.println(Thread.currentThread().getName()+":生产了一个" +pro.name+",价格是"+pro.price+"¥"); // p.notify(); }else{ System.out.println(Thread.currentThread().getName()+":存储区域已经满了......"); try { p.wait(); } catch (InterruptedException e.printStackTrace(); } } } } } } /** * */ class Consumer List<Product> public Consumer(List<Product> p) { this.p=p; } @Override public while(true){ synchronized (p) { if(p.size()>0){ System.out.println(Thread.currentThread().getName()+ ":取出了一个"+p.get(0).name+",价格"+p.get(0).price+"¥"); p.remove(0); p.notify(); }else{ System.out.println(Thread.currentThread().getName()+":物品已经被取完......"); try { p.wait(); } catch (InterruptedException e.printStackTrace(); } } } } } } publicclass Tm11 { public List<Product> Producer producer=new Producer(p); Consumer consumer=new Consumer(p); Thread th1=new Thread(producer, Thread th2=new Thread(consumer,"消费者"); th1.start(); th2.start(); } } |
** 其他
后台线程:就是隐藏起来一直在默默运行的线程,直到进程结束。
setDaemon(booleanon)
当所有的非后台线程结束时,程序也就终止了同时还会杀死进程中的所有后台线程,也就是说,只要有非后台线程还在运行,程序就不会终止,执行main方法的主线程就是一个非后台线程。
必须在启动线程之前(调用start方法之前)调用setDaemon(true)方法,才可以把该线程设置为后台线程。
join方法
当A线程执行到了B线程Join方法时A就会等待,等B线程都执行完A才会执行,Join可以用来临时加入线程执行