用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”。
多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
并行与并发:
- 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
- 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
单线程会按照代码的调用顺序进行执行
多线程中,main()方法和MyThread类的run()方法可以同时运行.
继承Thread类创建多线程
public class Thread1 {
public static void main(String []args){
MyThread myThread = new MyThread();
myThread.start();
while(true){
System.out.println("Main函数运行");
}
}
}
class MyThread extends Thread{
public void run(){
while(true){
System.out.println("Thread函数运行");
}
}
}
//结果是Main函数运行和Thread函数运行交替打印.
实现Runnable接口创建多线程
java中,如果一个类继承某个父类就无法再继承Thread类,构造方法Thread(Runnable target),提供Runnable接口,只有一个run()方法…
public class Thread1 {
public static void main(String []args){
MyThread myThread = new MyThread(); //创建MyThread的实例对象
Thread thread = new Thread(myThread); //创建线程对象
thread.start(); //开启线程
while(true){
System.out.println("Main函数运行");
}
}
}
class MyThread implements Runnable{
public void run(){
while(true){
System.out.println("Thread函数运行");
}
}
}
两种实现多线程方法的对比分析
public class Thread1 {
public static void main(String []args){
//MyThread myThread = new MyThread(); //创建MyThread的实例对象
new MyThread().start(); //创建线程对象
new MyThread().start(); //创建线程对象
new MyThread().start(); //创建线程对象
new MyThread().start(); //创建线程对象
}
}
class MyThread extends Thread{
private int tickets = 100;
public void run(){
while(true){
if (tickets>0){
Thread th = Thread.currentThread(); //获取当前线程.
String th_name = th.getName(); //获取当前线程的名字..
System.out.println(th_name + " 正在发售第 : " + tickets-- + "张票");
}
}
}
}
运行结果:
从运行结果可以看出,每张票被打印了4次,出现这种现象的原因是四个线程没有共享100张票.
用户创建的第一个线程默认的名字为Thread-0,第二个默认为Thread-1,希望指定名称,可以调用setName(String
name)
一般来说,不同的线程是在独立地处理各自的资源,如果希望共用资源,可以调用Runnable,可以使用Thread(Runnabletarget,String name)设置名称. 这里以售票厅为例,多个售票窗口共同享用票数资源
public class Thread1 {
public static void main(String []args){
MyThread myThread = new MyThread(); //创建MyThread的实例对象
new Thread(myThread,"A").start(); //创建线程对象
new Thread(myThread,"B").start(); //创建线程对象
new Thread(myThread,"C").start(); //创建线程对象
new Thread(myThread,"D").start(); //创建线程对象
}
}
class MyThread implements Runnable{
private int tickets = 100;
public void run(){
while(true){
if (tickets>0){
Thread th = Thread.currentThread(); //获取当前线程.
String th_name = th.getName(); //获取当前线程的名字..
System.out.println(th_name + " 正在发售第 : " + tickets-- + "张票");
}
}
}
}
实现Runnable接口相对继承Thread有如下好处.
- 1.适合多个相同代码的线程去处理一个资源的情况,把线程同程序代码,数据有效分离,体现面对对象的设计思想.
- 2.避免单继承带来的局限性.
线程的生命周期和状态转移.
当Thread对象创建完成时,线程的生命周期便开始了。当run()方法中代码正常执行完毕或者线程抛出一个未捕获的异常(Exception)或者错误(Error),线程的生命周期结束.
整个生命周期分成5个阶段,
1.新建状态(New)
2.就绪状态(Runnable)
3.运行状态(Running)
4.阻塞状态(Blocked)
5.死亡状态(Terminated)
1.新建状态: 创建当前对象,但是不能运行,仅仅时分配了内存
2.就绪状态调用start()方法后,线程进入就绪状态(称可运行状态),具备运行条件,需要等待系统调用以获得CPU使用权.
3.运行状态开始执行run()方法中的线程体。不可能一直处于运行状态,当使用完系统分配时间后,会自动返回就绪状态
4.阻塞状态执行耗时的输入/输出操作时,放弃对CPU使用权,进入阻塞状态
- 试图获取某个对象同步锁,进入阻塞状态,当获取到其他线程持有的锁之后,会返回就绪状态.
- 线程调用阻塞式的IO方法,进入阻塞状态,等IO方法返回,会返回就绪状态.
- 调用某个对象的wait()方法,进入阻塞状态,使用notify()方法唤醒线程.
- 调用sleep()方法时,进入阻塞状态,等线程睡眠时间到达,自动进入就绪状态
- 调用另一个线程join()方法,需等新加入的线程结束后才能进入就绪状态.
如图所示
多线程同步
线程安全,售票案例中,极有可能碰到"意外"情况,如一张票被打印多次,或者出现票数为0甚至负数,这些都是线程安全问题.
//以下是没有使用同步锁,多个线程处理不同的对象.
public class Thread1 {
public static void main(String []args){
MyThread myThread = new MyThread(); //创建MyThread的实例对象
new Thread(myThread,"A").start(); //创建线程对象
new Thread(myThread,"B").start(); //创建线程对象
new Thread(myThread,"C").start(); //创建线程对象
new Thread(myThread,"D").start(); //创建线程对象
}
}
class MyThread implements Runnable{
private int tickets = 100;
public void run(){
while(tickets>0){
try{
Thread.sleep(10); //线程休眠10ms
}catch (Exception e){
e.printStackTrace();
}finally {
Thread th = Thread.currentThread(); //获取当前线程.
String th_name = th.getName(); //获取当前线程的名字..
System.out.println(th_name + " 正在发售第 : " + tickets-- + "张票");
}
}
}
}
运行结果:
可以看出,出现了-1.-2张票,这是不应该有的情况.因为在售票程序中做了判断,只有当票号大于0才会进行售票。之所以出现负数的票号是因为多线程售票出现了安全问题.
解决方法如下:
多线程同步-限制某资源在同一时刻只能被一个线程访问.
public class Thread1 {
public static void main(String []args){
MyThread myThread = new MyThread(); //创建MyThread的实例对象
new Thread(myThread,"A").start(); //创建线程对象
new Thread(myThread,"B").start(); //创建线程对象
new Thread(myThread,"C").start(); //创建线程对象
new Thread(myThread,"D").start(); //创建线程对象
}
}
class MyThread implements Runnable{
private int tickets = 30;
Object lock = new Object(); //定义任意一个对象,用作同步代码块的锁.
//默认情况下锁的标志位为1,线程执行同步代码块,同时将锁对象的标志位置为0.当一个线程执行到这段同步
//代码的时候,锁对象标志位为0,新线程发生阻塞,等待锁执行完,新线程才能执行同步代码块的代码.
public void run(){
while(true){
synchronized (lock){
try{
Thread.sleep(20); //线程休眠10ms
}catch (Exception e) {
e.printStackTrace();
}
if (tickets>0){
Thread th = Thread.currentThread(); //获取当前线程.
String th_name = th.getName(); //获取当前线程的名字..
System.out.println(th_name + " 正在发售第 : " + tickets-- + "张票");
}else break;
}
}
}
}
运行结果下图:
可以看出,解决了不同线程共同处理同一资源对象.
方法前面同样可以使用synchronized关键字修饰,被修饰的方法称为同步方法,实现和同步代码块一样的功能.
synchronized 返回值类型 方法名 ([参数一,…])同步方法的锁时调用该方法的对象,也就是this指的对象.静态方法的锁是类所在的class对象
多线程通信.
Object类提供wait(),notify(),notifyAll()方法解决线程间通信问题.调用者都应该是同步锁的对象,否则抛出IllegalMonitorStateException异常
wait():使当前线程放弃同步锁进入等待,直到其他线程进入此同步锁,调用notify()方法唤醒该线程
notify():唤醒同步锁等待的第一个调用wait()方法的线程.
class Storage{
private int [] cells = new int[10];
private int inPos,outPos; //存入时,取出时下标
private int count ;
public synchronized void put(int num){
try{
while(count == cells.length){
this.wait(); //如果放入数据等于cells长度,等待
}
cells[inPos] = num;
System.out.println("从cells["+inPos+"]放入数据"+cells[inPos]);
inPos++;
if (inPos==cells.length) inPos=0; //当在cells[9]放完数据后再从cells[0]开始
count ++; //每放一个数据count+1
this.notify();
}catch (Exception e){
e.printStackTrace();
}
}
public synchronized void get(){
try{
while(count == 0){
this.wait(); //如果count为0,此线程等待.
}
int data = cells[outPos];
System.out.println("从cells["+outPos+"]取出数据"+data);
cells[outPos] = 0; //取完,当前位置数据为0
outPos++;
if (outPos==cells.length) outPos = 0;
count--; //取出一个元素count-1
this.notify();
}catch (Exception e){
e.printStackTrace();
}
}
}
class Input implements Runnable{
private Storage st;
private int num;
Input(Storage st){
this.st = st;
}
public void run(){
while(true){
st.put(num++);
if (num==1000) break; //只看前1000个
}
}
}
class Output implements Runnable{
private Storage st;
Output(Storage st){
this.st = st;
}
public void run(){
while(true){
st.get();
}
}
}
public class Thread1{
public static void main(String []args){
Storage st = new Storage(); //创建数据存储类对象
Input input = new Input(st);
Output output = new Output(st);
new Thread(input).start();开启新线程
new Thread(output).start();
}
}
运行结果:
提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。
由于笔者水平有限,所以有不足之处还请提出,大家互相学习.