多线程相关(学习笔记)

1、线程与进程

  • 进程

    进程指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。一个应用程序可以同时运行多个进程,进程也是程序的一次执行过程,是系统运行程序的基本单位。系统运行一个程序即是一个进程从创建、运行到消亡的过程。
    进程的内存空间(堆空间和栈空间)是独立的。

  • 线程

    线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程至少有一个线程,也可以包含多个线程。
    线程的堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小。

  • 线程的状态:

    (1)初始化(NEW)
        创建线程,但未调用start()方法
    (2)运行(RUNNABLE)
        Java线程中将就绪(ready)和运行中(running)两种状态称为运行(RUNNABLE)状态。调用了start()方法后线程状态变为就绪,就绪状态的  线程获得CPU时间后变为运行。
    (3)阻塞(BLOCKED):表示线程阻塞于锁。当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状 态;当该线程获得锁时,该线程将变成Runnable状态。
    (4)等待(WAITING):一个线程在等待另一个线程执行一个(唤醒)动作时,该线程处于Waiting状态。进入这个状态后必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
    (5)超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、 Object.wait。
    (6)终止(TERMINATED):表示该线程已经执行完毕,因为run方法正常退出而死亡,或被没有捕获的异常终止而死亡。
    

2、线程的创建方式

  • 线程类

     Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码,Java使用线程执行体来代表这段程序流。
    
  • Thread类
    构造方法

     (1)public Thread() 分配一个新的线程对象
     (2)public Thread(String name) 分配一个指定名字的新的线程对象
     (3)public Thread(Runnable target) 分配一个带有指定目标新的线程对象
     (4)public Thread(Runnable target,String name) 分配一个带有指定目标新的线程对象并指定名字   
    

常用方法

 - public String getName() :获取当前线程名称。
 - public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
 - public void run() :此线程要执行的任务在此处定义代码。
 - public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
 - public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

(1)、创建线程的方式一(继承Thread类)

1、创建一个类继承Thread类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,所以 run()方法就是线程执行体。
2、在test类的main方法中创建Thread子类的实例,即创建线程对象。
3、调用线程对象的start()方法来启动该线程。
public class MyThread extends Tread{
	//重写Thread类的run方法,设置线程任务
	@Override 
	public void run(){
		方法体
	}
}

public class Test(){
	public static void main(){
		MyThread mt = new MyThread();
		mt.start();
		//主线程开启新线程之后继续执行的代码
		for (int i = 0;i<1000;i++){
           	System.out.println("main"+i);
      		}
	}
}

(2)、创建线程的方式二(实现Runnable接口创建线程)

1、创建一个类实现Runnable接口
2、重写Runnable接口中的run方法,该run()方法的方法体就代表了线程需要完成的任务,所以 run()方法就是线程执行体。
3、在test类的main方法中创建Runnable接口的实现类对象
4、创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5、调用Thread类中的start()方法,开启新的线程,执行run方法
public class RunnableImpl implements Runnable{
    //重写Runnable接口中的run方法,设置线程任务
    @Override
    public void run() {
        //新线程执行的代码	        
        }
    }
}
public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl r = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(r);
        //调用Thread类中的start方法,开启新的线程,执行run方法
        t.start();
        //主线程开启新线程之后继续执行的代码
        for (int i = 0; i <20; i++) {
            System.out.println(i);
        }
    }

使用Runnable接口的好处

 - 避免单继承的局限性,一个类继承了Thread类就不能继承其他的类,但是一个类实现了Runnable接口,还可以继续继承别的类,实现其他的接口(伪多继承)。
 - 增强了程序的扩展性,降低程序的耦合度,使用Runnable接口把设置线程任务和开启线程相分离,实现类当中,重写run方法,设置线程任务,创建Thread类对象,调用start()方法,开启新线程。

(3)、创建线程的方式三(匿名内部类创建线程)

创建线程有2种方式,所以匿名内部类创建线程也有两种方式:
公式一(继承Thread类):

public static void main(String[] args) {
    new Thread(){
    		//new匿名的继承Thread类的类
            //重写父类中的run()方法
            @Override
            public void run() {
        		//新线程执行的代码	        
        	}           
    }.start();
}

例:

public static void main(String[] args) {
        new Thread(){    
        	//new 匿名的继承Thread类的类
            //重写run方法,设置线程任务
            @Override
            public void run() {
                for (int i = 0; i <20 ; i++) {
                    System.out.println(i);
                }
            }
        }.start();
 }

公式二(实现Runnable接口):

public static void main(String[] args) {
	new Thread(new Runnable(){
			//new 匿名的实现Runnable接口的类
			//重写父类中的run()方法
            @Override
            public void run() {
        		//新线程执行的代码	        
        	}
	}).start();
}

例:

public static void main(String[] args) {
	   new Thread(new Runnable() {  
		        //new没有名称的实现了Runnable接口的类
	            //重写接口中的run方法
	            @Override
	            public void run() { //实现接口当中run方法
	                for (int i = 0; i <20 ; i++) {
	                    System.out.println(i);
	                }
            	}
       }).start();
}

3、线程安全

多个线程同时访问共享的数据,可能会产生安全问题

例:多个窗口同时卖一种票,如果不进行控制, 可以会出现卖重复的现象

public class TicketRunnableImpl implements Runnable {
    //初始化票数
    private int ticket = 10;
    //卖票
    @Override
    public void run() {
        while (ticket > 0) {
            /*为了提高线程安全问题出现的几率
              让线程睡眠10毫秒,放弃cpu的执行权*/
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卖票操作,ticket--
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}

开启多线程同时执行

public static void main(String[] args) {
    	//创建Runnable接口的实现类对象
        RunnableImpl r = new RunnableImpl();
        //创建3个线程
        Thread t0 = new Thread(r);
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        //启动新的线程
        t0.start();
        t1.start();
        t2.start();
}

**解决线程安全问题的方法

1、同步代码块synchronized

格式:

synchronized(锁对象){
            出现安全问题的代码(访问了共享数据的代码)
 }

注意:
1.锁对象可以是任意对象 new Person、new Student …
2.必须保证多个线程使用的是同一个锁对象
3.锁对象的作用:把{}中代码锁住,只让一个线程进去执行

例:

public class TicketRunnableImpl implements Runnable {
    //定义共享的票源
    private int ticket = 100;
    private Object obj = new Object(); //锁对象
    //线程任务:卖票
    @Override
    public void run() {
        synchronized (obj){
            while (ticket > 0) {
                /*为了提高线程安全问题出现的几率
                  让线程睡眠10毫秒,放弃cpu的执行权*/
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //卖票操作,ticket--
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
            }
        }
    }
}

说明:

- 同步中的线程如果没有执行完毕,不会释放锁对象。同步外的线程没有锁对象无法进入同步代码块,此时进入阻塞状态 
- 出了同步后,线程会归还锁对象。同步锁保证了在同一时间只能有一个线程访问共享数据
- 程序频繁获得锁、释放锁会降低效率。

2、同步方法synchronized

格式:

    修饰符 synchronized 返回值类型 方法名(参数列表){
            出现安全问题的代码(访问了共享数据的代码)
    }

例:

 @Override
    public void run() {
        ticketMethods();
    }
    public synchronized void ticketMethods(){
        while (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卖票操作,ticket--
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
            ticket--;
        }
    }

还有另一种写法(两种写法其实相同,只不过下面这种体现出同步方法中锁的对象为this):

 public  void ticketMethods(){
        synchronized(this){
            while (ticket > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //卖票操作,ticket--
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                ticket--;
            }
        }
    }

3、静态同步方法
格式:

    修饰符 static synchronized 返回值类型 方法名(参数列表){
            出现安全问题的代码(访问了共享数据的代码)
    }

此处锁对象是谁?

答:当前方法所在类的字节码对象(类名.class)

例:

public class TicketRunnableImpl implements Runnable {
    //定义共享的票源
    private static int ticket = 10;
    private Object obj = new Object(); //锁对象
    //线程任务:卖票
    @Override
    public void run() {
        ticketMethods();
    }
    public static synchronized void ticketMethods(){
            while (ticket > 0) {
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //卖票操作,ticket--
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                ticket--;
            }
    }
}

4、Lock锁

java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。

Lock接口中最主要用的方法:

void lock() 获取锁。(用在要锁的代码之前)
void unlock() 释放锁。(用在要锁的代码之后)

用法:

1、创建一个Lock接口的实现类对象ReentrantLock
2、在需要同步锁的代码前使用Lock接口的实现类对象调用lock方法获取锁对象
3、在需要同步锁的代码后Lock接口的实现类对象调用unlock方法释放锁对象

例:

public class TicketRunnableImpl implements Runnable {
    //定义共享的票源
    private  int ticket = 100;
    //1.创建一个Lock接口的实现类对象ReentrantLock
    Lock l = new ReentrantLock();
    //线程任务:卖票
    @Override
    public void run() {
        while (true) {
        	//获得锁对象
            l.lock();
            if (ticket > 0){
                try {
                    Thread.sleep(5);
                    //卖票操作,ticket--
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {                   
                    l.unlock(); //释放锁对象
                }
            }
        }
    }
}

以上内容查阅了网上资料,参考了其他文章,仅作为自己学习笔记。
参考文章连接:
https://www.kuangstudy.com/bbs/1373202797771706370
https://blog.csdn.net/pange1991/article/details/53860651

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值